modprobe-small: (un)load all modules which match the alias, not only first one
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 21 Apr 2014 14:59:36 +0000 (16:59 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 21 Apr 2014 14:59:36 +0000 (16:59 +0200)
Closes 627 and 7034.

Commonly seen case is (un)loading of an alias
which matches ata_generic and a more specific ata module.

For example:

modprobe [-r] pci:v00008086d00007010sv00000000sd00000000bc01sc01i80
(ata_generic and pata_acpi)

modprobe [-r] pci:v00001106d00000571sv00001509sd00009022bc01sc01i8a
(ata_generic and pata_via)

function                                             old     new   delta
process_module                                       615     728    +113
parse_module                                         309     395     +86
find_alias                                           621     653     +32
pathname_matches_modname                              78      79      +1
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/0 up/down: 232/0)             Total: 232 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
modutils/modprobe-small.c

index 223eba915cc18c120f156997ba27c9ebe46a50a0..91e0c1380885c33b5e764b9c5d31e7bb6e48acdf 100644 (file)
@@ -22,6 +22,9 @@
 extern int init_module(void *module, unsigned long len, const char *options);
 extern int delete_module(const char *module, unsigned flags);
 extern int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret);
+/* linux/include/linux/module.h has limit of 64 chars on module names */
+#undef MODULE_NAME_LEN
+#define MODULE_NAME_LEN 64
 
 
 #if 1
@@ -143,6 +146,19 @@ static void replace(char *s, char what, char with)
        }
 }
 
+static char *filename2modname(const char *filename, char *modname)
+{
+       int i;
+       char *from;
+
+       from = bb_get_last_path_component_nostrip(filename);
+       for (i = 0; i < (MODULE_NAME_LEN-1) && from[i] != '\0' && from[i] != '.'; i++)
+               modname[i] = (from[i] == '-') ? '_' : from[i];
+       modname[i] = '\0';
+
+       return modname;
+}
+
 /* Take "word word", return malloced "word",NUL,"word",NUL,NUL */
 static char* str_2_list(const char *str)
 {
@@ -277,14 +293,13 @@ static void parse_module(module_info *info, const char *pathname)
 
 static int pathname_matches_modname(const char *pathname, const char *modname)
 {
+       int r;
+       char name[MODULE_NAME_LEN];
        const char *fname = bb_get_last_path_component_nostrip(pathname);
        const char *suffix = strrstr(fname, ".ko");
-//TODO: can do without malloc?
-       char *name = xstrndup(fname, suffix - fname);
-       int r;
+       safe_strncpy(name, fname, suffix - fname);
        replace(name, '-', '_');
        r = (strcmp(name, modname) == 0);
-       free(name);
        return r;
 }
 
@@ -447,11 +462,12 @@ static void write_out_dep_bb(int fd)
        }
 }
 
-static module_info* find_alias(const char *alias)
+static module_info** find_alias(const char *alias)
 {
        int i;
        int dep_bb_fd;
-       module_info *result;
+       int infoidx;
+       module_info **infovec;
        dbg1_error_msg("find_alias('%s')", alias);
 
  try_again:
@@ -464,7 +480,9 @@ static module_info* find_alias(const char *alias)
                        if (!modinfo[i].aliases) {
                                parse_module(&modinfo[i], modinfo[i].pathname);
                        }
-                       return &modinfo[i];
+                       infovec = xzalloc(2 * sizeof(infovec[0]));
+                       infovec[0] = &modinfo[i];
+                       return infovec;
                }
                i++;
        }
@@ -477,16 +495,13 @@ static module_info* find_alias(const char *alias)
 
        /* Scan all module bodies, extract modinfo (it contains aliases) */
        i = 0;
-       result = NULL;
+       infoidx = 0;
+       infovec = NULL;
        while (modinfo[i].pathname) {
                char *desc, *s;
                if (!modinfo[i].aliases) {
                        parse_module(&modinfo[i], modinfo[i].pathname);
                }
-               if (result) {
-                       i++;
-                       continue;
-               }
                /* "alias1 symbol:sym1 alias2 symbol:sym2" */
                desc = str_2_list(modinfo[i].aliases);
                /* Does matching substring exist? */
@@ -498,13 +513,12 @@ static module_info* find_alias(const char *alias)
                        if (fnmatch(s, alias, 0) == 0) {
                                dbg1_error_msg("found alias '%s' in module '%s'",
                                                alias, modinfo[i].pathname);
-                               result = &modinfo[i];
+                               infovec = xrealloc_vector(infovec, 1, infoidx);
+                               infovec[infoidx++] = &modinfo[i];
                                break;
                        }
                }
                free(desc);
-               if (result && dep_bb_fd < 0)
-                       return result;
                i++;
        }
 
@@ -513,8 +527,8 @@ static module_info* find_alias(const char *alias)
                write_out_dep_bb(dep_bb_fd);
        }
 
-       dbg1_error_msg("find_alias '%s' returns %p", alias, result);
-       return result;
+       dbg1_error_msg("find_alias '%s' returns %d results", alias, infoidx);
+       return infovec;
 }
 
 #if ENABLE_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED
@@ -550,14 +564,23 @@ static int already_loaded(const char *name)
 static void process_module(char *name, const char *cmdline_options)
 {
        char *s, *deps, *options;
+       module_info **infovec;
        module_info *info;
+       int infoidx;
        int is_rmmod = (option_mask32 & OPT_r) != 0;
+
        dbg1_error_msg("process_module('%s','%s')", name, cmdline_options);
 
        replace(name, '-', '_');
 
        dbg1_error_msg("already_loaded:%d is_rmmod:%d", already_loaded(name), is_rmmod);
-       if (already_loaded(name) != is_rmmod) {
+       /*
+        * We used to have "is_rmmod != already_loaded(name)" check here, but
+        *  modprobe -r pci:v00008086d00007010sv00000000sd00000000bc01sc01i80
+        * won't unload modules (there are more than one)
+        * which have this alias.
+        */
+       if (!is_rmmod && already_loaded(name)) {
                dbg1_error_msg("nothing to do for '%s'", name);
                return;
        }
@@ -586,39 +609,51 @@ static void process_module(char *name, const char *cmdline_options)
        if (!module_count) {
                /* Scan module directory. This is done only once.
                 * It will attempt module load, and will exit(EXIT_SUCCESS)
-                * on success. */
+                * on success.
+                */
                module_found_idx = -1;
                recursive_action(".",
                        ACTION_RECURSE, /* flags */
                        fileAction, /* file action */
                        NULL, /* dir action */
                        name, /* user data */
-                       0); /* depth */
+                       0 /* depth */
+               );
                dbg1_error_msg("dirscan complete");
                /* Module was not found, or load failed, or is_rmmod */
                if (module_found_idx >= 0) { /* module was found */
-                       info = &modinfo[module_found_idx];
+                       infovec = xzalloc(2 * sizeof(infovec[0]));
+                       infovec[0] = &modinfo[module_found_idx];
                } else { /* search for alias, not a plain module name */
-                       info = find_alias(name);
+                       infovec = find_alias(name);
                }
        } else {
-               info = find_alias(name);
+               infovec = find_alias(name);
        }
 
-// Problem here: there can be more than one module
-// for the given alias. For example,
-// "pci:v00008086d00007010sv00000000sd00000000bc01sc01i80" matches
-// ata_piix because it has an alias "pci:v00008086d00007010sv*sd*bc*sc*i*"
-// and ata_generic, it has an alias "pci:v*d*sv*sd*bc01sc01i*"
-// Standard modprobe would load them both.
-// In this code, find_alias() returns only the first matching module.
+       /* There can be more than one module for the given alias. For example,
+        * "pci:v00008086d00007010sv00000000sd00000000bc01sc01i80" matches
+        * ata_piix because it has alias "pci:v00008086d00007010sv*sd*bc*sc*i*"
+        * and ata_generic, it has alias "pci:v*d*sv*sd*bc01sc01i*"
+        * Standard modprobe loads them both. We achieve it by returning
+        * a *list* of modinfo pointers from find_alias().
+        */
 
-       /* rmmod? unload it by name */
+       /* rmmod or modprobe -r? unload module(s) */
        if (is_rmmod) {
-               if (delete_module(name, O_NONBLOCK | O_EXCL) != 0) {
-                       if (!(option_mask32 & OPT_q))
-                               bb_perror_msg("remove '%s'", name);
-                       goto ret;
+               infoidx = 0;
+               while ((info = infovec[infoidx++]) != NULL) {
+                       int r;
+                       char modname[MODULE_NAME_LEN];
+
+                       filename2modname(info->pathname, modname);
+                       r = delete_module(modname, O_NONBLOCK | O_EXCL);
+                       dbg1_error_msg("delete_module('%s', O_NONBLOCK | O_EXCL):%d", modname, r);
+                       if (r != 0) {
+                               if (!(option_mask32 & OPT_q))
+                                       bb_perror_msg("remove '%s'", modname);
+                               goto ret;
+                       }
                }
 
                if (applet_name[0] == 'r') {
@@ -634,7 +669,7 @@ static void process_module(char *name, const char *cmdline_options)
                 */
        }
 
-       if (!info) {
+       if (!infovec) {
                /* both dirscan and find_alias found nothing */
                if (!is_rmmod && applet_name[0] != 'd') /* it wasn't rmmod or depmod */
                        bb_error_msg("module '%s' not found", name);
@@ -642,36 +677,41 @@ static void process_module(char *name, const char *cmdline_options)
                goto ret;
        }
 
-       /* Iterate thru dependencies, trying to (un)load them */
-       deps = str_2_list(info->deps);
-       for (s = deps; *s; s += strlen(s) + 1) {
-               //if (strcmp(name, s) != 0) // N.B. do loops exist?
-               dbg1_error_msg("recurse on dep '%s'", s);
-               process_module(s, NULL);
-               dbg1_error_msg("recurse on dep '%s' done", s);
-       }
-       free(deps);
+       infoidx = 0;
+       while ((info = infovec[infoidx++]) != NULL) {
+               /* Iterate thru dependencies, trying to (un)load them */
+               deps = str_2_list(info->deps);
+               for (s = deps; *s; s += strlen(s) + 1) {
+                       //if (strcmp(name, s) != 0) // N.B. do loops exist?
+                       dbg1_error_msg("recurse on dep '%s'", s);
+                       process_module(s, NULL);
+                       dbg1_error_msg("recurse on dep '%s' done", s);
+               }
+               free(deps);
 
-       /* modprobe -> load it */
-       if (!is_rmmod) {
-               if (!options || strstr(options, "blacklist") == NULL) {
-                       errno = 0;
-                       if (load_module(info->pathname, options) != 0) {
-                               if (EEXIST != errno) {
-                                       bb_error_msg("'%s': %s",
-                                               info->pathname,
-                                               moderror(errno));
-                               } else {
-                                       dbg1_error_msg("'%s': %s",
-                                               info->pathname,
-                                               moderror(errno));
-                               }
-                       }
-               } else {
+               if (is_rmmod)
+                       continue;
+
+               /* We are modprobe: load it */
+               if (options && strstr(options, "blacklist")) {
                        dbg1_error_msg("'%s': blacklisted", info->pathname);
+                       continue;
+               }
+               errno = 0;
+               if (load_module(info->pathname, options) != 0) {
+                       if (EEXIST != errno) {
+                               bb_error_msg("'%s': %s",
+                                       info->pathname,
+                                       moderror(errno));
+                       } else {
+                               dbg1_error_msg("'%s': %s",
+                                       info->pathname,
+                                       moderror(errno));
+                       }
                }
        }
  ret:
+       free(infovec);
        free(options);
 //TODO: return load attempt result from process_module.
 //If dep didn't load ok, continuing makes little sense.