X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=modutils%2Fmodprobe.c;h=a5cf4babf2aa31204d36099298e1bdf2b1e99f09;hb=c15613c9757f5a26da3623c1bcfa3580c81b74c3;hp=59c06ee1ed7bfffd4ca782a8297525b70b08ce4f;hpb=e3c150bc67cb158ee854ffb498f0066c79cd842c;p=oweals%2Fbusybox.git diff --git a/modutils/modprobe.c b/modutils/modprobe.c index 59c06ee1e..a5cf4babf 100644 --- a/modutils/modprobe.c +++ b/modutils/modprobe.c @@ -2,899 +2,606 @@ /* * Modprobe written from scratch for BusyBox * - * Copyright (c) 2002 by Robert Griebl, griebl@gmx.de - * Copyright (c) 2003 by Andrew Dennison, andrew.dennison@motec.com.au - * Copyright (c) 2005 by Jim Bauer, jfbauer@nfr.com + * Copyright (c) 2008 Timo Teras + * Copyright (c) 2008 Vladimir Dronnikov * - * Portions Copyright (c) 2005 by Yann E. MORIN, yann.morin.1998@anciens.enib.fr - * - * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. -*/ + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +//applet:IF_MODPROBE(APPLET(modprobe, _BB_DIR_SBIN, _BB_SUID_DROP)) + +//usage:#if !ENABLE_MODPROBE_SMALL +//usage:#define modprobe_notes_usage +//usage: "modprobe can (un)load a stack of modules, passing each module options (when\n" +//usage: "loading). modprobe uses a configuration file to determine what option(s) to\n" +//usage: "pass each module it loads.\n" +//usage: "\n" +//usage: "The configuration file is searched (in this order):\n" +//usage: "\n" +//usage: " /etc/modprobe.conf (2.6 only)\n" +//usage: " /etc/modules.conf\n" +//usage: " /etc/conf.modules (deprecated)\n" +//usage: "\n" +//usage: "They all have the same syntax (see below). If none is present, it is\n" +//usage: "_not_ an error; each loaded module is then expected to load without\n" +//usage: "options. Once a file is found, the others are tested for.\n" +//usage: "\n" +//usage: "/etc/modules.conf entry format:\n" +//usage: "\n" +//usage: " alias \n" +//usage: " Makes it possible to modprobe alias_name, when there is no such module.\n" +//usage: " It makes sense if your mod_name is long, or you want a more representative\n" +//usage: " name for that module (eg. 'scsi' in place of 'aha7xxx').\n" +//usage: " This makes it also possible to use a different set of options (below) for\n" +//usage: " the module and the alias.\n" +//usage: " A module can be aliased more than once.\n" +//usage: "\n" +//usage: " options \n" +//usage: " When loading module mod_name (or the module aliased by alias_name), pass\n" +//usage: " the \"symbol=value\" pairs as option to that module.\n" +//usage: "\n" +//usage: "Sample /etc/modules.conf file:\n" +//usage: "\n" +//usage: " options tulip irq=3\n" +//usage: " alias tulip tulip2\n" +//usage: " options tulip2 irq=4 io=0x308\n" +//usage: "\n" +//usage: "Other functionality offered by 'classic' modprobe is not available in\n" +//usage: "this implementation.\n" +//usage: "\n" +//usage: "If module options are present both in the config file, and on the command line,\n" +//usage: "then the options from the command line will be passed to the module _after_\n" +//usage: "the options from the config file. That way, you can have defaults in the config\n" +//usage: "file, and override them for a specific usage from the command line.\n" +//usage:#define modprobe_example_usage +//usage: "(with the above /etc/modules.conf):\n\n" +//usage: "$ modprobe tulip\n" +//usage: " will load the module 'tulip' with default option 'irq=3'\n\n" +//usage: "$ modprobe tulip irq=5\n" +//usage: " will load the module 'tulip' with option 'irq=5', thus overriding the default\n\n" +//usage: "$ modprobe tulip2\n" +//usage: " will load the module 'tulip' with default options 'irq=4 io=0x308',\n" +//usage: " which are the default for alias 'tulip2'\n\n" +//usage: "$ modprobe tulip2 irq=8\n" +//usage: " will load the module 'tulip' with default options 'irq=4 io=0x308 irq=8',\n" +//usage: " which are the default for alias 'tulip2' overridden by the option 'irq=8'\n\n" +//usage: " from the command line\n\n" +//usage: "$ modprobe tulip2 irq=2 io=0x210\n" +//usage: " will load the module 'tulip' with default options 'irq=4 io=0x308 irq=4 io=0x210',\n" +//usage: " which are the default for alias 'tulip2' overridden by the options 'irq=2 io=0x210'\n\n" +//usage: " from the command line\n" +//usage: +//usage:#define modprobe_trivial_usage +//usage: "[-alrqvs" +//usage: IF_FEATURE_MODPROBE_BLACKLIST("b") +//usage: "] MODULE [symbol=value]..." +//usage:#define modprobe_full_usage "\n\n" +//usage: "Options:" +//usage: "\n -a Load multiple MODULEs" +//usage: "\n -l List (MODULE is a pattern)" +//usage: "\n -r Remove MODULE (stacks) or do autoclean" +//usage: "\n -q Quiet" +//usage: "\n -v Verbose" +//usage: "\n -s Log to syslog" +//usage: IF_FEATURE_MODPROBE_BLACKLIST( +//usage: "\n -b Apply blacklist to module names too" +//usage: ) +//usage:#endif /* !ENABLE_MODPROBE_SMALL */ + +#include "libbb.h" +#include "modutils.h" #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "busybox.h" - -struct mod_opt_t { /* one-way list of options to pass to a module */ - char * m_opt_val; - struct mod_opt_t * m_next; -}; +#include -struct dep_t { /* one-way list of dependency rules */ - /* a dependency rule */ - char * m_name; /* the module name*/ - char * m_path; /* the module file path */ - struct mod_opt_t * m_options; /* the module options */ +//#define DBG(fmt, ...) bb_error_msg("%s: " fmt, __func__, ## __VA_ARGS__) +#define DBG(...) ((void)0) - int m_isalias : 1; /* the module is an alias */ - int m_reserved : 15; /* stuffin' */ - - int m_depcnt : 16; /* the number of dependable module(s) */ - char ** m_deparr; /* the list of dependable module(s) */ +/* Note that unlike older versions of modules.dep/depmod (busybox and m-i-t), + * we expect the full dependency list to be specified in modules.dep. + * Older versions would only export the direct dependency list. + */ - struct dep_t * m_next; /* the next dependency rule */ +/* Note that usage text doesn't document various 2.4 options + * we pull in through INSMOD_OPTS define */ + +#define MODPROBE_COMPLEMENTARY "q-v:v-q:l--ar:a--lr:r--al" +#define MODPROBE_OPTS "alr" IF_FEATURE_MODPROBE_BLACKLIST("b") +//#define MODPROBE_COMPLEMENTARY "q-v:v-q:l--acr:a--lr:r--al" +//#define MODPROBE_OPTS "acd:lnrt:C:" IF_FEATURE_MODPROBE_BLACKLIST("b") +enum { + MODPROBE_OPT_INSERT_ALL = (INSMOD_OPT_UNUSED << 0), /* a */ + //MODPROBE_OPT_DUMP_ONLY= (INSMOD_OPT_UNUSED << x), /* c */ + //MODPROBE_OPT_DIRNAME = (INSMOD_OPT_UNUSED << x), /* d */ + MODPROBE_OPT_LIST_ONLY = (INSMOD_OPT_UNUSED << 1), /* l */ + //MODPROBE_OPT_SHOW_ONLY= (INSMOD_OPT_UNUSED << x), /* n */ + MODPROBE_OPT_REMOVE = (INSMOD_OPT_UNUSED << 2), /* r */ + //MODPROBE_OPT_RESTRICT = (INSMOD_OPT_UNUSED << x), /* t */ + //MODPROBE_OPT_VERONLY = (INSMOD_OPT_UNUSED << x), /* V */ + //MODPROBE_OPT_CONFIGFILE=(INSMOD_OPT_UNUSED << x), /* C */ + MODPROBE_OPT_BLACKLIST = (INSMOD_OPT_UNUSED << 3) * ENABLE_FEATURE_MODPROBE_BLACKLIST, }; -struct mod_list_t { /* two-way list of modules to process */ - /* a module description */ - char * m_name; - char * m_path; - struct mod_opt_t * m_options; - - struct mod_list_t * m_prev; - struct mod_list_t * m_next; +#define MODULE_FLAG_LOADED 0x0001 +#define MODULE_FLAG_NEED_DEPS 0x0002 +/* "was seen in modules.dep": */ +#define MODULE_FLAG_FOUND_IN_MODDEP 0x0004 +#define MODULE_FLAG_BLACKLISTED 0x0008 + +struct module_entry { /* I'll call it ME. */ + unsigned flags; + char *modname; /* stripped of /path/, .ext and s/-/_/g */ + const char *probed_name; /* verbatim as seen on cmdline */ + char *options; /* options from config files */ + llist_t *realnames; /* strings. if this module is an alias, */ + /* real module name is one of these. */ +//Can there really be more than one? Example from real kernel? + llist_t *deps; /* strings. modules we depend on */ }; +struct globals { + llist_t *db; /* MEs of all modules ever seen (caching for speed) */ + llist_t *probes; /* MEs of module(s) requested on cmdline */ + char *cmdline_mopts; /* module options from cmdline */ + int num_unresolved_deps; + /* bool. "Did we have 'symbol:FOO' requested on cmdline?" */ + smallint need_symbols; +} FIX_ALIASING; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define INIT_G() do { } while (0) -static struct dep_t *depend; - -#define main_options "acdklnqrst:vVC:" -#define INSERT_ALL 1 /* a */ -#define DUMP_CONF_EXIT 2 /* c */ -#define D_OPT_IGNORED 4 /* d */ -#define AUTOCLEAN_FLG 8 /* k */ -#define LIST_ALL 16 /* l */ -#define SHOW_ONLY 32 /* n */ -#define QUIET 64 /* q */ -#define REMOVE_OPT 128 /* r */ -#define DO_SYSLOG 256 /* s */ -#define RESTRICT_DIR 512 /* t */ -#define VERBOSE 1024 /* v */ -#define VERSION_ONLY 2048 /* V */ -#define CONFIG_FILE 4096 /* C */ - -#define autoclean (main_opts & AUTOCLEAN_FLG) -#define show_only (main_opts & SHOW_ONLY) -#define quiet (main_opts & QUIET) -#define remove_opt (main_opts & REMOVE_OPT) -#define do_syslog (main_opts & DO_SYSLOG) -#define verbose (main_opts & VERBOSE) - -static int main_opts; - -static int parse_tag_value ( char *buffer, char **ptag, char **pvalue ) -{ - char *tag, *value; - - while ( isspace ( *buffer )) - buffer++; - tag = value = buffer; - while ( !isspace ( *value )) - if (!*value) return 0; - else value++; - *value++ = 0; - while ( isspace ( *value )) - value++; - if (!*value) return 0; - - *ptag = tag; - *pvalue = value; - - return 1; -} - -/* Jump through hoops to simulate how fgets() grabs just one line at a - * time... Don't use any stdio since modprobe gets called from a kernel - * thread and stdio junk can overflow the limited stack... - */ -static char *reads ( int fd, char *buffer, size_t len ) -{ - int n = read ( fd, buffer, len ); - - if ( n > 0 ) { - char *p; - - buffer [len-1] = 0; - p = strchr ( buffer, '\n' ); - - if ( p ) { - off_t offset; - offset = lseek ( fd, 0L, SEEK_CUR ); // Get the current file descriptor offset - lseek ( fd, offset-n + (p-buffer) + 1, SEEK_SET ); // Set the file descriptor offset to right after the \n +static int read_config(const char *path); - p[1] = 0; +static char *gather_options_str(char *opts, const char *append) +{ + /* Speed-optimized. We call gather_options_str many times. */ + if (append) { + if (opts == NULL) { + opts = xstrdup(append); + } else { + int optlen = strlen(opts); + opts = xrealloc(opts, optlen + strlen(append) + 2); + sprintf(opts + optlen, " %s", append); } - return buffer; } - - else - return 0; + return opts; } -/* - * This function appends an option to a list - */ -static struct mod_opt_t *append_option( struct mod_opt_t *opt_list, char *opt ) +static struct module_entry *helper_get_module(const char *module, int create) { - struct mod_opt_t *ol = opt_list; - - if( ol ) { - while( ol-> m_next ) { - ol = ol-> m_next; - } - ol-> m_next = xmalloc( sizeof( struct mod_opt_t ) ); - ol = ol-> m_next; - } else { - ol = opt_list = xmalloc( sizeof( struct mod_opt_t ) ); + char modname[MODULE_NAME_LEN]; + struct module_entry *e; + llist_t *l; + + filename2modname(module, modname); + for (l = G.db; l != NULL; l = l->link) { + e = (struct module_entry *) l->data; + if (strcmp(e->modname, modname) == 0) + return e; } + if (!create) + return NULL; - ol-> m_opt_val = bb_xstrdup( opt ); - ol-> m_next = NULL; + e = xzalloc(sizeof(*e)); + e->modname = xstrdup(modname); + llist_add_to(&G.db, e); - return opt_list; + return e; } - -#if ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS -/* static char* parse_command_string( char* src, char **dst ); - * src: pointer to string containing argument - * dst: pointer to where to store the parsed argument - * return value: the pointer to the first char after the parsed argument, - * NULL if there was no argument parsed (only trailing spaces). - * Note that memory is allocated with bb_xstrdup when a new argument was - * parsed. Don't forget to free it! - */ -#define ARG_EMPTY 0x00 -#define ARG_IN_DQUOTES 0x01 -#define ARG_IN_SQUOTES 0x02 -static char *parse_command_string( char *src, char **dst ) +static struct module_entry *get_or_add_modentry(const char *module) +{ + return helper_get_module(module, 1); +} +static struct module_entry *get_modentry(const char *module) { - int opt_status = ARG_EMPTY; - char* tmp_str; + return helper_get_module(module, 0); +} - /* Dumb you, I have nothing to do... */ - if( src == NULL ) return src; +static void add_probe(const char *name) +{ + struct module_entry *m; - /* Skip leading spaces */ - while( *src == ' ' ) { - src++; - } - /* Is the end of string reached? */ - if( *src == '\0' ) { - return NULL; - } - /* Reached the start of an argument - * By the way, we duplicate a little too much - * here but what is too much is freed later. */ - *dst = tmp_str = bb_xstrdup( src ); - /* Get to the end of that argument */ - while( ( *tmp_str != '\0' ) - && ( ( *tmp_str != ' ' ) - || ( opt_status & ( ARG_IN_DQUOTES | ARG_IN_SQUOTES ) ) ) ) { - switch( *tmp_str ) { - case '\'': - if( opt_status & ARG_IN_DQUOTES ) { - /* Already in double quotes, keep current char as is */ - } else { - /* shift left 1 char, until end of string: get rid of the opening/closing quotes */ - memmove( tmp_str, tmp_str + 1, strlen( tmp_str ) ); - /* mark me: we enter or leave single quotes */ - opt_status ^= ARG_IN_SQUOTES; - /* Back one char, as we need to re-scan the new char there. */ - tmp_str--; - } - break; - case '"': - if( opt_status & ARG_IN_SQUOTES ) { - /* Already in single quotes, keep current char as is */ - } else { - /* shift left 1 char, until end of string: get rid of the opening/closing quotes */ - memmove( tmp_str, tmp_str + 1, strlen( tmp_str ) ); - /* mark me: we enter or leave double quotes */ - opt_status ^= ARG_IN_DQUOTES; - /* Back one char, as we need to re-scan the new char there. */ - tmp_str--; - } - break; - case '\\': - if( opt_status & ARG_IN_SQUOTES ) { - /* Between single quotes: keep as is. */ - } else { - switch( *(tmp_str+1) ) { - case 'a': - case 'b': - case 't': - case 'n': - case 'v': - case 'f': - case 'r': - case '0': - /* We escaped a special character. For now, keep - * both the back-slash and the following char. */ - tmp_str++; src++; - break; - default: - /* We escaped a space or a single or double quote, - * or a back-slash, or a non-escapable char. Remove - * the '\' and keep the new current char as is. */ - memmove( tmp_str, tmp_str + 1, strlen( tmp_str ) ); - break; - } - } - break; - /* Any other char that is special shall appear here. - * Example: $ starts a variable - case '$': - do_variable_expansion(); - break; - * */ - default: - /* any other char is kept as is. */ - break; - } - tmp_str++; /* Go to next char */ - src++; /* Go to next char to find the end of the argument. */ + m = get_or_add_modentry(name); + if (!(option_mask32 & MODPROBE_OPT_REMOVE) + && (m->flags & MODULE_FLAG_LOADED) + ) { + DBG("skipping %s, it is already loaded", name); + return; } - /* End of string, but still no ending quote */ - if( opt_status & ( ARG_IN_DQUOTES | ARG_IN_SQUOTES ) ) { - bb_error_msg_and_die( "unterminated (single or double) quote in options list: %s", src ); + + DBG("queuing %s", name); + m->probed_name = name; + m->flags |= MODULE_FLAG_NEED_DEPS; + llist_add_to_end(&G.probes, m); + G.num_unresolved_deps++; + if (ENABLE_FEATURE_MODUTILS_SYMBOLS + && strncmp(m->modname, "symbol:", 7) == 0 + ) { + G.need_symbols = 1; } - *tmp_str++ = '\0'; - *dst = xrealloc( *dst, (tmp_str - *dst ) ); - return src; } -#else -#define parse_command_string(src, dst) (0) -#endif /* ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS */ -/* - * This function reads aliases and default module options from a configuration file - * (/etc/modprobe.conf syntax). It supports includes (only files, no directories). - */ -static void include_conf ( struct dep_t **first, struct dep_t **current, char *buffer, int buflen, int fd ) +static int FAST_FUNC config_file_action(const char *filename, + struct stat *statbuf UNUSED_PARAM, + void *userdata UNUSED_PARAM, + int depth UNUSED_PARAM) { - int continuation_line = 0; - - // alias parsing is not 100% correct (no correct handling of continuation lines within an alias) ! - - while ( reads ( fd, buffer, buflen)) { - int l; - char *p; - - p = strchr ( buffer, '#' ); - if ( p ) - *p = 0; - - l = strlen ( buffer ); - - while ( l && isspace ( buffer [l-1] )) { - buffer [l-1] = 0; - l--; - } - - if ( l == 0 ) { - continuation_line = 0; - continue; - } - - if ( !continuation_line ) { - if (( strncmp ( buffer, "alias", 5 ) == 0 ) && isspace ( buffer [5] )) { - char *alias, *mod; - - if ( parse_tag_value ( buffer + 6, &alias, &mod )) { - /* handle alias as a module dependent on the aliased module */ - if ( !*current ) { - (*first) = (*current) = (struct dep_t *) xcalloc ( 1, sizeof ( struct dep_t )); - } - else { - (*current)-> m_next = (struct dep_t *) xcalloc ( 1, sizeof ( struct dep_t )); - (*current) = (*current)-> m_next; - } - (*current)-> m_name = bb_xstrdup ( alias ); - (*current)-> m_isalias = 1; - - if (( strcmp ( mod, "off" ) == 0 ) || ( strcmp ( mod, "null" ) == 0 )) { - (*current)-> m_depcnt = 0; - (*current)-> m_deparr = 0; - } - else { - (*current)-> m_depcnt = 1; - (*current)-> m_deparr = xmalloc ( 1 * sizeof( char * )); - (*current)-> m_deparr[0] = bb_xstrdup ( mod ); - } - (*current)-> m_next = 0; - } - } - else if (( strncmp ( buffer, "options", 7 ) == 0 ) && isspace ( buffer [7] )) { - char *mod, *opt; + char *tokens[3]; + parser_t *p; + struct module_entry *m; + int rc = TRUE; + + if (bb_basename(filename)[0] == '.') + goto error; + + p = config_open2(filename, fopen_for_read); + if (p == NULL) { + rc = FALSE; + goto error; + } - /* split the line in the module/alias name, and options */ - if ( parse_tag_value ( buffer + 8, &mod, &opt )) { - struct dep_t *dt; + while (config_read(p, tokens, 3, 2, "# \t", PARSE_NORMAL)) { +//Use index_in_strings? + if (strcmp(tokens[0], "alias") == 0) { + /* alias */ + llist_t *l; + char wildcard[MODULE_NAME_LEN]; + char *rmod; + + if (tokens[2] == NULL) + continue; + filename2modname(tokens[1], wildcard); + + for (l = G.probes; l != NULL; l = l->link) { + m = (struct module_entry *) l->data; + if (fnmatch(wildcard, m->modname, 0) != 0) + continue; + rmod = filename2modname(tokens[2], NULL); + llist_add_to(&m->realnames, rmod); - /* find the corresponding module */ - for ( dt = *first; dt; dt = dt-> m_next ) { - if ( strcmp ( dt-> m_name, mod ) == 0 ) - break; - } - if ( dt ) { - if ( ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS ) { - char* new_opt = NULL; - while( ( opt = parse_command_string( opt, &new_opt ) ) ) { - dt-> m_options = append_option( dt-> m_options, new_opt ); - } - } else { - dt-> m_options = append_option( dt-> m_options, opt ); - } - } + if (m->flags & MODULE_FLAG_NEED_DEPS) { + m->flags &= ~MODULE_FLAG_NEED_DEPS; + G.num_unresolved_deps--; } - } - else if (( strncmp ( buffer, "include", 7 ) == 0 ) && isspace ( buffer [7] )) { - - int fdi; char *filename = buffer + 8; - - while ( isspace ( *filename )) - filename++; - if (( fdi = open ( filename, O_RDONLY )) >= 0 ) { - include_conf(first, current, buffer, buflen, fdi); - close(fdi); + m = get_or_add_modentry(rmod); + if (!(m->flags & MODULE_FLAG_NEED_DEPS)) { + m->flags |= MODULE_FLAG_NEED_DEPS; + G.num_unresolved_deps++; } } + } else if (strcmp(tokens[0], "options") == 0) { + /* options */ + if (tokens[2] == NULL) + continue; + m = get_or_add_modentry(tokens[1]); + m->options = gather_options_str(m->options, tokens[2]); + } else if (strcmp(tokens[0], "include") == 0) { + /* include */ + read_config(tokens[1]); + } else if (ENABLE_FEATURE_MODPROBE_BLACKLIST + && strcmp(tokens[0], "blacklist") == 0 + ) { + /* blacklist */ + get_or_add_modentry(tokens[1])->flags |= MODULE_FLAG_BLACKLISTED; } } + config_close(p); + error: + return rc; } -/* - * This function builds a list of dependency rules from /lib/modules/`uname -r`\modules.dep. - * It then fills every modules and aliases with their default options, found by parsing - * modprobe.conf (or modules.conf, or conf.modules). - */ -static struct dep_t *build_dep ( void ) +static int read_config(const char *path) { - int fd; - struct utsname un; - struct dep_t *first = 0; - struct dep_t *current = 0; - char buffer[2048]; - char *filename; - int continuation_line = 0; - int k_version; - - k_version = 0; - if ( uname ( &un )) - bb_error_msg_and_die("can't determine kernel version"); - - if (un.release[0] == '2') { - k_version = un.release[2] - '0'; - } - - filename = bb_xasprintf("/lib/modules/%s/modules.dep", un.release ); - fd = open ( filename, O_RDONLY ); - if (ENABLE_FEATURE_CLEAN_UP) - free(filename); - if (fd < 0) { - /* Ok, that didn't work. Fall back to looking in /lib/modules */ - if (( fd = open ( "/lib/modules/modules.dep", O_RDONLY )) < 0 ) { - return 0; - } - } - - while ( reads ( fd, buffer, sizeof( buffer ))) { - int l = strlen ( buffer ); - char *p = 0; + return recursive_action(path, ACTION_RECURSE | ACTION_QUIET, + config_file_action, NULL, NULL, 1); +} - while ( l > 0 && isspace ( buffer [l-1] )) { - buffer [l-1] = 0; - l--; - } +static const char *humanly_readable_name(struct module_entry *m) +{ + /* probed_name may be NULL. modname always exists. */ + return m->probed_name ? m->probed_name : m->modname; +} - if ( l == 0 ) { - continuation_line = 0; +static char *parse_and_add_kcmdline_module_options(char *options, const char *modulename) +{ + char *kcmdline_buf; + char *kcmdline; + char *kptr; + int len; + + kcmdline_buf = xmalloc_open_read_close("/proc/cmdline", NULL); + if (!kcmdline_buf) + return options; + + kcmdline = kcmdline_buf; + len = strlen(modulename); + while ((kptr = strsep(&kcmdline, "\n\t ")) != NULL) { + if (strncmp(modulename, kptr, len) != 0) + continue; + kptr += len; + if (*kptr != '.') continue; + /* It is "modulename.xxxx" */ + kptr++; + if (strchr(kptr, '=') != NULL) { + /* It is "modulename.opt=[val]" */ + options = gather_options_str(options, kptr); } + } + free(kcmdline_buf); - /* Is this a new module dep description? */ - if ( !continuation_line ) { - /* find the dep beginning */ - char *col = strchr ( buffer, ':' ); - char *dot = col; - - if ( col ) { - /* This line is a dep description */ - char *mods; - char *modpath; - char *mod; - - /* Find the beginning of the module file name */ - *col = 0; - mods = strrchr ( buffer, '/' ); - - if ( !mods ) - mods = buffer; /* no path for this module */ - else - mods++; /* there was a path for this module... */ - - /* find the path of the module */ - modpath = strchr ( buffer, '/' ); /* ... and this is the path */ - if ( !modpath ) - modpath = buffer; /* module with no path */ - /* find the end of the module name in the file name */ - if ( ENABLE_FEATURE_2_6_MODULES && - (k_version > 4) && ( *(col-3) == '.' ) && - ( *(col-2) == 'k' ) && ( *(col-1) == 'o' ) ) - dot = col - 3; - else - if (( *(col-2) == '.' ) && ( *(col-1) == 'o' )) - dot = col - 2; - - mod = bb_xstrndup ( mods, dot - mods ); - - /* enqueue new module */ - if ( !current ) { - first = current = (struct dep_t *) xmalloc ( sizeof ( struct dep_t )); - } - else { - current-> m_next = (struct dep_t *) xmalloc ( sizeof ( struct dep_t )); - current = current-> m_next; + return options; +} + +/* Return: similar to bb_init_module: + * 0 on success, + * -errno on open/read error, + * errno on init_module() error + */ +/* NB: INSMOD_OPT_SILENT bit suppresses ONLY non-existent modules, + * not deleted ones (those are still listed in modules.dep). + * module-init-tools version 3.4: + * # modprobe bogus + * FATAL: Module bogus not found. [exitcode 1] + * # modprobe -q bogus [silent, exitcode still 1] + * but: + * # rm kernel/drivers/net/dummy.ko + * # modprobe -q dummy + * FATAL: Could not open '/lib/modules/xxx/kernel/drivers/net/dummy.ko': No such file or directory + * [exitcode 1] + */ +static int do_modprobe(struct module_entry *m) +{ + struct module_entry *m2 = m2; /* for compiler */ + char *fn, *options; + int rc, first; + llist_t *l; + + if (!(m->flags & MODULE_FLAG_FOUND_IN_MODDEP)) { + if (!(option_mask32 & INSMOD_OPT_SILENT)) + bb_error_msg("module %s not found in modules.dep", + humanly_readable_name(m)); + return -ENOENT; + } + DBG("do_modprob'ing %s", m->modname); + + if (!(option_mask32 & MODPROBE_OPT_REMOVE)) + m->deps = llist_rev(m->deps); + + for (l = m->deps; l != NULL; l = l->link) + DBG("dep: %s", l->data); + + first = 1; + rc = 0; + while (m->deps) { + rc = 0; + fn = llist_pop(&m->deps); /* we leak it */ + m2 = get_or_add_modentry(fn); + + if (option_mask32 & MODPROBE_OPT_REMOVE) { + /* modprobe -r */ + if (m2->flags & MODULE_FLAG_LOADED) { + rc = bb_delete_module(m2->modname, O_EXCL); + if (rc) { + if (first) { + bb_error_msg("can't unload module %s: %s", + humanly_readable_name(m2), + moderror(rc)); + break; + } + } else { + m2->flags &= ~MODULE_FLAG_LOADED; } - current-> m_name = mod; - current-> m_path = bb_xstrdup(modpath); - current-> m_options = NULL; - current-> m_isalias = 0; - current-> m_depcnt = 0; - current-> m_deparr = 0; - current-> m_next = 0; - - p = col + 1; } - else - /* this line is not a dep description */ - p = 0; + /* do not error out if *deps* fail to unload */ + first = 0; + continue; } - else - /* It's a dep description continuation */ - p = buffer; - - while ( p && *p && isblank(*p)) - p++; - - /* p points to the first dependable module; if NULL, no dependable module */ - if ( p && *p ) { - char *end = &buffer [l-1]; - char *deps; - char *dep; - char *next; - int ext = 0; - - while ( isblank ( *end ) || ( *end == '\\' )) - end--; - - do - { - /* search the end of the dependency */ - next = strchr (p, ' ' ); - if (next) - { - *next = 0; - next--; - } - else - next = end; - - /* find the beginning of the module file name */ - deps = strrchr ( p, '/' ); - - if ( !deps || ( deps < p )) { - deps = p; - while ( isblank ( *deps )) - deps++; - } - else - deps++; - - /* find the end of the module name in the file name */ - if ( ENABLE_FEATURE_2_6_MODULES && - (k_version > 4) && ( *(next-2) == '.' ) && - ( *(next-1) == 'k' ) && ( *next == 'o' ) ) - ext = 3; - else - if (( *(next-1) == '.' ) && ( *next == 'o' )) - ext = 2; - - /* Cope with blank lines */ - if ((next-deps-ext+1) <= 0) - continue; - dep = bb_xstrndup ( deps, next - deps - ext + 1 ); - - /* Add the new dependable module name */ - current-> m_depcnt++; - current-> m_deparr = (char **) xrealloc ( current-> m_deparr, - sizeof ( char *) * current-> m_depcnt ); - current-> m_deparr [current-> m_depcnt - 1] = dep; - - p = next + 2; - } while (next < end); + if (m2->flags & MODULE_FLAG_LOADED) { + DBG("%s is already loaded, skipping", fn); + continue; } - /* is there other dependable module(s) ? */ - if ( buffer [l-1] == '\\' ) - continuation_line = 1; - else - continuation_line = 0; - } - close ( fd ); - - if (!ENABLE_FEATURE_2_6_MODULES - || ( fd = open ( "/etc/modprobe.conf", O_RDONLY )) < 0 ) - if (( fd = open ( "/etc/modules.conf", O_RDONLY )) < 0 ) - if (( fd = open ( "/etc/conf.modules", O_RDONLY )) < 0 ) - return first; - - include_conf (&first, ¤t, buffer, sizeof(buffer), fd); - close(fd); - - filename = bb_xasprintf("/lib/modules/%s/modules.alias", un.release); - fd = open ( filename, O_RDONLY ); - if (ENABLE_FEATURE_CLEAN_UP) - free(filename); - if (fd < 0) { - /* Ok, that didn't work. Fall back to looking in /lib/modules */ - if (( fd = open ( "/lib/modules/modules.alias", O_RDONLY )) < 0 ) { - return first; + options = m2->options; + m2->options = NULL; + options = parse_and_add_kcmdline_module_options(options, m2->modname); + if (m == m2) + options = gather_options_str(options, G.cmdline_mopts); + rc = bb_init_module(fn, options); + DBG("loaded %s '%s', rc:%d", fn, options, rc); + if (rc == EEXIST) + rc = 0; + free(options); + if (rc) { + bb_error_msg("can't load module %s (%s): %s", + humanly_readable_name(m2), + fn, + moderror(rc) + ); + break; } + m2->flags |= MODULE_FLAG_LOADED; } - include_conf (&first, ¤t, buffer, sizeof(buffer), fd); - close(fd); - - return first; + return rc; } -/* return 1 = loaded, 0 = not loaded, -1 = can't tell */ -static int already_loaded (const char *name) +static void load_modules_dep(void) { - int fd; - char buffer[4096]; - - fd = open ("/proc/modules", O_RDONLY); - if (fd < 0) - return -1; + struct module_entry *m; + char *colon, *tokens[2]; + parser_t *p; + + /* Modprobe does not work at all without modules.dep, + * even if the full module name is given. Returning error here + * was making us later confuse user with this message: + * "module /full/path/to/existing/file/module.ko not found". + * It's better to die immediately, with good message. + * xfopen_for_read provides that. */ + p = config_open2(CONFIG_DEFAULT_DEPMOD_FILE, xfopen_for_read); + + while (G.num_unresolved_deps + && config_read(p, tokens, 2, 1, "# \t", PARSE_NORMAL) + ) { + colon = last_char_is(tokens[0], ':'); + if (colon == NULL) + continue; + *colon = 0; - while ( reads ( fd, buffer, sizeof( buffer ))) { - char *p; + m = get_modentry(tokens[0]); + if (m == NULL) + continue; - p = strchr (buffer, ' '); - if (p) { - *p = 0; - for( p = buffer; ENABLE_FEATURE_2_6_MODULES && *p; p++ ) { - *p = ((*p)=='-')?'_':*p; - } - if (strcmp (name, buffer) == 0) { - close (fd); - return 1; - } + /* Optimization... */ + if ((m->flags & MODULE_FLAG_LOADED) + && !(option_mask32 & MODPROBE_OPT_REMOVE) + ) { + DBG("skip deps of %s, it's already loaded", tokens[0]); + continue; } - } - close (fd); - return 0; + m->flags |= MODULE_FLAG_FOUND_IN_MODDEP; + if ((m->flags & MODULE_FLAG_NEED_DEPS) && (m->deps == NULL)) { + G.num_unresolved_deps--; + llist_add_to(&m->deps, xstrdup(tokens[0])); + if (tokens[1]) + string_to_llist(tokens[1], &m->deps, " \t"); + } else + DBG("skipping dep line"); + } + config_close(p); } -static int mod_process ( struct mod_list_t *list, int do_insert ) +int modprobe_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int modprobe_main(int argc UNUSED_PARAM, char **argv) { - int rc = 0; - char **argv = NULL; - struct mod_opt_t *opts; - int argc_malloc; /* never used when CONFIG_FEATURE_CLEAN_UP not defined */ - int argc; - - while ( list ) { - argc = 0; - if( ENABLE_FEATURE_CLEAN_UP ) - argc_malloc = 0; - /* If CONFIG_FEATURE_CLEAN_UP is not defined, then we leak memory - * each time we allocate memory for argv. - * But it is (quite) small amounts of memory that leak each - * time a module is loaded, and it is reclaimed when modprobe - * exits anyway (even when standalone shell?). - * This could become a problem when loading a module with LOTS of - * dependencies, with LOTS of options for each dependencies, with - * very little memory on the target... But in that case, the module - * would not load because there is no more memory, so there's no - * problem. */ - /* enough for minimal insmod (5 args + NULL) or rmmod (3 args + NULL) */ - argv = (char**) malloc( 6 * sizeof( char* ) ); - if ( do_insert ) { - if (already_loaded (list->m_name) != 1) { - argv[argc++] = "insmod"; - if (do_syslog) - argv[argc++] = "-s"; - if (autoclean) - argv[argc++] = "-k"; - if (quiet) - argv[argc++] = "-q"; - else if(verbose) /* verbose and quiet are mutually exclusive */ - argv[argc++] = "-v"; - argv[argc++] = list-> m_path; - if( ENABLE_FEATURE_CLEAN_UP ) - argc_malloc = argc; - opts = list-> m_options; - while( opts ) { - /* Add one more option */ - argc++; - argv = (char**) xrealloc( argv, ( argc + 1 ) * sizeof( char* ) ); - argv[argc-1] = opts-> m_opt_val; - opts = opts-> m_next; - } - } - } else { - /* modutils uses short name for removal */ - if (already_loaded (list->m_name) != 0) { - argv[argc++] = "rmmod"; - if (do_syslog) - argv[argc++] = "-s"; - argv[argc++] = list->m_name; - if( ENABLE_FEATURE_CLEAN_UP ) - argc_malloc = argc; - } - } - argv[argc] = NULL; - - if (argc) { - if (verbose) { - printf("%s module %s\n", do_insert?"Loading":"Unloading", list-> m_name ); - } - if (!show_only) { - int rc2 = 0; - int status; - switch (fork()) { - case -1: - rc2 = 1; - break; - case 0: //child - execvp(argv[0], argv); - bb_perror_msg_and_die("exec of %s", argv[0]); - /* NOTREACHED */ - default: - if (wait(&status) == -1) { - rc2 = 1; - break; + struct utsname uts; + int rc; + unsigned opt; + struct module_entry *me; + + opt_complementary = MODPROBE_COMPLEMENTARY; + opt = getopt32(argv, INSMOD_OPTS MODPROBE_OPTS INSMOD_ARGS); + argv += optind; + + /* Goto modules location */ + xchdir(CONFIG_DEFAULT_MODULES_DIR); + uname(&uts); + xchdir(uts.release); + + if (opt & MODPROBE_OPT_LIST_ONLY) { + char name[MODULE_NAME_LEN]; + char *colon, *tokens[2]; + parser_t *p = config_open2(CONFIG_DEFAULT_DEPMOD_FILE, xfopen_for_read); + + while (config_read(p, tokens, 2, 1, "# \t", PARSE_NORMAL)) { + colon = last_char_is(tokens[0], ':'); + if (!colon) + continue; + *colon = '\0'; + filename2modname(tokens[0], name); + if (!argv[0]) + puts(tokens[0]); + else { + int i; + for (i = 0; argv[i]; i++) { + if (fnmatch(argv[i], name, 0) == 0) { + puts(tokens[0]); } - if (WIFEXITED(status)) - rc2 = WEXITSTATUS(status); - if (WIFSIGNALED(status)) - rc2 = WTERMSIG(status); - break; } - if (do_insert) { - rc = rc2; /* only last module matters */ - } - else if (!rc2) { - rc = 0; /* success if remove any mod */ - } - } - if( ENABLE_FEATURE_CLEAN_UP ) - /* the last value in the array has index == argc, but - * it is the terminating NULL, so we must not free it. */ - while( argc_malloc < argc ) { - free( argv[argc_malloc++] ); } } - if( ENABLE_FEATURE_CLEAN_UP ) { - free( argv ); - argv = NULL; - } - list = do_insert ? list-> m_prev : list-> m_next; + return EXIT_SUCCESS; } - return (show_only) ? 0 : rc; -} -/* - * Builds the dependency list (aka stack) of a module. - * head: the highest module in the stack (last to insmod, first to rmmod) - * tail: the lowest module in the stack (first to insmod, last to rmmod) - */ -static void check_dep ( char *mod, struct mod_list_t **head, struct mod_list_t **tail ) -{ - struct mod_list_t *find; - struct dep_t *dt; - struct mod_opt_t *opt = 0; - char *path = 0; - - // check dependencies - for ( dt = depend; dt; dt = dt-> m_next ) { - if ( strcmp ( dt-> m_name, mod ) == 0) { - break; + /* Yes, for some reason -l ignores -s... */ + if (opt & INSMOD_OPT_SYSLOG) + logmode = LOGMODE_SYSLOG; + + if (!argv[0]) { + if (opt & MODPROBE_OPT_REMOVE) { + /* "modprobe -r" (w/o params). + * "If name is NULL, all unused modules marked + * autoclean will be removed". + */ + if (bb_delete_module(NULL, O_NONBLOCK | O_EXCL) != 0) + bb_perror_msg_and_die("rmmod"); } + return EXIT_SUCCESS; } - if( !dt ) { - bb_error_msg ("module %s not found.", mod); - return; + /* Retrieve module names of already loaded modules */ + { + char *s; + parser_t *parser = config_open2("/proc/modules", fopen_for_read); + while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY)) + get_or_add_modentry(s)->flags |= MODULE_FLAG_LOADED; + config_close(parser); } - // resolve alias names - while ( dt-> m_isalias ) { - if ( dt-> m_depcnt == 1 ) { - struct dep_t *adt; - - for ( adt = depend; adt; adt = adt-> m_next ) { - if ( strcmp ( adt-> m_name, dt-> m_deparr [0] ) == 0 ) - break; - } - if ( adt ) { - /* This is the module we are aliased to */ - struct mod_opt_t *opts = dt-> m_options; - /* Option of the alias are appended to the options of the module */ - while( opts ) { - adt-> m_options = append_option( adt-> m_options, opts-> m_opt_val ); - opts = opts-> m_next; - } - dt = adt; - } - else { - bb_error_msg ("module %s not found.", mod); - return; - } - } - else { - bb_error_msg ("Bad alias %s", dt-> m_name); - return; - } - } - - mod = dt-> m_name; - path = dt-> m_path; - opt = dt-> m_options; - - // search for duplicates - for ( find = *head; find; find = find-> m_next ) { - if ( !strcmp ( mod, find-> m_name )) { - // found -> dequeue it - - if ( find-> m_prev ) - find-> m_prev-> m_next = find-> m_next; - else - *head = find-> m_next; - - if ( find-> m_next ) - find-> m_next-> m_prev = find-> m_prev; - else - *tail = find-> m_prev; - - break; // there can be only one duplicate - } - } - - if ( !find ) { // did not find a duplicate - find = (struct mod_list_t *) xmalloc ( sizeof(struct mod_list_t)); - find-> m_name = mod; - find-> m_path = path; - find-> m_options = opt; + if (opt & (MODPROBE_OPT_INSERT_ALL | MODPROBE_OPT_REMOVE)) { + /* Each argument is a module name */ + do { + DBG("adding module %s", *argv); + add_probe(*argv++); + } while (*argv); + } else { + /* First argument is module name, rest are parameters */ + DBG("probing just module %s", *argv); + add_probe(argv[0]); + G.cmdline_mopts = parse_cmdline_module_options(argv); } - // enqueue at tail - if ( *tail ) - (*tail)-> m_next = find; - find-> m_prev = *tail; - find-> m_next = 0; - - if ( !*head ) - *head = find; - *tail = find; - - if ( dt ) { - int i; - - /* Add all dependable module for that new module */ - for ( i = 0; i < dt-> m_depcnt; i++ ) - check_dep ( dt-> m_deparr [i], head, tail ); + /* Happens if all requested modules are already loaded */ + if (G.probes == NULL) + return EXIT_SUCCESS; + + read_config("/etc/modprobe.conf"); + read_config("/etc/modprobe.d"); + if (ENABLE_FEATURE_MODUTILS_SYMBOLS && G.need_symbols) + read_config("modules.symbols"); + load_modules_dep(); + if (ENABLE_FEATURE_MODUTILS_ALIAS && G.num_unresolved_deps) { + read_config("modules.alias"); + load_modules_dep(); } -} -static int mod_insert ( char *mod, int argc, char **argv ) -{ - struct mod_list_t *tail = 0; - struct mod_list_t *head = 0; - int rc; - - // get dep list for module mod - check_dep ( mod, &head, &tail ); - - if ( head && tail ) { - if( argc ) { - int i; - // append module args - for ( i = 0; i < argc; i++ ) - head->m_options = append_option( head->m_options, argv[i] ); + rc = 0; + while ((me = llist_pop(&G.probes)) != NULL) { + if (me->realnames == NULL) { + DBG("probing by module name"); + /* This is not an alias. Literal names are blacklisted + * only if '-b' is given. + */ + if (!(opt & MODPROBE_OPT_BLACKLIST) + || !(me->flags & MODULE_FLAG_BLACKLISTED) + ) { + rc |= do_modprobe(me); + } + continue; } - // process tail ---> head - rc = mod_process ( tail, 1 ); - } - else - rc = 1; - - return rc; -} - -static int mod_remove ( char *mod ) -{ - int rc; - static struct mod_list_t rm_a_dummy = { "-a", NULL, NULL, NULL, NULL }; - - struct mod_list_t *head = 0; - struct mod_list_t *tail = 0; - - if ( mod ) - check_dep ( mod, &head, &tail ); - else // autoclean - head = tail = &rm_a_dummy; - - if ( head && tail ) - rc = mod_process ( head, 0 ); // process head ---> tail - else - rc = 1; - return rc; - -} - -int modprobe_main(int argc, char** argv) -{ - int rc = EXIT_SUCCESS; - char *unused; - - bb_opt_complementally = "?V-:q-v:v-q"; - main_opts = bb_getopt_ulflags(argc, argv, "acdklnqrst:vVC:", - &unused, &unused); - if((main_opts & (DUMP_CONF_EXIT | LIST_ALL))) - return EXIT_SUCCESS; - if((main_opts & (RESTRICT_DIR | CONFIG_FILE))) - bb_error_msg_and_die("-t and -C not supported"); - - depend = build_dep ( ); - - if ( !depend ) - bb_error_msg_and_die ( "could not parse modules.dep\n" ); - - if (remove_opt) { + /* Probe all real names for the alias */ do { - if (mod_remove ( optind < argc ? - argv [optind] : NULL )) { - bb_error_msg ("failed to remove module %s", - argv [optind] ); - rc = EXIT_FAILURE; + char *realname = llist_pop(&me->realnames); + struct module_entry *m2; + + DBG("probing alias %s by realname %s", me->modname, realname); + m2 = get_or_add_modentry(realname); + if (!(m2->flags & MODULE_FLAG_BLACKLISTED) + && (!(m2->flags & MODULE_FLAG_LOADED) + || (opt & MODPROBE_OPT_REMOVE)) + ) { +//TODO: we can pass "me" as 2nd param to do_modprobe, +//and make do_modprobe emit more meaningful error messages +//with alias name included, not just module name alias resolves to. + rc |= do_modprobe(m2); } - } while ( ++optind < argc ); - } else { - if (optind >= argc) - bb_error_msg_and_die ( "No module or pattern provided\n" ); - - if ( mod_insert ( argv [optind], argc - optind - 1, argv + optind + 1 )) - bb_error_msg_and_die ( "failed to load module %s", argv [optind] ); + free(realname); + } while (me->realnames != NULL); } - /* Here would be a good place to free up memory allocated during the dependencies build. */ - - return rc; + return (rc != 0); }