/* modprobe.c: add or remove a module from the kernel, intelligently. * * Copyright (C) 2001 Rusty Russell. * Copyright (C) 2002, 2003 Rusty Russell, IBM Corporation. * Copyright (C) 2006-2011 Jon Masters , and others. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #define _GNU_SOURCE /* asprintf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "elfops.h" #include "zlibsupport.h" #include "logging.h" #include "index.h" #include "list.h" #include "config_filter.h" #include "testing.h" /* NOTE: in the future, binary indexes will always be used */ static int use_binary_indexes = 1; /* default to enabled. */ /* Limit do_softdep/do_modprobe recursion. * This is a simple way to handle dependency loops * caused by poorly written softdep commands. */ static int recursion_depth = 0; static const int MAX_RECURSION = 50; /* Arbitrary choice */ extern long init_module(void *, unsigned long, const char *); extern long delete_module(const char *, unsigned int); struct module { struct list_head list; char *modname; char filename[0]; }; typedef enum { mit_remove = 1, mit_dry_run = 2, mit_first_time = 4, mit_use_blacklist = 8, mit_ignore_commands = 16, mit_ignore_loaded = 32, mit_quiet_inuse = 64, mit_strip_vermagic = 128, mit_strip_modversion = 256, mit_resolve_alias = 512 } modprobe_flags_t; #ifndef MODULE_DIR #define MODULE_DIR "/lib/modules" #endif /** * print_usage - output the prefered program usage * * @progname: binary name invoked * */ static void print_usage(const char *progname) { fprintf(stderr, "Usage: %s [-v] [-V] [-C config-file] [-d ] [-n] [-i] [-q] [-b] [-o ] [ --dump-modversions ] [parameters...]\n" "%s -r [-n] [-i] [-v] ...\n" "%s -l -t [ -a ...]\n", progname, progname, progname); exit(1); } /** * find_module - search module list for module name * * @filename: module file name * @list: module list * */ static struct module *find_module(const char *filename, struct list_head *list) { struct module *i; list_for_each_entry(i, list, list) { if (streq(i->filename, filename)) return i; } return NULL; } /** * add_module - add a module to the global module list * * @filename: module file name * @list: module list * */ static void add_module(char *filename, int namelen, struct list_head *list) { struct module *mod; /* If it's a duplicate: move it to the end, so it gets inserted where it is *first* required. */ mod = find_module(filename, list); if (mod) list_del(&mod->list); else { /* No match. Create a new module. */ mod = NOFAIL(malloc(sizeof(struct module) + namelen + 1)); memcpy(mod->filename, filename, namelen); mod->filename[namelen] = '\0'; mod->modname = NOFAIL(malloc(namelen + 1)); filename2modname(mod->modname, mod->filename); } list_add_tail(&mod->list, list); } /** * free_module - de-allocate module structure * * @mod: module structure * */ static void free_module(struct module *mod) { free(mod->modname); free(mod); } /** * modname_equal - compare module names (up to len), with '_' and '-' equal * * @a: first module name * @b: second module name * @len: length to compare * */ static int modname_equal(const char *a, const char *b, unsigned int len) { unsigned int i; if (strlen(b) != len) return 0; for (i = 0; i < len; i++) { if ((a[i] == '_' || a[i] == '-') && (b[i] == '_' || b[i] == '-')) continue; if (a[i] != b[i]) return 0; } return 1; } /** * add_modules_dep_line - parse a dep line from the module.dep[.bin] file * * @line: input file line * @name: module name * @list: list of modules * @dirname: module directory * * Add dependency information if this line of the dep file matches mod name */ static int add_modules_dep_line(char *line, const char *name, struct list_head *list, const char *dirname) { char *ptr; int len; char *modname, *fullpath; /* Ignore lines without : or which start with a # */ ptr = strchr(line, ':'); if (ptr == NULL || line[strspn(line, "\t ")] == '#') return 0; /* Is this the module we are looking for? */ *ptr = '\0'; modname = my_basename(line); len = strlen(modname); if (strchr(modname, '.')) len = strchr(modname, '.') - modname; if (!modname_equal(modname, name, len)) return 0; /* Create the list. */ if ('/' == line[0]) { /* old style deps - absolute path specified */ add_module(line, ptr - line, list); } else { nofail_asprintf(&fullpath, "%s/%s", dirname, line); add_module(fullpath, strlen(dirname)+1+(ptr - line), list); free(fullpath); } ptr++; for(;;) { char *dep_start; ptr += strspn(ptr, " \t"); if (*ptr == '\0') break; dep_start = ptr; ptr += strcspn(ptr, " \t"); /* We handle deps in two possible ways. Either they have */ /* an absolute path, or a relative path (to toplevel moddir). */ if ('/' == dep_start[0]) { /* old style deps */ add_module(dep_start, ptr - dep_start, list); } else { nofail_asprintf(&fullpath, "%s/%s", dirname, dep_start); add_module(fullpath, strlen(dirname)+1+(ptr - dep_start), list); free(fullpath); } } return 1; } /** * read_depends_file - import the modules.dep.bin file * * @dirname: module directory * @start_name: initial module name to search * @list: list of modules * */ static int read_depends_file(const char *dirname, const char *start_name, struct list_head *list) { char *modules_dep_name; char *line; struct index_file *modules_dep; nofail_asprintf(&modules_dep_name, "%s/%s", dirname, "modules.dep.bin"); modules_dep = index_file_open(modules_dep_name); if (!modules_dep) { free(modules_dep_name); return 0; } line = index_search(modules_dep, start_name); if (line) { /* Value is standard dependency line format */ if (!add_modules_dep_line(line, start_name, list, dirname)) fatal("Module index is inconsistent\n"); free(line); } index_file_close(modules_dep); free(modules_dep_name); return 1; } /** * read_depends - import the modules.dep[.bin] file * * @dirname: module directory * @start_name: initial module name to search * @list: list of modules * */ static void read_depends(const char *dirname, const char *start_name, struct list_head *list) { char *modules_dep_name; char *line; FILE *modules_dep; int done = 0; if (use_binary_indexes) if (read_depends_file(dirname, start_name, list)) return; nofail_asprintf(&modules_dep_name, "%s/%s", dirname, "modules.dep"); modules_dep = fopen(modules_dep_name, "r"); if (!modules_dep) fatal("Could not load %s: %s\n", modules_dep_name, strerror(errno)); /* Stop at first line, as we can have duplicates (eg. symlinks from boot/ */ while (!done && (line = getline_wrapped(modules_dep, NULL)) != NULL) { done = add_modules_dep_line(line, start_name, list, dirname); free(line); } fclose(modules_dep); free(modules_dep_name); } /** * insert_moderror - convert standard insert error numbers into error messages * * @err: error number * */ static const char *insert_moderror(int err) { switch (err) { case ENOEXEC: return "Invalid module format"; case ENOENT: return "Unknown symbol in module, or unknown parameter (see dmesg)"; case ENOSYS: return "Kernel does not have module support"; default: return strerror(err); } } /** * remove_moderror - convert standard remove error numbers into error messages * * @err: error number * */ static const char *remove_moderror(int err) { switch (err) { case ENOENT: return "No such module"; case ENOSYS: return "Kernel does not have module unloading support"; default: return strerror(err); } } /** * clear_magic - strip any versioning information from the module * * @module: mapped module file * */ static void clear_magic(struct elf_file *module) { struct string_table *tbl; int j; /* Old-style: __vermagic section */ module->ops->strip_section(module, "__vermagic"); /* New-style: in .modinfo section */ tbl = module->ops->load_strings(module, ".modinfo", NULL); for (j = 0; tbl && j < tbl->cnt; j++) { const char *p = tbl->str[j]; if (strstarts(p, "vermagic=")) { memset((char *)p, 0, strlen(p)); return; } } } /* keep track of module options from config file(s) */ struct module_options { struct module_options *next; char *modulename; char *options; }; /* keep track of module install commands from config file(s) */ struct module_command { struct module_command *next; char *modulename; char *command; }; /* keep track of module aliases added in the config file(s) */ struct module_alias { struct module_alias *next; char *aliasname; char *module; }; /* keep track of modules blacklisted in the config file(s) */ struct module_blacklist { struct module_blacklist *next; char *modulename; }; /* keep track of module softdeps added in the config file(s) */ struct module_softdep { struct module_softdep *next; char *buf; /* The modname and string tables point to buf. */ char *modname; struct string_table *pre; struct string_table *post; }; /* keep track of all config options */ struct modprobe_conf { struct module_options *options; struct module_command *commands; struct module_alias *aliases; struct module_blacklist *blacklist; struct module_softdep *softdeps; }; /** * add_options - module options added in the config file(s) * * @modname: module name * @option: options string * @options: list of options * */ static struct module_options * add_options(const char *modname, const char *option, struct module_options *options) { struct module_options *new; char *tab; new = NOFAIL(malloc(sizeof(*new))); new->modulename = NOFAIL(strdup(modname)); new->options = NOFAIL(strdup(option)); /* We can handle tabs, kernel can't. */ for (tab = strchr(new->options, '\t'); tab; tab = strchr(tab, '\t')) *tab = ' '; new->next = options; return new; } /** * add_command - module install commands added in the config file(s) * * @modname: module name * @command: command string * @commands: list of commands * */ static struct module_command * add_command(const char *modname, const char *command, struct module_command *commands) { struct module_command *new; new = NOFAIL(malloc(sizeof(*new))); new->modulename = NOFAIL(strdup(modname)); new->command = NOFAIL(strdup(command)); new->next = commands; return new; } /** * add_alias - module aliases added in the config file(s) * * @aliasname: alias string * @modname: module name * @aliases: list of aliases * */ static struct module_alias * add_alias(const char *aliasname, const char *modname, struct module_alias *aliases) { struct module_alias *new; new = NOFAIL(malloc(sizeof(*new))); new->aliasname = NOFAIL(strdup(aliasname)); new->module = NOFAIL(strdup(modname)); new->next = aliases; return new; } /** * find_aliases - find aliases for a module * * @aliases: list of aliases * @name: module name * */ static struct module_alias * find_aliases(const struct module_alias *aliases, const char *name) { struct module_alias *result = NULL; while (aliases) { char *aliasname = aliases->aliasname; char *modname = aliases->module; if (fnmatch(aliasname, name, 0) == 0) result = add_alias(aliasname, modname, result); aliases = aliases->next; } return result; } /** * free_aliases - de-allocate the aliases list * * @alias_list: list of aliases * */ static void free_aliases(struct module_alias *alias_list) { while (alias_list) { struct module_alias *alias; alias = alias_list; alias_list = alias_list->next; free(alias->aliasname); free(alias->module); free(alias); } } /** * add_blacklist - blacklist modules in config file(s) * * @modname: module name * @blacklist list of blacklisted module names * */ static struct module_blacklist * add_blacklist(const char *modname, struct module_blacklist *blacklist) { struct module_blacklist *new; new = NOFAIL(malloc(sizeof(*new))); new->modulename = NOFAIL(strdup(modname)); new->next = blacklist; return new; } /** * find_blacklist - lookup any potentially blacklisted module * * @modname: module name * @blacklist: list of blacklisted module names * */ static int find_blacklist(const char *modname, const struct module_blacklist *blacklist) { while (blacklist) { if (streq(blacklist->modulename, modname)) return 1; blacklist = blacklist->next; } return 0; } /** * apply_blacklist - remove blacklisted modules from alias list * * @aliases: module alias list * @blacklist: list of blacklisted module names * */ static void apply_blacklist(struct module_alias **aliases, const struct module_blacklist *blacklist) { struct module_alias *result = NULL; struct module_alias *alias = *aliases; while (alias) { char *modname = alias->module; if (!find_blacklist(modname, blacklist)) result = add_alias(alias->aliasname, modname, result); alias = alias->next; } free_aliases(*aliases); *aliases = result; } /** * find_command - lookup any install commands for a module * * @modname: module name * @commands: list of install commands * */ static const char *find_command(const char *modname, const struct module_command *commands) { while (commands) { if (fnmatch(commands->modulename, modname, 0) == 0) return commands->command; commands = commands->next; } return NULL; } /** * find_softdep - lookup any softdeps for a module * * @modname: module name * @softdeps: list of module softdeps * */ static const struct module_softdep * find_softdep(const char *modname, const struct module_softdep *softdeps) { while (softdeps) { if (fnmatch(softdeps->modname, modname, 0) == 0) return softdeps; softdeps = softdeps->next; } return NULL; } /** * append_option - add further options to modules (tail) * * @options: existing option string * @newoption: additional option string * * options supplied on the command line */ static char *append_option(char *options, const char *newoption) { options = NOFAIL(realloc(options, strlen(options) + 1 + strlen(newoption) + 1)); if (strlen(options)) strcat(options, " "); strcat(options, newoption); return options; } /** * prepend_option - add further options to modules (head) * * @options: existing option string * @newoption: additional option string * * options supplied in the config file(s) */ static char *prepend_option(char *options, const char *newoption) { size_t l1, l2; l1 = strlen(options); l2 = strlen(newoption); /* the resulting string will look like * newoption + ' ' + options + '\0' */ if (l1) { options = NOFAIL(realloc(options, l2 + 1 + l1 + 1)); memmove(options + l2 + 1, options, l1 + 1); options[l2] = ' '; memcpy(options, newoption, l2); } else { options = NOFAIL(realloc(options, l2 + 1)); memcpy(options, newoption, l2); options[l2] = '\0'; } return options; } /** * add_extra_options - add any relevant options from the config file(s) * * @modname: module name * @optstring: options * @options: list of options * */ static char *add_extra_options(const char *modname, const char *optstring, const struct module_options *options) { char *opts = NOFAIL(strdup(optstring)); while (options) { if (streq(options->modulename, modname)) opts = prepend_option(opts, options->options); options = options->next; } return opts; } /** * module_in_procfs - check if module is known to be loaded already * * @modname: module name * @usecount: update usecount if possible (-1 if unknown) * */ static int module_in_procfs(const char *modname, unsigned int *usecount) { FILE *proc_modules; char *line; again: /* Might not be mounted yet. Don't fail. */ proc_modules = fopen("/proc/modules", "r"); if (!proc_modules) return -1; while ((line = getline_wrapped(proc_modules, NULL)) != NULL) { char *entry = strtok(line, " \n"); if (entry && streq(entry, modname)) { /* If it exists, usecount is the third entry. */ if (!strtok(NULL, " \n")) goto out; if (!(entry = strtok(NULL, " \n"))) /* usecount */ goto out; else if (usecount) *usecount = atoi(entry); /* Followed by - then status. */ if (strtok(NULL, " \n") && (entry = strtok(NULL, " \n")) != NULL) { /* No locking, we might hit cases * where module is in flux. Spin. */ if (streq(entry, "Loading") || streq(entry, "Unloading")) { usleep(100000); free(line); fclose(proc_modules); goto again; } } out: free(line); fclose(proc_modules); return 1; } free(line); } fclose(proc_modules); return 0; } /** * read_attribute - read sysfs file attributes into a buffer * * @filename: name of sysfs file * @buf: buffer * @buflen: size of buffer * */ static int read_attribute(const char *filename, char *buf, size_t buflen) { FILE *file; char *s; file = fopen(filename, "r"); if (file == NULL) return (errno == ENOENT) ? 0 : -1; s = fgets(buf, buflen, file); fclose(file); /* return -1 if any problems */ return (s == NULL) ? -1 : 1; } /** * module_builtin - try to determine whether a module is built-in * * @dirname: module directory * @modname: name of module * */ static int module_builtin(const char *dirname, const char *modname) { struct index_file *index; char *filename, *value; nofail_asprintf(&filename, "%s/modules.builtin.bin", dirname); index = index_file_open(filename); free(filename); /* return -1 if no builtin list available (modern depmod file) */ if (!index) return -1; value = index_search(index, modname); free(value); return value ? 1 : 0; } /** * module_in_sysfs - try to determine if module has a sysfs entry * * @modname: module name * @usecount: update use count if possible (-1 if unknown) * */ static int module_in_sysfs(const char *modname, unsigned int *usecount) { int ret; char *name; struct stat finfo; const int ATTR_LEN = 16; char attr[ATTR_LEN]; /* Check sysfs is mounted */ if (stat("/sys/module", &finfo) < 0) return -1; /* Find module. */ nofail_asprintf(&name, "/sys/module/%s", modname); ret = stat(name, &finfo); free(name); if (ret < 0) return (errno == ENOENT) ? 0 : -1; /* Not found or unknown. */ nofail_asprintf(&name, "/sys/module/%s/initstate", modname); ret = read_attribute(name, attr, ATTR_LEN); if (ret == 0) { free(name); nofail_asprintf(&name, "/sys/module/%s", modname); if (stat(name, &finfo) < 0) { /* module was removed before we could read initstate */ ret = 0; } else { /* initstate not available (2.6.19 or earlier) */ ret = -1; } free(name); return ret; } /* Wait for the existing module to either go live or disappear. */ while (ret == 1 && !streq(attr, "live\n")) { usleep(100000); ret = read_attribute(name, attr, ATTR_LEN); } free(name); if (ret != 1) return ret; /* Get reference count, if it exists. */ if (usecount != NULL) { nofail_asprintf(&name, "/sys/module/%s/refcnt", modname); ret = read_attribute(name, attr, ATTR_LEN); free(name); if (ret == 1) *usecount = atoi(attr); } return 1; } /** * module_in_kernel - try to determine if the module is loaded (sysfs,procfs) * * @modname: name of module * @usecount: update module use count (-1 if unknown) * */ static int module_in_kernel(const char *modname, unsigned int *usecount) { int result; result = module_in_sysfs(modname, usecount); if (result != -1) return result; /* /sys/module/%s/initstate is only available since 2.6.20, fallback to /proc/modules to get module state on earlier kernels. */ return module_in_procfs(modname, usecount); } /** * dump_modversions - output list of module version checksums * * @filename: module file name * @error: error function to use * */ static void dump_modversions(const char *filename, errfn_t error) { struct elf_file *module; module = grab_elf_file(filename); if (!module) { error("%s: %s\n", filename, strerror(errno)); return; } if (module->ops->dump_modvers(module) < 0) error("Wrong section size in '%s'\n", filename); release_elf_file(module); } /** * type_matches - constrain wildcard module matching to subdirectory * * @path: path string * @subpath: search path for subpath * * Legacy function used to support deprecated option */ static int type_matches(const char *path, const char *subpath) { char *subpath_with_slashes; int ret; nofail_asprintf(&subpath_with_slashes, "/%s/", subpath); ret = (strstr(path, subpath_with_slashes) != NULL); free(subpath_with_slashes); return ret; } /** * do_wildcard - match modules (possibly in directory names containing "type") * * @dirname: module directory * @type: possible subdirectory limiting * @wildcard: what to match * */ static int do_wildcard(const char *dirname, const char *type, const char *wildcard) { char *modules_dep_name; char *line, *wcard; FILE *modules_dep; /* Canonicalize wildcard */ wcard = strdup(wildcard); underscores(wcard); nofail_asprintf(&modules_dep_name, "%s/%s", dirname, "modules.dep"); modules_dep = fopen(modules_dep_name, "r"); if (!modules_dep) fatal("Could not load %s: %s\n", modules_dep_name, strerror(errno)); while ((line = getline_wrapped(modules_dep, NULL)) != NULL) { char *ptr; /* Ignore lines without : or which start with a # */ ptr = strchr(line, ':'); if (ptr == NULL || line[strspn(line, "\t ")] == '#') goto next; *ptr = '\0'; /* "type" must match complete directory component(s). */ if (!type || type_matches(line, type)) { char modname[strlen(line)+1]; filename2modname(modname, line); if (fnmatch(wcard, modname, 0) == 0) printf("%s\n", line); } next: free(line); } free(modules_dep_name); free(wcard); return 0; } /** * strsep_skipspace - ignore certain delimitor characters in strings * * @string: what to search * @delim: delimitor string * */ static char *strsep_skipspace(char **string, char *delim) { if (!*string) return NULL; *string += strspn(*string, delim); return strsep(string, delim); } static int parse_config_scan(struct modprobe_conf *conf, int dump_only, int removing, ...); /** * parse_config_file - read in configuration file options * * @filename: name of file * @conf: config options lists * @dump_only: print out config * @removing: determine whether to run install/softdep/etc. * */ static int parse_config_file(const char *filename, struct modprobe_conf *conf, int dump_only, int removing) { char *line; unsigned int linenum = 0; FILE *cfile; struct module_options **options = &conf->options; struct module_command **commands = &conf->commands; struct module_alias **aliases = &conf->aliases; struct module_blacklist **blacklist = &conf->blacklist; cfile = fopen(filename, "r"); if (!cfile) return 0; while ((line = getline_wrapped(cfile, &linenum)) != NULL) { char *ptr = line; char *cmd, *modname; /* output configuration */ if (dump_only) printf("%s\n", line); cmd = strsep_skipspace(&ptr, "\t "); if (cmd == NULL || cmd[0] == '#' || cmd[0] == '\0') { free(line); continue; } if (streq(cmd, "alias")) { char *wildcard = strsep_skipspace(&ptr, "\t "); char *realname = strsep_skipspace(&ptr, "\t "); if (!wildcard || !realname) goto syntax_error; *aliases = add_alias(underscores(wildcard), underscores(realname), *aliases); } else if (streq(cmd, "include")) { struct modprobe_conf newconf = *conf; newconf.aliases = NULL; char *newfilename; newfilename = strsep_skipspace(&ptr, "\t "); if (!newfilename) goto syntax_error; warn("\"include %s\" is deprecated, " "please use /etc/modprobe.d\n", newfilename); if (strstarts(newfilename, "/etc/modprobe.d")) { warn("\"include /etc/modprobe.d\" is " "the default, ignored\n"); } else { if (!parse_config_scan(&newconf, dump_only, removing, newfilename, NULL)) warn("Failed to open included" " config file %s: %s\n", newfilename, strerror(errno)); } /* Files included override aliases, etc that was already set ... */ if (newconf.aliases) *aliases = newconf.aliases; } else if (streq(cmd, "options")) { modname = strsep_skipspace(&ptr, "\t "); if (!modname || !ptr) goto syntax_error; ptr += strspn(ptr, "\t "); *options = add_options(underscores(modname), ptr, *options); } else if (streq(cmd, "install")) { modname = strsep_skipspace(&ptr, "\t "); if (!modname || !ptr) goto syntax_error; if (!removing) { ptr += strspn(ptr, "\t "); *commands = add_command(underscores(modname), ptr, *commands); } } else if (streq(cmd, "blacklist")) { modname = strsep_skipspace(&ptr, "\t "); if (!modname) goto syntax_error; if (!removing) { *blacklist = add_blacklist(underscores(modname), *blacklist); } } else if (streq(cmd, "remove")) { modname = strsep_skipspace(&ptr, "\t "); if (!modname || !ptr) goto syntax_error; if (removing) { ptr += strspn(ptr, "\t "); *commands = add_command(underscores(modname), ptr, *commands); } } else if (streq(cmd, "softdep")) { char *tk; int pre = 0, post = 0; struct string_table *pre_modnames = NULL; struct string_table *post_modnames = NULL; struct module_softdep *new; modname = strsep_skipspace(&ptr, "\t "); if (!modname || !ptr) goto syntax_error; modname = underscores(modname); while ((tk = strsep_skipspace(&ptr, "\t ")) != NULL) { tk = underscores(tk); if (streq(tk, "pre:")) { pre = 1; post = 0; } else if (streq(tk, "post:")) { pre = 0; post = 1; } else if (pre) { pre_modnames = NOFAIL( strtbl_add(tk, pre_modnames)); } else if (post) { post_modnames = NOFAIL( strtbl_add(tk, post_modnames)); } else { strtbl_free(pre_modnames); strtbl_free(post_modnames); goto syntax_error; } } new = NOFAIL(malloc(sizeof(*new))); new->buf = line; new->modname = modname; new->pre = pre_modnames; new->post = post_modnames; new->next = conf->softdeps; conf->softdeps = new; line = NULL; /* Don't free() this line. */ /* allocated in buf above */ } else if (streq(cmd, "config")) { char *tmp = strsep_skipspace(&ptr, "\t "); if (!tmp) goto syntax_error; if (streq(tmp, "binary_indexes")) { tmp = strsep_skipspace(&ptr, "\t "); if (streq(tmp, "yes")) use_binary_indexes = 1; if (streq(tmp, "no")) use_binary_indexes = 0; } } else { syntax_error: grammar(cmd, filename, linenum); } free(line); } fclose(cfile); return 1; } /** * read_aliases_file - process binary module aliases file * * @filename: alias file * @name: module name * @dump_only: dump aliases only * @aliases: list of aliases * */ static int read_aliases_file(const char *filename, const char *name, int dump_only, struct module_alias **aliases) { struct index_value *realnames; struct index_value *realname; char *binfile; struct index_file *index; nofail_asprintf(&binfile, "%s.bin", filename); index = index_file_open(binfile); if (!index) { free(binfile); return 0; } if (dump_only) { index_dump(index, stdout, "alias "); free(binfile); index_file_close(index); return 1; } realnames = index_searchwild(index, name); for (realname = realnames; realname; realname = realname->next) *aliases = add_alias("*", realname->value, *aliases); index_values_free(realnames); free(binfile); index_file_close(index); return 1; } /** * read_aliases - process module aliases file * * @filename: alias file * @name: module name * @dump_only: dump aliases only * @aliases: list of aliases * */ static int read_aliases(const char *filename, const char *name, int dump_only, struct module_alias **aliases) { char *line; unsigned int linenum = 0; FILE *cfile; /* prefer the binary version if available and configured */ if (use_binary_indexes) if (read_aliases_file(filename, name, dump_only, aliases)) return 1; cfile = fopen(filename, "r"); if (!cfile) return 0; while ((line = getline_wrapped(cfile, &linenum)) != NULL) { char *ptr = line; char *cmd; if (dump_only) printf("%s\n", line); cmd = strsep_skipspace(&ptr, "\t "); if (cmd == NULL || cmd[0] == '#' || cmd[0] == '\0') { free(line); continue; } if (streq(cmd, "alias")) { char *wildcard = strsep_skipspace(&ptr, "\t "); char *realname = strsep_skipspace(&ptr, "\t "); if (!wildcard || !realname) goto syntax_error; if (fnmatch(underscores(wildcard),name,0) == 0) *aliases = add_alias(wildcard, underscores(realname), *aliases); } else { syntax_error: grammar(cmd, filename, linenum); } free(line); } fclose(cfile); return 1; } /** * parse_config_scan - process a directory of configuration files * * @conf: config options lists * @dump_only: print out config * @removing: determine whether to run install/softdep/etc. * */ static int parse_config_scan(struct modprobe_conf *conf, int dump_only, int removing, ...) { va_list filelist; char *filename; DIR *dir; struct file_entry { struct list_head node; char *name; char *path; }; struct file_entry *fe, *fe_tmp; LIST_HEAD(files_list); int ret = 0; va_start(filelist, removing); while ((filename = va_arg(filelist, char*))) { dir = opendir(filename); if (dir) { struct dirent *i; /* sort files from directories into list, ignoring duplicates */ while ((i = readdir(dir)) != NULL) { size_t len; int cmp = -1; if (i->d_name[0] == '.') continue; if (!config_filter(i->d_name)) continue; len = strlen(i->d_name); if (len < 6 || (strcmp(&i->d_name[len-5], ".conf") != 0 && strcmp(&i->d_name[len-6], ".alias") != 0)) warn("All config files need .conf: %s/%s, " "it will be ignored in a future release.\n", filename, i->d_name); fe = malloc(sizeof(struct file_entry)); if (fe == NULL) continue; list_for_each_entry(fe_tmp, &files_list, node) if ((cmp = strcmp(fe_tmp->name, i->d_name)) >= 0) break; if (cmp != 0) { fe->name = malloc(len + 1); fe->path = malloc(strlen(filename) + 1); strcpy(fe->name, i->d_name); strcpy(fe->path, filename); if (cmp < 0) list_add_tail(&fe->node, &files_list); else list_add_tail(&fe->node, &fe_tmp->node); } else info("Ignoring config file %s/%s\n", filename, i->d_name); } closedir(dir); ret = 1; } else { if (parse_config_file(filename, conf, dump_only, removing)) ret = 1; } } /* parse list of files */ list_for_each_entry_safe(fe, fe_tmp, &files_list, node) { char *cfgfile; nofail_asprintf(&cfgfile, "%s/%s", fe->path, fe->name); if (!parse_config_file(cfgfile, conf, dump_only, removing)) warn("Failed to open config file %s: %s\n", cfgfile, strerror(errno)); free(cfgfile); list_del(&fe->node); free(fe->name); free(fe->path); free(fe); } va_end(filelist); return ret; } /** * parse_toplevel_config - search configuration directories * * @filename: specified on command line * @conf: config options lists * @dump_only: print out config * @removing: determine whether to run install/softdep/etc. * */ static void parse_toplevel_config(const char *filename, struct modprobe_conf *conf, int dump_only, int removing) { if (filename) { if (!parse_config_scan(conf, dump_only, removing, filename, NULL)) fatal("Failed to open config file %s: %s\n", filename, strerror(errno)); return; } /* deprecated config file */ if (parse_config_file("/etc/modprobe.conf", conf, dump_only, removing) > 0) warn("Deprecated config file /etc/modprobe.conf, " "all config files belong into /etc/modprobe.d/.\n"); /* default config */ parse_config_scan(conf, dump_only, removing, "/run/modprobe.d", "/etc/modprobe.d", "/usr/local/lib/modprobe.d", "/lib/modprobe.d", NULL); } /** * parse_kcmdline - process configuration options on kernel boot line * * @dump_only: print out config * @conf: config options lists * */ static int parse_kcmdline(int dump_only, struct modprobe_conf *conf) { char *line; unsigned int linenum = 0; FILE *kcmdline; struct module_options **options = &conf->options; struct module_blacklist **blacklist = &conf->blacklist; kcmdline = fopen("/proc/cmdline", "r"); if (!kcmdline) return 0; while ((line = getline_wrapped(kcmdline, &linenum)) != NULL) { char *ptr = line; char *arg; while ((arg = strsep_skipspace(&ptr, "\t ")) != NULL) { char *sep, *modname, *opt; if (strstr(arg, "modprobe.blacklist=") != NULL) { ptr = strchr(arg,'=') + 1; while ((modname = strsep(&ptr, ",")) != NULL) { if (dump_only) printf("blacklist %s\n", modname); *blacklist = add_blacklist(underscores(modname), *blacklist); } } sep = strchr(arg, '.'); if (sep) { if (!strchr(sep, '=')) continue; modname = arg; *sep = '\0'; opt = ++sep; if (dump_only) printf("options %s %s\n", modname, opt); *options = add_options(underscores(modname), opt, *options); } } free(line); } fclose(kcmdline); return 1; } /** * add_to_env_var - set environment variables * * @option: variable to set * */ static void add_to_env_var(const char *option) { const char *oldenv; if ((oldenv = getenv("MODPROBE_OPTIONS")) != NULL) { char *newenv; nofail_asprintf(&newenv, "%s %s", oldenv, option); setenv("MODPROBE_OPTIONS", newenv, 1); free(newenv); } else setenv("MODPROBE_OPTIONS", option, 1); } /** * merge_args - load options from the environment (head) * * @args: list of arguments * @argv: program arguments * @argc: argument count * */ static char **merge_args(char *args, char *argv[], int *argc) { char *arg, *argstring; char **newargs = NULL; unsigned int i, num_env = 0; if (!args) return argv; argstring = NOFAIL(strdup(args)); for (arg = strtok(argstring, " "); arg; arg = strtok(NULL, " ")) { num_env++; newargs = NOFAIL(realloc(newargs, sizeof(newargs[0]) * (num_env + *argc + 1))); newargs[num_env] = arg; } if (!newargs) return argv; /* Append commandline args */ newargs[0] = argv[0]; for (i = 1; i <= *argc; i++) newargs[num_env+i] = argv[i]; *argc += num_env; return newargs; } /** * gather_options - load options from command line * * @argv: program arguments * */ static char *gather_options(char *argv[]) { char *optstring = NOFAIL(strdup("")); /* Rest is module options */ while (*argv) { /* Quote value if it contains spaces. */ unsigned int eq = strcspn(*argv, "="); if (strchr(*argv+eq, ' ') && !strchr(*argv, '"')) { char quoted[strlen(*argv) + 3]; (*argv)[eq] = '\0'; sprintf(quoted, "%s=\"%s\"", *argv, *argv+eq+1); optstring = append_option(optstring, quoted); } else optstring = append_option(optstring, *argv); argv++; } return optstring; } /** * do_command - execute a module install or remove command * * @modname: module name * @command: command string * @dry_run: possibly do nothing * @error: error function * @type: install or remove * @cmdline_opts: substitute for $CMDLINE_OPTS * */ static void do_command(const char *modname, const char *command, int dry_run, errfn_t error, const char *type, const char *cmdline_opts) { int ret; char *p, *replaced_cmd = NOFAIL(strdup(command)); while ((p = strstr(replaced_cmd, "$CMDLINE_OPTS")) != NULL) { char *new; nofail_asprintf(&new, "%.*s%s%s", (int)(p - replaced_cmd), replaced_cmd, cmdline_opts, p + strlen("$CMDLINE_OPTS")); free(replaced_cmd); replaced_cmd = new; } info("%s %s\n", type, replaced_cmd); if (dry_run) goto out; setenv("MODPROBE_MODULE", modname, 1); ret = system(replaced_cmd); if (ret == -1 || WEXITSTATUS(ret)) error("Error running %s command for %s\n", type, modname); out: free(replaced_cmd); } /* Forward declaration */ static int do_modprobe(const char *modname, const char *cmdline_opts, const struct modprobe_conf *conf, const char *dirname, errfn_t error, modprobe_flags_t flags); /** * do_softdep - load or unload module soft dependencies * * @softdep: soft dependency module(s) * @cmdline_opts: command line options * @conf: config options lists * @dirname: module directory * @error: error function * @flags: general flags * */ static void do_softdep(const struct module_softdep *softdep, const char *cmdline_opts, const struct modprobe_conf *conf, const char *dirname, errfn_t error, modprobe_flags_t flags) { struct string_table *pre_modnames, *post_modnames; modprobe_flags_t softdep_flags = flags; int i, j; softdep_flags &= ~mit_first_time; softdep_flags &= ~mit_ignore_commands; if (flags & mit_remove) softdep_flags |= mit_quiet_inuse; if (++recursion_depth >= MAX_RECURSION) fatal("modprobe: softdep dependency loop encountered %s %s\n", (flags & mit_remove) ? "removing" : "inserting", softdep->modname); if (flags & mit_remove) { /* Reverse module order if removing. */ pre_modnames = softdep->post; post_modnames = softdep->pre; } else { pre_modnames = softdep->pre; post_modnames = softdep->post; } /* Modprobe pre_modnames */ for (i = 0; pre_modnames && i < pre_modnames->cnt; i++) { /* Reverse module order if removing. */ j = (flags & mit_remove) ? pre_modnames->cnt-1 - i : i; do_modprobe(pre_modnames->str[j], "", conf, dirname, warn, softdep_flags); } /* Modprobe main module, passing cmdline_opts, ignoring softdep */ do_modprobe(softdep->modname, cmdline_opts, conf, dirname, warn, flags | mit_ignore_commands); /* Modprobe post_modnames */ for (i = 0; post_modnames && i < post_modnames->cnt; i++) { /* Reverse module order if removing. */ j = (flags & mit_remove) ? post_modnames->cnt-1 - i : i; do_modprobe(post_modnames->str[j], "", conf, dirname, warn, softdep_flags); } } /** * insmod - load a module(s) * * @list: list of modules * @optstring: module options * @cmdline_opts: command line options * @conf: config options lists * @dirname: module directory * @error: error function * @flags: general flags * */ static int insmod(struct list_head *list, const char *optstring, const char *cmdline_opts, const struct modprobe_conf *conf, const char *dirname, errfn_t error, modprobe_flags_t flags) { int ret; struct elf_file *module; const struct module_softdep *softdep; const char *command; struct module *mod = list_entry(list->next, struct module, list); int rc = 0; int already_loaded; char *opts = NULL; /* Take us off the list. */ list_del(&mod->list); /* Do things we (or parent) depend on first. */ if (!list_empty(list)) { modprobe_flags_t f = flags; f &= ~mit_first_time; f &= ~mit_ignore_commands; if ((rc = insmod(list, "", "", conf, dirname, warn, f)) != 0) { error("Error inserting %s (%s): %s\n", mod->modname, mod->filename, insert_moderror(errno)); goto out; } } /* Don't do ANYTHING if already in kernel. */ already_loaded = module_in_kernel(mod->modname, NULL); if (!(flags & mit_ignore_loaded) && already_loaded == 1) { if (flags & mit_first_time) error("Module %s already in kernel.\n", mod->modname); goto out; } /* load any soft dependency modules */ softdep = find_softdep(mod->modname, conf->softdeps); if (softdep && !(flags & mit_ignore_commands)) { do_softdep(softdep, cmdline_opts, conf, dirname, error, flags); goto out; } /* run any install commands for this module */ command = find_command(mod->modname, conf->commands); if (command && !(flags & mit_ignore_commands)) { if (already_loaded == -1) { warn("/sys/module/ not present or too old," " and /proc/modules does not exist.\n"); warn("Ignoring install commands for %s" " in case it is already loaded.\n", mod->modname); } else { do_command(mod->modname, command, flags & mit_dry_run, error, "install", cmdline_opts); goto out; } } /* open the module */ module = grab_elf_file(mod->filename); if (!module) { error("Could not read '%s': %s\n", mod->filename, (errno == ENOEXEC) ? "Invalid module format" : strerror(errno)); goto out; } if (flags & mit_strip_modversion) module->ops->strip_section(module, "__versions"); if (flags & mit_strip_vermagic) clear_magic(module); /* Config file might have given more options */ opts = add_extra_options(mod->modname, optstring, conf->options); info("insmod %s %s\n", mod->filename, opts); if (flags & mit_dry_run) goto out_elf_file; /* request kernel linkage */ ret = init_module(module->data, module->len, opts); if (ret != 0) { if (errno == EEXIST) { if (flags & mit_first_time) error("Module %s already in kernel.\n", mod->modname); goto out_elf_file; } /* don't warn noisely if we're loading multiple aliases. */ /* one of the aliases may try to use hardware we don't have. */ if ((error != warn) || (verbose)) error("Error inserting %s (%s): %s\n", mod->modname, mod->filename, insert_moderror(errno)); rc = 1; } out_elf_file: release_elf_file(module); free(opts); out: free_module(mod); return rc; } /** * rmmod - unload a module(s) * * @list: list of modules * @cmdline_opts: command line options * @conf: config options lists * @dirname: module directory * @error: error function * @flags: general flags * */ static void rmmod(struct list_head *list, const char *cmdline_opts, const struct modprobe_conf *conf, const char *dirname, errfn_t error, modprobe_flags_t flags) { const struct module_softdep *softdep; const char *command; unsigned int usecount = 0; struct module *mod = list_entry(list->next, struct module, list); int exists; /* Take first one off the list. */ list_del(&mod->list); /* Don't do ANYTHING if not loaded. */ exists = module_in_kernel(mod->modname, &usecount); if (exists == 0) goto nonexistent_module; /* Even if renamed, find commands/softdeps to orig. name. */ softdep = find_softdep(mod->modname, conf->softdeps); if (softdep && !(flags & mit_ignore_commands)) { do_softdep(softdep, cmdline_opts, conf, dirname, error, flags); goto remove_rest; } /* run any remove commands for this module */ command = find_command(mod->modname, conf->commands); if (command && !(flags & mit_ignore_commands)) { if (exists == -1) { warn("/sys/module/ not present or too old," " and /proc/modules does not exist.\n"); warn("Ignoring remove commands for %s" " in case it is not loaded.\n", mod->modname); } else { do_command(mod->modname, command, flags & mit_dry_run, error, "remove", cmdline_opts); goto remove_rest; } } /* abort if use count is not zero */ if (usecount != 0) { if (!(flags & mit_quiet_inuse)) error("Module %s is in use.\n", mod->modname); goto remove_rest; } info("rmmod %s\n", mod->filename); if (flags & mit_dry_run) goto remove_rest; /* request kernel unlinkage */ if (delete_module(mod->modname, O_EXCL) != 0) { if (errno == ENOENT) goto nonexistent_module; error("Error removing %s (%s): %s\n", mod->modname, mod->filename, remove_moderror(errno)); } remove_rest: /* Now do things we depend. */ if (!list_empty(list)) { flags &= ~mit_first_time; flags &= ~mit_ignore_commands; flags |= mit_quiet_inuse; rmmod(list, "", conf, dirname, warn, flags); } free_module(mod); return; nonexistent_module: if (flags & mit_first_time) fatal("Module %s is not in kernel.\n", mod->modname); goto remove_rest; } /** * handle_module - load or unload a module(s) * * @modname: module requested * @todo_list: dependency list * @options: module options * @cmdline_opts: options passed on command line * @conf: config options lists * @dirname: module directory * @error: error function * @flags: general flags * */ static int handle_module(const char *modname, struct list_head *todo_list, const char *options, const char *cmdline_opts, const struct modprobe_conf *conf, const char *dirname, errfn_t error, modprobe_flags_t flags) { if (list_empty(todo_list)) { const char *command; /* The dependencies have to be real modules, but handle case where the first is completely bogus. */ command = find_command(modname, conf->commands); if (command && !(flags & mit_ignore_commands)) { do_command(modname, command, flags & mit_dry_run, error, (flags & mit_remove) ? "remove":"install", cmdline_opts); return 0; } if (!quiet) error("Module %s not found.\n", modname); return 1; } if (flags & mit_remove) rmmod(todo_list, cmdline_opts, conf, dirname, error, flags); else insmod(todo_list, options, cmdline_opts, conf, dirname, error, flags); return 0; } /** * handle_builtin_module - report built-in modules * * @modname: module name * @error: error function * @flags: general flags * */ static int handle_builtin_module(const char *modname, errfn_t error, modprobe_flags_t flags) { if (flags & mit_remove) { error("Module %s is builtin\n", modname); return 1; } else if (flags & mit_first_time) { error("Module %s already in kernel (builtin).\n", modname); return 1; } else if (flags & mit_ignore_loaded) { /* --show-depends given */ info("builtin %s\n", modname); } return 0; } /** * do_modprobe - find a module by name or alias and load or unload * * @modname: module name * @cmdline_opts: command line options * @conf: config options lists * @dirname: module directory * @error: error function * @flags: general flags * */ static int do_modprobe(const char *modname, const char *cmdline_opts, const struct modprobe_conf *conf, const char *dirname, errfn_t error, modprobe_flags_t flags) { struct module_alias *matching_aliases; LIST_HEAD(list); int failed = 0; matching_aliases = find_aliases(conf->aliases, modname); /* No luck? Try symbol names, if starts with symbol:. */ if (!matching_aliases && strstarts(modname, "symbol:")) { char *symfilename; nofail_asprintf(&symfilename, "%s/modules.symbols", dirname); read_aliases(symfilename, modname, 0, &matching_aliases); free(symfilename); } if (!matching_aliases) { if(!strchr(modname, ':')) read_depends(dirname, modname, &list); /* We only use canned aliases as last resort. */ if (list_empty(&list) && !find_softdep(modname, conf->softdeps) && !find_command(modname, conf->commands)) { char *aliasfilename; nofail_asprintf(&aliasfilename, "%s/modules.alias", dirname); read_aliases(aliasfilename, modname, 0, &matching_aliases); free(aliasfilename); /* builtin module? */ if (!matching_aliases && module_builtin(dirname, modname) > 0) { failed |= handle_builtin_module(modname, error, flags); goto out; } } } /* only load blacklisted modules with specific request (no alias) */ apply_blacklist(&matching_aliases, conf->blacklist); if(flags & mit_resolve_alias) { struct module_alias *aliases = matching_aliases; for(; aliases; aliases=aliases->next) printf("%s\n", aliases->module); goto out; } if (matching_aliases) { errfn_t err = error; struct module_alias *aliases = matching_aliases; /* More than one alias? Don't bail out on failure. */ if (aliases->next) err = warn; while (aliases) { /* Add the options for this alias. */ char *opts; opts = add_extra_options(modname, cmdline_opts, conf->options); read_depends(dirname, aliases->module, &list); failed |= handle_module(aliases->module, &list, opts, cmdline_opts, conf, dirname, err, flags); aliases = aliases->next; free(opts); INIT_LIST_HEAD(&list); } } else { if (flags & mit_use_blacklist && find_blacklist(modname, conf->blacklist)) goto out; failed |= handle_module(modname, &list, cmdline_opts, cmdline_opts, conf, dirname, error, flags); } out: free_aliases(matching_aliases); return failed; } static const struct option options[] = { { "version", 0, NULL, 'V' }, { "verbose", 0, NULL, 'v' }, { "quiet", 0, NULL, 'q' }, { "syslog", 0, NULL, 's' }, { "show", 0, NULL, 'n' }, { "dry-run", 0, NULL, 'n' }, { "show-depends", 0, NULL, 'D' }, { "resolve-alias", 0, NULL, 'R' }, { "dirname", 1, NULL, 'd' }, { "set-version", 1, NULL, 'S' }, { "config", 1, NULL, 'C' }, { "remove", 0, NULL, 'r' }, { "showconfig", 0, NULL, 'c' }, { "list", 0, NULL, 'l' }, { "type", 1, NULL, 't' }, { "all", 0, NULL, 'a' }, { "ignore-install", 0, NULL, 'i' }, { "ignore-remove", 0, NULL, 'i' }, { "use-blacklist", 0, NULL, 'b' }, { "force", 0, NULL, 'f' }, { "force-vermagic", 0, NULL, 1 }, { "force-modversion", 0, NULL, 2 }, { "first-time", 0, NULL, 3 }, { "dump-modversions", 0, NULL, 4 }, { NULL, 0, NULL, 0 } }; int main(int argc, char *argv[]) { struct utsname buf; struct stat statbuf; int opt; int dump_config = 0; int list_only = 0; int all = 0; int dump_modver = 0; unsigned int i, num_modules; char *type = NULL; const char *configname = NULL; char *basedir = ""; char *cmdline_opts = NULL; char *dirname; errfn_t error = fatal; int failed = 0; modprobe_flags_t flags = 0; struct modprobe_conf conf = {}; recursion_depth = 0; /* Prepend options from environment. */ argv = merge_args(getenv("MODPROBE_OPTIONS"), argv, &argc); uname(&buf); while ((opt = getopt_long(argc, argv, "Vvqsnd:S:C:DRrclt:aibf", options, NULL)) != -1){ switch (opt) { case 'V': puts(PACKAGE " version " VERSION); exit(0); case 'v': add_to_env_var("-v"); verbose = 1; break; case 'q': quiet = 1; add_to_env_var("-q"); break; case 's': add_to_env_var("-s"); logging = 1; break; case 'n': flags |= mit_dry_run; break; case 'd': basedir = optarg; break; case 'S': strncpy(buf.release, optarg, sizeof(buf.release)); buf.release[sizeof(buf.release)-1] = '\0'; break; case 'C': configname = optarg; add_to_env_var("-C"); add_to_env_var(configname); break; case 'D': flags |= mit_dry_run; flags |= mit_ignore_loaded; verbose = 1; break; case 'R': flags |= mit_resolve_alias; break; case 'r': flags |= mit_remove; break; case 'c': dump_config = 1; break; case 'l': list_only = 1; break; case 't': type = optarg; break; case 'a': all = 1; error = warn; break; case 'i': flags |= mit_ignore_commands; break; case 'b': flags |= mit_use_blacklist; break; case 'f': flags |= mit_strip_vermagic; flags |= mit_strip_modversion; break; case 1: flags |= mit_strip_vermagic; break; case 2: flags |= mit_strip_modversion; break; case 3: flags |= mit_first_time; break; case 4: dump_modver = 1; break; default: print_usage(argv[0]); } } /* If stderr not open, go to syslog */ if (logging || fstat(STDERR_FILENO, &statbuf) != 0) { openlog("modprobe", LOG_CONS, LOG_DAEMON); logging = 1; } if (argc < optind + 1 && !dump_config && !list_only) print_usage(argv[0]); nofail_asprintf(&dirname, "%s%s/%s", basedir, MODULE_DIR, buf.release); /* Old-style -t xxx wildcard? Only with -l. */ if (list_only) { if (optind+1 < argc) fatal("Can't have multiple wildcards\n"); /* fprintf(stderr, "man find\n"); return 1; */ failed = do_wildcard(dirname, type, argv[optind]?:"*"); goto out; } if (type) fatal("-t only supported with -l"); if (dump_modver) { dump_modversions(argv[optind], error); goto out; } /* Read aliases, options etc. */ parse_toplevel_config(configname, &conf, dump_config, flags & mit_remove); /* Read module options from kernel command line */ parse_kcmdline(dump_config, &conf); /* report config only? */ if (dump_config) { char *aliasfilename, *symfilename; struct modprobe_conf conf = {}; nofail_asprintf(&aliasfilename, "%s/modules.alias", dirname); nofail_asprintf(&symfilename, "%s/modules.symbols", dirname); read_aliases(aliasfilename, "", 1, &conf.aliases); read_aliases(symfilename, "", 1, &conf.aliases); goto out; } if ((flags & mit_remove) || all) { num_modules = argc - optind; cmdline_opts = NOFAIL(strdup("")); } else { num_modules = 1; cmdline_opts = gather_options(argv+optind+1); } /* Convert names we are looking for */ for (i = 0; i < num_modules; i++) underscores(argv[optind + i]); /* If we have a list of modules to remove, try the unused ones first. Aliases and modules which don't seem to exist are handled later. */ if (flags & mit_remove) { int progress; do { progress = 0; for (i = 0; i < num_modules; i++) { const char *modname; unsigned usecount; LIST_HEAD(list); modname = argv[optind + i]; if (!modname) continue; if (module_in_kernel(modname, &usecount) != 1) continue; if (usecount != 0) continue; read_depends(dirname, modname, &list); failed |= handle_module(modname, &list, cmdline_opts, cmdline_opts, &conf, dirname, error, flags); progress++; argv[optind + i] = NULL; INIT_LIST_HEAD(&list); } } while (progress > 0); } /* num_modules is always 1 except for -r or -a. */ for (i = 0; i < num_modules; i++) { const char *modname = argv[optind + i]; if (!modname) continue; failed |= do_modprobe(modname, cmdline_opts, &conf, dirname, error, flags); } out: if (logging) closelog(); free(dirname); free(cmdline_opts); /* Don't bother to free conf */ exit(failed); }