*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
-
-//applet:IF_MODPROBE(APPLET(modprobe, BB_DIR_SBIN, BB_SUID_DROP))
+//config:config MODPROBE
+//config: bool "modprobe (28 kb)"
+//config: default y
+//config: select PLATFORM_LINUX
+//config: help
+//config: Handle the loading of modules, and their dependencies on a high
+//config: level.
+//config:
+//config:config FEATURE_MODPROBE_BLACKLIST
+//config: bool "Blacklist support"
+//config: default y
+//config: depends on MODPROBE && !MODPROBE_SMALL
+//config: help
+//config: Say 'y' here to enable support for the 'blacklist' command in
+//config: modprobe.conf. This prevents the alias resolver to resolve
+//config: blacklisted modules. This is useful if you want to prevent your
+//config: hardware autodetection scripts to load modules like evdev, frame
+//config: buffer drivers etc.
+
+//applet:IF_MODPROBE(IF_NOT_MODPROBE_SMALL(APPLET_NOEXEC(modprobe, modprobe, BB_DIR_SBIN, BB_SUID_DROP, modprobe)))
+
+//kbuild:ifneq ($(CONFIG_MODPROBE_SMALL),y)
+//kbuild:lib-$(CONFIG_MODPROBE) += modprobe.o modutils.o
+//kbuild:endif
#include "libbb.h"
#include "modutils.h"
//usage:
//usage:#define modprobe_trivial_usage
//usage: "[-alrqvsD" IF_FEATURE_MODPROBE_BLACKLIST("b") "]"
-//usage: " MODULE [SYMBOL=VALUE]..."
+//usage: " MODULE" IF_FEATURE_CMDLINE_MODULE_OPTIONS(" [SYMBOL=VALUE]...")
//usage:#define modprobe_full_usage "\n\n"
//usage: " -a Load multiple MODULEs"
//usage: "\n -l List (MODULE is a pattern)"
*/
#define MODPROBE_OPTS "alrDb"
/* -a and -D _are_ in fact compatible */
-#define MODPROBE_COMPLEMENTARY ("q-v:v-q:l--arD:r--alD:a--lr:D--rl")
+#define MODPROBE_COMPLEMENTARY "q-v:v-q:l--arD:r--alD:a--lr:D--rl"
//#define MODPROBE_OPTS "acd:lnrt:C:b"
//#define MODPROBE_COMPLEMENTARY "q-v:v-q:l--acr:a--lr:r--al"
enum {
/* "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 */
-};
-
-#define DB_HASH_SIZE 256
+#define MODULE_FLAG_BUILTIN 0x0010
struct globals {
llist_t *probes; /* MEs of module(s) requested on cmdline */
+#if ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS
char *cmdline_mopts; /* module options from cmdline */
+#endif
int num_unresolved_deps;
/* bool. "Did we have 'symbol:FOO' requested on cmdline?" */
smallint need_symbols;
struct utsname uts;
- llist_t *db[DB_HASH_SIZE]; /* MEs of all modules ever seen (caching for speed) */
+ module_db db;
} FIX_ALIASING;
#define G (*ptr_to_globals)
#define INIT_G() do { \
return opts;
}
-/* These three functions called many times, optimizing for speed.
- * Users reported minute-long delays when they runn iptables repeatedly
- * (iptables use modprobe to install needed kernel modules).
- */
-static struct module_entry *helper_get_module(const char *module, int create)
-{
- char modname[MODULE_NAME_LEN];
- struct module_entry *e;
- llist_t *l;
- unsigned i;
- unsigned hash;
-
- filename2modname(bb_get_last_path_component_nostrip(module), modname);
-
- hash = 0;
- for (i = 0; modname[i]; i++)
- hash = ((hash << 5) + hash) + modname[i];
- hash %= DB_HASH_SIZE;
-
- for (l = G.db[hash]; l; l = l->link) {
- e = (struct module_entry *) l->data;
- if (strcmp(e->modname, modname) == 0)
- return e;
- }
- if (!create)
- return NULL;
-
- e = xzalloc(sizeof(*e));
- e->modname = xstrdup(modname);
- llist_add_to(&G.db[hash], e);
-
- return e;
-}
-static ALWAYS_INLINE struct module_entry *get_or_add_modentry(const char *module)
+static struct module_entry *get_or_add_modentry(const char *module)
{
- return helper_get_module(module, 1);
-}
-static ALWAYS_INLINE struct module_entry *get_modentry(const char *module)
-{
- return helper_get_module(module, 0);
+ return moddb_get_or_create(&G.db, module);
}
static void add_probe(const char *name)
m = get_or_add_modentry(name);
if (!(option_mask32 & (OPT_REMOVE | OPT_SHOW_DEPS))
- && (m->flags & MODULE_FLAG_LOADED)
+ && (m->flags & (MODULE_FLAG_LOADED | MODULE_FLAG_BUILTIN))
) {
DBG("skipping %s, it is already loaded", name);
return;
llist_add_to_end(&G.probes, m);
G.num_unresolved_deps++;
if (ENABLE_FEATURE_MODUTILS_SYMBOLS
- && strncmp(m->modname, "symbol:", 7) == 0
+ && is_prefixed_with(m->modname, "symbol:")
) {
G.need_symbols = 1;
}
static int FAST_FUNC config_file_action(const char *filename,
struct stat *statbuf UNUSED_PARAM,
void *userdata UNUSED_PARAM,
- int depth UNUSED_PARAM)
+ int depth)
{
char *tokens[3];
parser_t *p;
struct module_entry *m;
int rc = TRUE;
+ const char *base;
- if (bb_basename(filename)[0] == '.')
+ /* Skip files that begin with a "." */
+ base = bb_basename(filename);
+ if (base[0] == '.')
goto error;
+ /* "man modprobe.d" from kmod version 22 suggests
+ * that we shouldn't recurse into /etc/modprobe.d/dir/
+ * _subdirectories_:
+ */
+ if (depth > 1)
+ return SKIP; /* stop recursing */
+//TODO: instead, can use dirAction in recursive_action() to SKIP dirs
+//on depth == 1 level. But that's more code...
+
+ /* In dir recursion, skip files that do not end with a ".conf"
+ * depth==0: read_config("modules.{symbols,alias}") must work,
+ * "include FILE_NOT_ENDING_IN_CONF" must work too.
+ */
+ if (depth != 0) {
+ if (!is_suffixed_with(base, ".conf"))
+ goto error;
+ }
+
p = config_open2(filename, fopen_for_read);
if (p == NULL) {
rc = FALSE;
m = get_or_add_modentry(tokens[1]);
m->options = gather_options_str(m->options, tokens[2]);
} else if (strcmp(tokens[0], "include") == 0) {
- /* include <filename> */
+ /* include <filename>/<dirname> (yes, directories also must work) */
read_config(tokens[1]);
} else if (ENABLE_FEATURE_MODPROBE_BLACKLIST
&& strcmp(tokens[0], "blacklist") == 0
static int read_config(const char *path)
{
return recursive_action(path, ACTION_RECURSE | ACTION_QUIET,
- config_file_action, NULL, NULL, 1);
+ config_file_action, NULL, NULL,
+ /*depth:*/ 0);
}
static const char *humanly_readable_name(struct module_entry *m)
return m->probed_name ? m->probed_name : m->modname;
}
+/* Like strsep(&stringp, "\n\t ") but quoted text goes to single token
+ * even if it contains whitespace.
+ */
+static char *strsep_quotes(char **stringp)
+{
+ char *s, *start = *stringp;
+
+ if (!start)
+ return NULL;
+
+ for (s = start; ; s++) {
+ switch (*s) {
+ case '"':
+ s = strchrnul(s + 1, '"'); /* find trailing quote */
+ if (*s != '\0')
+ s++; /* skip trailing quote */
+ /* fall through */
+ case '\0':
+ case '\n':
+ case '\t':
+ case ' ':
+ if (*s != '\0') {
+ *s = '\0';
+ *stringp = s + 1;
+ } else {
+ *stringp = NULL;
+ }
+ return start;
+ }
+ }
+}
+
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 != '.')
+ while ((kptr = strsep_quotes(&kcmdline)) != NULL) {
+ char *after_modulename = is_prefixed_with(kptr, modulename);
+ if (!after_modulename || *after_modulename != '.')
continue;
/* It is "modulename.xxxx" */
- kptr++;
+ kptr = after_modulename + 1;
if (strchr(kptr, '=') != NULL) {
/* It is "modulename.opt=[val]" */
options = gather_options_str(options, kptr);
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));
+ bb_error_msg((m->flags & MODULE_FLAG_BUILTIN) ?
+ "module %s is builtin" :
+ "module %s not found in modules.dep",
+ humanly_readable_name(m));
return -ENOENT;
}
DBG("do_modprob'ing %s", m->modname);
rc = 0;
fn = llist_pop(&m->deps); /* we leak it */
- m2 = get_or_add_modentry(fn);
+ m2 = get_or_add_modentry(bb_get_last_path_component_nostrip(fn));
if (option_mask32 & OPT_REMOVE) {
/* modprobe -r */
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));
+ bb_perror_msg("can't unload module '%s'",
+ humanly_readable_name(m2));
break;
}
} else {
options = m2->options;
m2->options = NULL;
options = parse_and_add_kcmdline_module_options(options, m2->modname);
+#if ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS
if (m == m2)
options = gather_options_str(options, G.cmdline_mopts);
+#endif
if (option_mask32 & OPT_SHOW_DEPS) {
printf(options ? "insmod %s/%s/%s %s\n"
colon = last_char_is(tokens[0], ':');
if (colon == NULL)
continue;
- *colon = 0;
+ *colon = '\0';
- m = get_modentry(tokens[0]);
+ m = moddb_get(&G.db, bb_get_last_path_component_nostrip(tokens[0]));
if (m == NULL)
continue;
INIT_G();
- IF_LONG_OPTS(applet_long_options = modprobe_longopts;)
- opt_complementary = MODPROBE_COMPLEMENTARY;
- opt = getopt32(argv, INSMOD_OPTS MODPROBE_OPTS INSMOD_ARGS);
+ opt = getopt32long(argv, "^" INSMOD_OPTS MODPROBE_OPTS "\0" MODPROBE_COMPLEMENTARY,
+ modprobe_longopts
+ INSMOD_ARGS
+ );
argv += optind;
/* Goto modules location */
* autoclean will be removed".
*/
if (bb_delete_module(NULL, O_NONBLOCK | O_EXCL) != 0)
- bb_perror_msg_and_die("rmmod");
+ bb_perror_nomsg_and_die();
}
return EXIT_SUCCESS;
}
while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY))
get_or_add_modentry(s)->flags |= MODULE_FLAG_LOADED;
config_close(parser);
+
+ parser = config_open2("modules.builtin", fopen_for_read);
+ while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL))
+ get_or_add_modentry(s)->flags |= MODULE_FLAG_BUILTIN;
+ config_close(parser);
}
if (opt & (OPT_INSERT_ALL | OPT_REMOVE)) {
/* First argument is module name, rest are parameters */
DBG("probing just module %s", *argv);
add_probe(argv[0]);
+#if ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS
G.cmdline_mopts = parse_cmdline_module_options(argv, /*quote_spaces:*/ 1);
+#endif
}
/* Happens if all requested modules are already loaded */
} while (me->realnames != NULL);
}
+ if (ENABLE_FEATURE_CLEAN_UP)
+ moddb_free(&G.db);
+
return (rc != 0);
}