@@ -102,5 +102,6 @@ kmod_module_get_initstate
kmod_module_initstate_str
kmod_module_get_size
kmod_module_get_refcnt
+kmod_module_get_refcnt_wait
kmod_module_get_holders
</SECTION>
@@ -1915,6 +1915,56 @@ KMOD_EXPORT int kmod_module_get_refcnt(const struct kmod_module *mod)
return (int)refcnt;
}
+/**
+ * kmod_module_get_refcnt_wait:
+ * @mod: kmod module
+ * @wait: if true will wait until the refcnt is 0 patiently forever
+ *
+ * Get the ref count of this @mod, as returned by Linux Kernel, by reading
+ * /sys filesystem and wait patiently until the refcnt is 0, if the wait bool
+ * argument is set to true, otherwise this behaves just as the call
+ * kmod_module_get_refcnt(). Enabling wait is useful if you know for sure that
+ * the module is quiescing soon, and so you should be able to remove it soon.
+ * If wait is enabled, we wait 1 second per iteration check on the refcnt.
+ *
+ * Returns: the reference count on success or < 0 on failure.
+ */
+KMOD_EXPORT int kmod_module_get_refcnt_wait(const struct kmod_module *mod, bool wait)
+{
+ char path[PATH_MAX];
+ long refcnt;
+ int fd, err;
+
+ if (mod == NULL)
+ return -ENOENT;
+
+ snprintf(path, sizeof(path), "/sys/module/%s/refcnt", mod->name);
+
+ while (true) {
+ fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ err = -errno;
+ DBG(mod->ctx, "could not open '%s': %s\n",
+ path, strerror(errno));
+ return err;
+ }
+
+ err = read_str_long(fd, &refcnt, 10);
+ close(fd);
+ if (err < 0) {
+ ERR(mod->ctx, "could not read integer from '%s': '%s'\n",
+ path, strerror(-err));
+ return err;
+ }
+ if ((refcnt <= 0) || (refcnt > 0 && !wait))
+ break;
+ ERR(mod->ctx, "%s refcnt is %ld waiting for it to become 0\n", mod->name, refcnt);
+ sleep(1);
+ }
+
+ return (int)refcnt;
+}
+
/**
* kmod_module_get_holders:
* @mod: kmod module
@@ -217,6 +217,7 @@ enum kmod_module_initstate {
const char *kmod_module_initstate_str(enum kmod_module_initstate state);
int kmod_module_get_initstate(const struct kmod_module *mod);
int kmod_module_get_refcnt(const struct kmod_module *mod);
+int kmod_module_get_refcnt_wait(const struct kmod_module *mod, bool wait);
struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod);
struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod);
const char *kmod_module_section_get_name(const struct kmod_list *entry);
@@ -49,6 +49,7 @@ global:
kmod_module_initstate_str;
kmod_module_get_initstate;
kmod_module_get_refcnt;
+ kmod_module_get_refcnt_wait;
kmod_module_get_sections;
kmod_module_section_free_list;
kmod_module_section_get_name;
@@ -99,6 +99,7 @@ cdef extern from 'libkmod/libkmod.h':
# Information regarding "live information" from module's state, as
# returned by kernel
int kmod_module_get_refcnt(const_kmod_module_ptr mod)
+ int kmod_module_get_refcnt_wait(const_kmod_module_ptr mod, bool install)
long kmod_module_get_size(const_kmod_module_ptr mod)
# Information retrieved from ELF headers and section
@@ -72,6 +72,10 @@ cdef class Module (object):
return _libkmod_h.kmod_module_get_refcnt(self.module)
refcnt = property(fget=_refcnt_get)
+ def _refcnt_get_wait(self, wait=False):
+ return _libkmod_h.kmod_module_get_refcnt_wait(self.module, wait)
+ refcnt = property(fget=_refcnt_get_wait)
+
def _size_get(self):
return _libkmod_h.kmod_module_get_size(self.module)
size = property(fget=_size_get)
@@ -388,6 +388,28 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <option>-p</option>
+ </term>
+ <term>
+ <option>--remove-patiently</option>
+ </term>
+ <listitem>
+ <para>
+ This option causes <command>modprobe</command> to try to patiently
+ remove a module by waiting until its refcnt is 0. It checks the refcnt
+ and if its 0 it will immediately remove the module. If the refcnt is
+ not 0, it will sleep 1 second and check the refcnt again, and repeat
+ this until an error is found or the refcnt is 0. You can send a signal
+ to this command if you do not want to wait anymore.
+ </para>
+ <para>
+ Removing modules may be done by test infrastruture code, it can also
+ be done to remove a live kernel patch.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term>
<option>-S</option>
@@ -39,6 +39,7 @@
<cmdsynopsis>
<command>rmmod</command>
<arg><option>-f</option></arg>
+ <arg><option>-p</option></arg>
<arg><option>-s</option></arg>
<arg><option>-v</option></arg>
<arg><replaceable>modulename</replaceable></arg>
@@ -92,6 +93,28 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <option>-p</option>
+ </term>
+ <term>
+ <option>--remove-patiently</option>
+ </term>
+ <listitem>
+ <para>
+ This option tries to remove the module patiently by waiting
+ until the module refcnt is 0. It checks the refcnt
+ and if its 0 it will immediately remove the module. If the refcnt is
+ not 0, it will sleep 1 second and check the refcnt again, and repeat
+ this until an error is found or the refcnt is 0. You can send a signal
+ to this command if you do not want to wait anymore.
+ </para>
+ <para>
+ Removing modules may be done by test infrastruture code, it can also
+ be done to remove a live kernel patch.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term>
<option>-s</option>
@@ -56,11 +56,13 @@ static int strip_modversion = 0;
static int strip_vermagic = 0;
static int remove_dependencies = 0;
static int quiet_inuse = 0;
+static int do_remove_patient = 0;
-static const char cmdopts_s[] = "arRibfDcnC:d:S:sqvVh";
+static const char cmdopts_s[] = "arpRibfDcnC:d:S:sqvVh";
static const struct option cmdopts[] = {
{"all", no_argument, 0, 'a'},
{"remove", no_argument, 0, 'r'},
+ {"remove-patiently", no_argument, 0, 'p'},
{"remove-dependencies", no_argument, 0, 5},
{"resolve-alias", no_argument, 0, 'R'},
{"first-time", no_argument, 0, 3},
@@ -107,6 +109,7 @@ static void help(void)
"\t be a module name to be inserted\n"
"\t or removed (-r)\n"
"\t-r, --remove Remove modules instead of inserting\n"
+ "\t-p, --remove-patiently Patiently wait until the refcnt is 0 to remove\n"
"\t --remove-dependencies Also remove modules depending on it\n"
"\t-R, --resolve-alias Only lookup and print alias and exit\n"
"\t --first-time Fail if module already inserted or removed\n"
@@ -424,7 +427,7 @@ static int rmmod_do_module(struct kmod_module *mod, int flags)
}
if (!ignore_loaded && !cmd) {
- int usage = kmod_module_get_refcnt(mod);
+ int usage = kmod_module_get_refcnt_wait(mod, do_remove_patient);
if (usage > 0) {
if (!quiet_inuse)
@@ -785,6 +788,10 @@ static int do_modprobe(int argc, char **orig_argv)
case 'r':
do_remove = 1;
break;
+ case 'p':
+ do_remove = 1;
+ do_remove_patient = 1;
+ break;
case 5:
remove_dependencies = 1;
break;
@@ -27,9 +27,12 @@
#include "kmod.h"
-static const char cmdopts_s[] = "h";
+static int do_remove_patient = 0;
+
+static const char cmdopts_s[] = "hp";
static const struct option cmdopts[] = {
{"help", no_argument, 0, 'h'},
+ {"remove-patiently", no_argument, 0, 'p'},
{ }
};
@@ -74,7 +77,7 @@ static int check_module_inuse(struct kmod_module *mod) {
return -EBUSY;
}
- ret = kmod_module_get_refcnt(mod);
+ ret = kmod_module_get_refcnt_wait(mod, do_remove_patient);
if (ret > 0) {
ERR("Module %s is in use\n", kmod_module_get_name(mod));
return -EBUSY;
@@ -101,6 +104,9 @@ static int do_remove(int argc, char *argv[])
case 'h':
help();
return EXIT_SUCCESS;
+ case 'p':
+ do_remove_patient = 1;
+ break;
default:
ERR("Unexpected getopt_long() value '%c'.\n", c);
@@ -35,10 +35,12 @@
#define DEFAULT_VERBOSE LOG_ERR
static int verbose = DEFAULT_VERBOSE;
static int use_syslog;
+static int do_remove_patient;
-static const char cmdopts_s[] = "fsvVwh";
+static const char cmdopts_s[] = "fpsvVwh";
static const struct option cmdopts[] = {
{"force", no_argument, 0, 'f'},
+ {"remove-patiently", no_argument, 0, 'p'},
{"syslog", no_argument, 0, 's'},
{"verbose", no_argument, 0, 'v'},
{"version", no_argument, 0, 'V'},
@@ -93,7 +95,7 @@ static int check_module_inuse(struct kmod_module *mod) {
return -EBUSY;
}
- ret = kmod_module_get_refcnt(mod);
+ ret = kmod_module_get_refcnt_wait(mod, do_remove_patient);
if (ret > 0) {
ERR("Module %s is in use\n", kmod_module_get_name(mod));
return -EBUSY;
@@ -120,6 +122,9 @@ static int do_rmmod(int argc, char *argv[])
case 'f':
flags |= KMOD_REMOVE_FORCE;
break;
+ case 'p':
+ do_remove_patient = 1;
+ break;
case 's':
use_syslog = 1;
break;
When doing tests with modules such as scsi_debug, on test frameworks such as fstests [0] you may run into situations where false positives are triggered and a test fails but the reality is that the test did not fail, but what did fail was the removal of the module since the refcnt is not yet 0, as there is a delay in between umount and the module quiesces. This is documented on korg#21233 [1]. Although there are patches for fstests to account for this [2] and work around it, a much suitable solution long term is for these hacks to use a patient module remover from modprobe directly. This patch does just that, it adds the -p option and --remove-patiently to modprobe which let's a removal attempt wait until the refcnt is 0. This is useful for cases where you know the refcnt is going to become 0, and it is just a matter of time. This adds a new call kmod_module_get_refcnt_wait() which works just as kmod_module_get_refcnt() but gives you the option to patiently wait. This then updates modprobe, rmmod to support this feature usign the -p or --remove-patiently argument. [0] git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git [1] https://bugzilla.kernel.org/show_bug.cgi?id=21233 [2] https://lkml.kernel.org/r/20210727201045.2540681-1-mcgrof@kernel.org Signed-off-by: Luis Chamberlain <mcgrof@kernel.org> --- Cc'ing folks who I think *might* be interested in a patient module removal such as live-patching and blktests folks. libkmod/docs/libkmod-sections.txt | 1 + libkmod/libkmod-module.c | 50 ++++++++++++++++++++++++++++++ libkmod/libkmod.h | 1 + libkmod/libkmod.sym | 1 + libkmod/python/kmod/_libkmod_h.pxd | 1 + libkmod/python/kmod/module.pyx | 4 +++ man/modprobe.xml | 22 +++++++++++++ man/rmmod.xml | 23 ++++++++++++++ tools/modprobe.c | 11 +++++-- tools/remove.c | 10 ++++-- tools/rmmod.c | 9 ++++-- 11 files changed, 127 insertions(+), 6 deletions(-)