busybox/modutils/modprobe-small.c
Explorer09 80172e4622 modprobe-small: optimizations for single applet build
(I'm requesting for a review first because I fear such an aggressive
change could lead to bugs. While I observe the sizes have reduced, I
haven't test the functionality of each applet after that. So please
test before merging.)

Aggressively cut off unneeded code when the relevant applets are not
built.

Correct dependencies of FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE and
FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED.

Don't bother with the '-r' option check if only rmmod is built (assume
true then), or when neither rmmod or mobprobe is built (assume false
then).

Size comparison before and after the change (single applet
configuration):

     text    data     bss     dec     hex filename
    34778     946     112   35836    8bfc old/busybox_DEPMOD
    34151     946     112   35209    8989 new/busybox_DEPMOD

    34903     946     112   35961    8c79 old/busybox_INSMOD
    28316     778     112   29206    7216 new/busybox_INSMOD

    35228     962     112   36302    8dce old/busybox_LSMOD
     5011     706      40    5757    167d new/busybox_LSMOD

    34830     946     112   35888    8c30 old/busybox_MODPROBE
    34795     946     112   35853    8c0d new/busybox_MODPROBE

    34718     946     112   35776    8bc0 old/busybox_RMMOD
     7502     714     104    8320    2080 new/busybox_RMMOD

Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2017-01-09 09:31:17 +01:00

1059 lines
30 KiB
C

/* vi: set sw=4 ts=4: */
/*
* simplified modprobe
*
* Copyright (c) 2008 Vladimir Dronnikov
* Copyright (c) 2008 Bernhard Reutner-Fischer (initial depmod code)
*
* Licensed under GPLv2, see file LICENSE in this source tree.
*/
/* config MODPROBE_SMALL is defined in Config.src to ensure better "make config" order */
//config:config FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
//config: bool "Accept module options on modprobe command line"
//config: default y
//config: depends on MODPROBE_SMALL && (INSMOD || MODPROBE)
//config: help
//config: Allow insmod and modprobe take module options from command line.
//config:
//config:config FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED
//config: bool "Skip loading of already loaded modules"
//config: default y
//config: depends on MODPROBE_SMALL && (DEPMOD || INSMOD || MODPROBE)
//config: help
//config: Check if the module is already loaded.
//applet:IF_MODPROBE(IF_MODPROBE_SMALL(APPLET(modprobe, BB_DIR_SBIN, BB_SUID_DROP)))
//applet:IF_DEPMOD(IF_MODPROBE_SMALL(APPLET_ODDNAME(depmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, depmod)))
//applet:IF_INSMOD(IF_MODPROBE_SMALL(APPLET_ODDNAME(insmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, insmod)))
//applet:IF_LSMOD(IF_MODPROBE_SMALL(APPLET_ODDNAME(lsmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, lsmod)))
//applet:IF_RMMOD(IF_MODPROBE_SMALL(APPLET_ODDNAME(rmmod, modprobe, BB_DIR_SBIN, BB_SUID_DROP, rmmod)))
//kbuild:lib-$(CONFIG_MODPROBE_SMALL) += modprobe-small.o
#include "libbb.h"
/* After libbb.h, since it needs sys/types.h on some systems */
#include <sys/utsname.h> /* uname() */
#include <fnmatch.h>
#include <sys/syscall.h>
#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
#define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)
#ifdef __NR_finit_module
# define finit_module(fd, uargs, flags) syscall(__NR_finit_module, fd, uargs, flags)
#endif
/* linux/include/linux/module.h has limit of 64 chars on module names */
#undef MODULE_NAME_LEN
#define MODULE_NAME_LEN 64
#if 1
# define dbg1_error_msg(...) ((void)0)
# define dbg2_error_msg(...) ((void)0)
#else
# define dbg1_error_msg(...) bb_error_msg(__VA_ARGS__)
# define dbg2_error_msg(...) bb_error_msg(__VA_ARGS__)
#endif
#define DEPFILE_BB CONFIG_DEFAULT_DEPMOD_FILE".bb"
#define ONLY_APPLET (ENABLE_MODPROBE + ENABLE_DEPMOD + ENABLE_INSMOD \
+ ENABLE_LSMOD + ENABLE_RMMOD <= 1)
#define is_modprobe (ENABLE_MODPROBE && (ONLY_APPLET || applet_name[0] == 'm'))
#define is_depmod (ENABLE_DEPMOD && (ONLY_APPLET || applet_name[0] == 'd'))
#define is_insmod (ENABLE_INSMOD && (ONLY_APPLET || applet_name[0] == 'i'))
#define is_lsmod (ENABLE_LSMOD && (ONLY_APPLET || applet_name[0] == 'l'))
#define is_rmmod (ENABLE_RMMOD && (ONLY_APPLET || applet_name[0] == 'r'))
enum {
OPT_q = (1 << 0), /* be quiet */
OPT_r = (1 << 1), /* module removal instead of loading */
};
typedef struct module_info {
char *pathname;
char *aliases;
char *deps;
smallint open_read_failed;
} module_info;
/*
* GLOBALS
*/
struct globals {
module_info *modinfo;
char *module_load_options;
smallint dep_bb_seen;
smallint wrote_dep_bb_ok;
unsigned module_count;
int module_found_idx;
unsigned stringbuf_idx;
unsigned stringbuf_size;
char *stringbuf; /* some modules have lots of stuff */
/* for example, drivers/media/video/saa7134/saa7134.ko */
/* therefore having a fixed biggish buffer is not wise */
};
#define G (*ptr_to_globals)
#define modinfo (G.modinfo )
#define dep_bb_seen (G.dep_bb_seen )
#define wrote_dep_bb_ok (G.wrote_dep_bb_ok )
#define module_count (G.module_count )
#define module_found_idx (G.module_found_idx )
#define module_load_options (G.module_load_options)
#define stringbuf_idx (G.stringbuf_idx )
#define stringbuf_size (G.stringbuf_size )
#define stringbuf (G.stringbuf )
#define INIT_G() do { \
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
} while (0)
static void append(const char *s)
{
unsigned len = strlen(s);
if (stringbuf_idx + len + 15 > stringbuf_size) {
stringbuf_size = stringbuf_idx + len + 127;
dbg2_error_msg("grow stringbuf to %u", stringbuf_size);
stringbuf = xrealloc(stringbuf, stringbuf_size);
}
memcpy(stringbuf + stringbuf_idx, s, len);
stringbuf_idx += len;
}
static void appendc(char c)
{
/* We appendc() only after append(), + 15 trick in append()
* makes it unnecessary to check for overflow here */
stringbuf[stringbuf_idx++] = c;
}
static void bksp(void)
{
if (stringbuf_idx)
stringbuf_idx--;
}
static void reset_stringbuf(void)
{
stringbuf_idx = 0;
}
static char* copy_stringbuf(void)
{
char *copy = xzalloc(stringbuf_idx + 1); /* terminating NUL */
return memcpy(copy, stringbuf, stringbuf_idx);
}
static char* find_keyword(char *ptr, size_t len, const char *word)
{
if (!ptr) /* happens if xmalloc_open_zipped_read_close cannot read it */
return NULL;
len -= strlen(word) - 1;
while ((ssize_t)len > 0) {
char *old = ptr;
char *after_word;
/* search for the first char in word */
ptr = memchr(ptr, word[0], len);
if (ptr == NULL) /* no occurance left, done */
break;
after_word = is_prefixed_with(ptr, word);
if (after_word)
return after_word; /* found, return ptr past it */
++ptr;
len -= (ptr - old);
}
return NULL;
}
static void replace(char *s, char what, char with)
{
while (*s) {
if (what == *s)
*s = with;
++s;
}
}
static char *filename2modname(const char *filename, char *modname)
{
int i;
const char *from;
// Disabled since otherwise "modprobe dir/name" would work
// as if it is "modprobe name". It is unclear why
// 'basenamization' was here in the first place.
//from = bb_get_last_path_component_nostrip(filename);
from = filename;
for (i = 0; i < (MODULE_NAME_LEN-1) && from[i] != '\0' && from[i] != '.'; i++)
modname[i] = (from[i] == '-') ? '_' : from[i];
modname[i] = '\0';
return modname;
}
static int pathname_matches_modname(const char *pathname, const char *modname)
{
int r;
char name[MODULE_NAME_LEN];
filename2modname(bb_get_last_path_component_nostrip(pathname), name);
r = (strcmp(name, modname) == 0);
return r;
}
/* Take "word word", return malloced "word",NUL,"word",NUL,NUL */
static char* str_2_list(const char *str)
{
int len = strlen(str) + 1;
char *dst = xmalloc(len + 1);
dst[len] = '\0';
memcpy(dst, str, len);
//TODO: protect against 2+ spaces: "word word"
replace(dst, ' ', '\0');
return dst;
}
/* We use error numbers in a loose translation... */
static const char *moderror(int err)
{
switch (err) {
case ENOEXEC:
return "invalid module format";
case ENOENT:
return "unknown symbol in module or invalid parameter";
case ESRCH:
return "module has wrong symbol version";
case EINVAL: /* "invalid parameter" */
return "unknown symbol in module or invalid parameter"
+ sizeof("unknown symbol in module or");
default:
return strerror(err);
}
}
static int load_module(const char *fname, const char *options)
{
#if 1
int r;
size_t len = MAXINT(ssize_t);
char *module_image;
if (!options)
options = "";
dbg1_error_msg("load_module('%s','%s')", fname, options);
/*
* First we try finit_module if available. Some kernels are configured
* to only allow loading of modules off of secure storage (like a read-
* only rootfs) which needs the finit_module call. If it fails, we fall
* back to normal module loading to support compressed modules.
*/
r = 1;
# ifdef __NR_finit_module
{
int fd = open(fname, O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
r = finit_module(fd, options, 0) != 0;
close(fd);
}
}
# endif
if (r != 0) {
module_image = xmalloc_open_zipped_read_close(fname, &len);
r = (!module_image || init_module(module_image, len, options) != 0);
free(module_image);
}
dbg1_error_msg("load_module:%d", r);
return r; /* 0 = success */
#else
/* For testing */
dbg1_error_msg("load_module('%s','%s')", fname, options);
return 1;
#endif
}
/* Returns !0 if open/read was unsuccessful */
static int parse_module(module_info *info, const char *pathname)
{
char *module_image;
char *ptr;
size_t len;
size_t pos;
dbg1_error_msg("parse_module('%s')", pathname);
/* Read (possibly compressed) module */
errno = 0;
len = 64 * 1024 * 1024; /* 64 Mb at most */
module_image = xmalloc_open_zipped_read_close(pathname, &len);
/* module_image == NULL is ok here, find_keyword handles it */
//TODO: optimize redundant module body reads
/* "alias1 symbol:sym1 alias2 symbol:sym2" */
reset_stringbuf();
pos = 0;
while (1) {
unsigned start = stringbuf_idx;
ptr = find_keyword(module_image + pos, len - pos, "alias=");
if (!ptr) {
ptr = find_keyword(module_image + pos, len - pos, "__ksymtab_");
if (!ptr)
break;
/* DOCME: __ksymtab_gpl and __ksymtab_strings occur
* in many modules. What do they mean? */
if (strcmp(ptr, "gpl") == 0 || strcmp(ptr, "strings") == 0)
goto skip;
dbg2_error_msg("alias:'symbol:%s'", ptr);
append("symbol:");
} else {
dbg2_error_msg("alias:'%s'", ptr);
}
append(ptr);
appendc(' ');
/*
* Don't add redundant aliases, such as:
* libcrc32c.ko symbol:crc32c symbol:crc32c
*/
if (start) { /* "if we aren't the first alias" */
char *found, *last;
stringbuf[stringbuf_idx] = '\0';
last = stringbuf + start;
/*
* String at last-1 is " symbol:crc32c "
* (with both leading and trailing spaces).
*/
if (strncmp(stringbuf, last, stringbuf_idx - start) == 0)
/* First alias matches us */
found = stringbuf;
else
/* Does any other alias match? */
found = strstr(stringbuf, last-1);
if (found < last-1) {
/* There is absolutely the same string before us */
dbg2_error_msg("redundant:'%s'", last);
stringbuf_idx = start;
goto skip;
}
}
skip:
pos = (ptr - module_image);
}
bksp(); /* remove last ' ' */
info->aliases = copy_stringbuf();
replace(info->aliases, '-', '_');
/* "dependency1 depandency2" */
reset_stringbuf();
ptr = find_keyword(module_image, len, "depends=");
if (ptr && *ptr) {
replace(ptr, ',', ' ');
replace(ptr, '-', '_');
dbg2_error_msg("dep:'%s'", ptr);
append(ptr);
}
free(module_image);
info->deps = copy_stringbuf();
info->open_read_failed = (module_image == NULL);
return info->open_read_failed;
}
static FAST_FUNC int fileAction(const char *pathname,
struct stat *sb UNUSED_PARAM,
void *modname_to_match,
int depth UNUSED_PARAM)
{
int cur;
const char *fname;
bool is_remove = (ENABLE_RMMOD && ONLY_APPLET)
|| ((ENABLE_RMMOD || ENABLE_MODPROBE) && (option_mask32 & OPT_r));
pathname += 2; /* skip "./" */
fname = bb_get_last_path_component_nostrip(pathname);
if (!strrstr(fname, ".ko")) {
dbg1_error_msg("'%s' is not a module", pathname);
return TRUE; /* not a module, continue search */
}
cur = module_count++;
modinfo = xrealloc_vector(modinfo, 12, cur);
modinfo[cur].pathname = xstrdup(pathname);
/*modinfo[cur].aliases = NULL; - xrealloc_vector did it */
/*modinfo[cur+1].pathname = NULL;*/
if (!pathname_matches_modname(fname, modname_to_match)) {
dbg1_error_msg("'%s' module name doesn't match", pathname);
return TRUE; /* module name doesn't match, continue search */
}
dbg1_error_msg("'%s' module name matches", pathname);
module_found_idx = cur;
if (parse_module(&modinfo[cur], pathname) != 0)
return TRUE; /* failed to open/read it, no point in trying loading */
if (!is_remove) {
if (load_module(pathname, module_load_options) == 0) {
/* Load was successful, there is nothing else to do.
* This can happen ONLY for "top-level" module load,
* not a dep, because deps dont do dirscan. */
exit(EXIT_SUCCESS);
}
}
return TRUE;
}
static int load_dep_bb(void)
{
char *line;
FILE *fp = fopen_for_read(DEPFILE_BB);
if (!fp)
return 0;
dep_bb_seen = 1;
dbg1_error_msg("loading "DEPFILE_BB);
/* Why? There is a rare scenario: we did not find modprobe.dep.bb,
* we scanned the dir and found no module by name, then we search
* for alias (full scan), and we decided to generate modprobe.dep.bb.
* But we see modprobe.dep.bb.new! Other modprobe is at work!
* We wait and other modprobe renames it to modprobe.dep.bb.
* Now we can use it.
* But we already have modinfo[] filled, and "module_count = 0"
* makes us start anew. Yes, we leak modinfo[].xxx pointers -
* there is not much of data there anyway. */
module_count = 0;
memset(&modinfo[0], 0, sizeof(modinfo[0]));
while ((line = xmalloc_fgetline(fp)) != NULL) {
char* space;
char* linebuf;
int cur;
if (!line[0]) {
free(line);
continue;
}
space = strchrnul(line, ' ');
cur = module_count++;
modinfo = xrealloc_vector(modinfo, 12, cur);
/*modinfo[cur+1].pathname = NULL; - xrealloc_vector did it */
modinfo[cur].pathname = line; /* we take ownership of malloced block here */
if (*space)
*space++ = '\0';
modinfo[cur].aliases = space;
linebuf = xmalloc_fgetline(fp);
modinfo[cur].deps = linebuf ? linebuf : xzalloc(1);
if (modinfo[cur].deps[0]) {
/* deps are not "", so next line must be empty */
line = xmalloc_fgetline(fp);
/* Refuse to work with damaged config file */
if (line && line[0])
bb_error_msg_and_die("error in %s at '%s'", DEPFILE_BB, line);
free(line);
}
}
return 1;
}
static int start_dep_bb_writeout(void)
{
int fd;
/* depmod -n: write result to stdout */
if (applet_name[0] == 'd' && (option_mask32 & 1))
return STDOUT_FILENO;
fd = open(DEPFILE_BB".new", O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644);
if (fd < 0) {
if (errno == EEXIST) {
int count = 5 * 20;
dbg1_error_msg(DEPFILE_BB".new exists, waiting for "DEPFILE_BB);
while (1) {
usleep(1000*1000 / 20);
if (load_dep_bb()) {
dbg1_error_msg(DEPFILE_BB" appeared");
return -2; /* magic number */
}
if (!--count)
break;
}
bb_error_msg("deleting stale %s", DEPFILE_BB".new");
fd = open_or_warn(DEPFILE_BB".new", O_WRONLY | O_CREAT | O_TRUNC);
}
}
dbg1_error_msg("opened "DEPFILE_BB".new:%d", fd);
return fd;
}
static void write_out_dep_bb(int fd)
{
int i;
FILE *fp;
/* We want good error reporting. fdprintf is not good enough. */
fp = xfdopen_for_write(fd);
i = 0;
while (modinfo[i].pathname) {
fprintf(fp, "%s%s%s\n" "%s%s\n",
modinfo[i].pathname, modinfo[i].aliases[0] ? " " : "", modinfo[i].aliases,
modinfo[i].deps, modinfo[i].deps[0] ? "\n" : "");
i++;
}
/* Badly formatted depfile is a no-no. Be paranoid. */
errno = 0;
if (ferror(fp) | fclose(fp)) /* | instead of || is intended */
goto err;
if (fd == STDOUT_FILENO) /* it was depmod -n */
goto ok;
if (rename(DEPFILE_BB".new", DEPFILE_BB) != 0) {
err:
bb_perror_msg("can't create '%s'", DEPFILE_BB);
unlink(DEPFILE_BB".new");
} else {
ok:
wrote_dep_bb_ok = 1;
dbg1_error_msg("created "DEPFILE_BB);
}
}
static module_info** find_alias(const char *alias)
{
int i;
int dep_bb_fd;
int infoidx;
module_info **infovec;
dbg1_error_msg("find_alias('%s')", alias);
try_again:
/* First try to find by name (cheaper) */
i = 0;
while (modinfo[i].pathname) {
if (pathname_matches_modname(modinfo[i].pathname, alias)) {
dbg1_error_msg("found '%s' in module '%s'",
alias, modinfo[i].pathname);
if (!modinfo[i].aliases) {
parse_module(&modinfo[i], modinfo[i].pathname);
}
infovec = xzalloc(2 * sizeof(infovec[0]));
infovec[0] = &modinfo[i];
return infovec;
}
i++;
}
/* Ok, we definitely have to scan module bodies. This is a good
* moment to generate modprobe.dep.bb, if it does not exist yet */
dep_bb_fd = dep_bb_seen ? -1 : start_dep_bb_writeout();
if (dep_bb_fd == -2) /* modprobe.dep.bb appeared? */
goto try_again;
/* Scan all module bodies, extract modinfo (it contains aliases) */
i = 0;
infoidx = 0;
infovec = NULL;
while (modinfo[i].pathname) {
char *desc, *s;
if (!modinfo[i].aliases) {
parse_module(&modinfo[i], modinfo[i].pathname);
}
/* "alias1 symbol:sym1 alias2 symbol:sym2" */
desc = str_2_list(modinfo[i].aliases);
/* Does matching substring exist? */
for (s = desc; *s; s += strlen(s) + 1) {
/* Aliases in module bodies can be defined with
* shell patterns. Example:
* "pci:v000010DEd000000D9sv*sd*bc*sc*i*".
* Plain strcmp() won't catch that */
if (fnmatch(s, alias, 0) == 0) {
dbg1_error_msg("found alias '%s' in module '%s'",
alias, modinfo[i].pathname);
infovec = xrealloc_vector(infovec, 1, infoidx);
infovec[infoidx++] = &modinfo[i];
break;
}
}
free(desc);
i++;
}
/* Create module.dep.bb if needed */
if (dep_bb_fd >= 0) {
write_out_dep_bb(dep_bb_fd);
}
dbg1_error_msg("find_alias '%s' returns %d results", alias, infoidx);
return infovec;
}
#if ENABLE_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED
// TODO: open only once, invent config_rewind()
static int already_loaded(const char *name)
{
int ret;
char *line;
FILE *fp;
ret = 5 * 2;
again:
fp = fopen_for_read("/proc/modules");
if (!fp)
return 0;
while ((line = xmalloc_fgetline(fp)) != NULL) {
char *live;
char *after_name;
// Examples from kernel 3.14.6:
//pcspkr 12718 0 - Live 0xffffffffa017e000
//snd_timer 28690 2 snd_seq,snd_pcm, Live 0xffffffffa025e000
//i915 801405 2 - Live 0xffffffffa0096000
after_name = is_prefixed_with(line, name);
if (!after_name || *after_name != ' ') {
free(line);
continue;
}
live = strstr(line, " Live");
free(line);
if (!live) {
/* State can be Unloading, Loading, or Live.
* modprobe must not return prematurely if we see "Loading":
* it can cause further programs to assume load completed,
* but it did not (yet)!
* Wait up to 5*20 ms for it to resolve.
*/
ret -= 2;
if (ret == 0)
break; /* huh? report as "not loaded" */
fclose(fp);
usleep(20*1000);
goto again;
}
ret = 1;
break;
}
fclose(fp);
return ret & 1;
}
#else
#define already_loaded(name) 0
#endif
static int rmmod(const char *filename)
{
int r;
char modname[MODULE_NAME_LEN];
filename2modname(filename, modname);
r = delete_module(modname, O_NONBLOCK | O_EXCL);
dbg1_error_msg("delete_module('%s', O_NONBLOCK | O_EXCL):%d", modname, r);
if (r != 0 && !(option_mask32 & OPT_q)) {
bb_perror_msg("remove '%s'", modname);
}
return r;
}
/*
* Given modules definition and module name (or alias, or symbol)
* load/remove the module respecting dependencies.
* NB: also called by depmod with bogus name "/",
* just in order to force modprobe.dep.bb creation.
*/
#if !ENABLE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
#define process_module(a,b) process_module(a)
#define cmdline_options ""
#endif
static int process_module(char *name, const char *cmdline_options)
{
char *s, *deps, *options;
module_info **infovec;
module_info *info;
int infoidx;
bool is_remove = (ENABLE_RMMOD && ONLY_APPLET)
|| ((ENABLE_RMMOD || ENABLE_MODPROBE) && (option_mask32 & OPT_r));
int exitcode = EXIT_SUCCESS;
dbg1_error_msg("process_module('%s','%s')", name, cmdline_options);
replace(name, '-', '_');
dbg1_error_msg("already_loaded:%d is_remove:%d", already_loaded(name), is_remove);
if (is_rmmod) {
/* Does not remove dependencies, no need to scan, just remove.
* (compat note: this allows and strips .ko suffix)
*/
rmmod(name);
return EXIT_SUCCESS;
}
/*
* We used to have "is_remove != already_loaded(name)" check here, but
* modprobe -r pci:v00008086d00007010sv00000000sd00000000bc01sc01i80
* won't unload modules (there are more than one)
* which have this alias.
*/
if (!is_remove && already_loaded(name)) {
dbg1_error_msg("nothing to do for '%s'", name);
return EXIT_SUCCESS;
}
options = NULL;
if (!is_remove) {
char *opt_filename = xasprintf("/etc/modules/%s", name);
options = xmalloc_open_read_close(opt_filename, NULL);
if (options)
replace(options, '\n', ' ');
#if ENABLE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
if (cmdline_options) {
/* NB: cmdline_options always have one leading ' '
* (see main()), we remove it here */
char *op = xasprintf(options ? "%s %s" : "%s %s" + 3,
cmdline_options + 1, options);
free(options);
options = op;
}
#endif
free(opt_filename);
module_load_options = options;
dbg1_error_msg("process_module('%s'): options:'%s'", name, options);
}
if (!module_count) {
/* Scan module directory. This is done only once.
* It will attempt module load, and will exit(EXIT_SUCCESS)
* on success.
*/
module_found_idx = -1;
recursive_action(".",
ACTION_RECURSE, /* flags */
fileAction, /* file action */
NULL, /* dir action */
name, /* user data */
0 /* depth */
);
dbg1_error_msg("dirscan complete");
/* Module was not found, or load failed, or is_remove */
if (module_found_idx >= 0) { /* module was found */
infovec = xzalloc(2 * sizeof(infovec[0]));
infovec[0] = &modinfo[module_found_idx];
} else { /* search for alias, not a plain module name */
infovec = find_alias(name);
}
} else {
infovec = find_alias(name);
}
if (!infovec) {
/* both dirscan and find_alias found nothing */
if (!is_remove && !is_depmod) /* it wasn't rmmod or depmod */
bb_error_msg("module '%s' not found", name);
//TODO: _and_die()? or should we continue (un)loading modules listed on cmdline?
goto ret;
}
/* There can be more than one module for the given alias. For example,
* "pci:v00008086d00007010sv00000000sd00000000bc01sc01i80" matches
* ata_piix because it has alias "pci:v00008086d00007010sv*sd*bc*sc*i*"
* and ata_generic, it has alias "pci:v*d*sv*sd*bc01sc01i*"
* Standard modprobe loads them both. We achieve it by returning
* a *list* of modinfo pointers from find_alias().
*/
/* modprobe -r? unload module(s) */
if (is_remove) {
infoidx = 0;
while ((info = infovec[infoidx++]) != NULL) {
int r = rmmod(bb_get_last_path_component_nostrip(info->pathname));
if (r != 0) {
goto ret; /* error */
}
}
/* modprobe -r: we do not stop here -
* continue to unload modules on which the module depends:
* "-r --remove: option causes modprobe to remove a module.
* If the modules it depends on are also unused, modprobe
* will try to remove them, too."
*/
}
infoidx = 0;
while ((info = infovec[infoidx++]) != NULL) {
/* Iterate thru dependencies, trying to (un)load them */
deps = str_2_list(info->deps);
for (s = deps; *s; s += strlen(s) + 1) {
//if (strcmp(name, s) != 0) // N.B. do loops exist?
dbg1_error_msg("recurse on dep '%s'", s);
process_module(s, NULL);
dbg1_error_msg("recurse on dep '%s' done", s);
}
free(deps);
if (is_remove)
continue;
/* We are modprobe: load it */
if (options && strstr(options, "blacklist")) {
dbg1_error_msg("'%s': blacklisted", info->pathname);
continue;
}
if (info->open_read_failed) {
/* We already tried it, didn't work. Don't try load again */
exitcode = EXIT_FAILURE;
continue;
}
errno = 0;
if (load_module(info->pathname, options) != 0) {
if (EEXIST != errno) {
bb_error_msg("'%s': %s",
info->pathname,
moderror(errno));
} else {
dbg1_error_msg("'%s': %s",
info->pathname,
moderror(errno));
}
exitcode = EXIT_FAILURE;
}
}
ret:
free(infovec);
free(options);
return exitcode;
}
#undef cmdline_options
/* For reference, module-init-tools v3.4 options:
# insmod
Usage: insmod filename [args]
# rmmod --help
Usage: rmmod [-fhswvV] modulename ...
-f (or --force) forces a module unload, and may crash your
machine. This requires the Forced Module Removal option
when the kernel was compiled.
-h (or --help) prints this help text
-s (or --syslog) says use syslog, not stderr
-v (or --verbose) enables more messages
-V (or --version) prints the version code
-w (or --wait) begins module removal even if it is used
and will stop new users from accessing the module (so it
should eventually fall to zero).
# modprobe
Usage: modprobe [-v] [-V] [-C config-file] [-n] [-i] [-q] [-b]
[-o <modname>] [ --dump-modversions ] <modname> [parameters...]
modprobe -r [-n] [-i] [-v] <modulename> ...
modprobe -l -t <dirname> [ -a <modulename> ...]
# depmod --help
depmod 3.4 -- part of module-init-tools
depmod -[aA] [-n -e -v -q -V -r -u]
[-b basedirectory] [forced_version]
depmod [-n -e -v -q -r -u] [-F kernelsyms] module1.ko module2.ko ...
If no arguments (except options) are given, "depmod -a" is assumed.
depmod will output a dependency list suitable for the modprobe utility.
Options:
-a, --all Probe all modules
-A, --quick Only does the work if there's a new module
-n, --show Write the dependency file on stdout only
-e, --errsyms Report not supplied symbols
-V, --version Print the release version
-v, --verbose Enable verbose mode
-h, --help Print this usage message
The following options are useful for people managing distributions:
-b basedirectory
--basedir basedirectory
Use an image of a module tree
-F kernelsyms
--filesyms kernelsyms
Use the file instead of the current kernel symbols
*/
//usage:#if ENABLE_MODPROBE_SMALL
//usage:#define depmod_trivial_usage NOUSAGE_STR
//usage:#define depmod_full_usage ""
//usage:#define lsmod_trivial_usage
//usage: ""
//usage:#define lsmod_full_usage "\n\n"
//usage: "List the currently loaded kernel modules"
//usage:#define insmod_trivial_usage
//usage: IF_FEATURE_2_4_MODULES("[OPTIONS] MODULE ")
//usage: IF_NOT_FEATURE_2_4_MODULES("FILE ")
//usage: "[SYMBOL=VALUE]..."
//usage:#define insmod_full_usage "\n\n"
//usage: "Load kernel module"
//usage: IF_FEATURE_2_4_MODULES( "\n"
//usage: "\n -f Force module to load into the wrong kernel version"
//usage: "\n -k Make module autoclean-able"
//usage: "\n -v Verbose"
//usage: "\n -q Quiet"
//usage: "\n -L Lock: prevent simultaneous loads"
//usage: IF_FEATURE_INSMOD_LOAD_MAP(
//usage: "\n -m Output load map to stdout"
//usage: )
//usage: "\n -x Don't export externs"
//usage: )
//usage:#define rmmod_trivial_usage
//usage: "[-wfa] [MODULE]..."
//usage:#define rmmod_full_usage "\n\n"
//usage: "Unload kernel modules\n"
//usage: "\n -w Wait until the module is no longer used"
//usage: "\n -f Force unload"
//usage: "\n -a Remove all unused modules (recursively)"
//usage:
//usage:#define rmmod_example_usage
//usage: "$ rmmod tulip\n"
//usage:#define modprobe_trivial_usage
//usage: "[-qfwrsv] MODULE [SYMBOL=VALUE]..."
//usage:#define modprobe_full_usage "\n\n"
//usage: " -r Remove MODULE (stacks) or do autoclean"
//usage: "\n -q Quiet"
//usage: "\n -v Verbose"
//usage: "\n -f Force"
//usage: "\n -w Wait for unload"
//usage: "\n -s Report via syslog instead of stderr"
//usage:#endif
int modprobe_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int modprobe_main(int argc UNUSED_PARAM, char **argv)
{
int exitcode;
struct utsname uts;
IF_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE(char *options = NULL;)
/* are we lsmod? -> just dump /proc/modules */
if (is_lsmod) {
xprint_and_close_file(xfopen_for_read("/proc/modules"));
return EXIT_SUCCESS;
}
INIT_G();
/* Prevent ugly corner cases with no modules at all */
modinfo = xzalloc(sizeof(modinfo[0]));
if (!is_insmod) {
/* Goto modules directory */
xchdir(CONFIG_DEFAULT_MODULES_DIR);
}
uname(&uts); /* never fails */
if (is_depmod) {
/* Supported:
* -n: print result to stdout
* -a: process all modules (default)
* optional VERSION parameter
* Ignored:
* -A: do work only if a module is newer than depfile
* -e: report any symbols which a module needs
* which are not supplied by other modules or the kernel
* -F FILE: System.map (symbols for -e)
* -q, -r, -u: noop?
* Not supported:
* -b BASEDIR: (TODO!) modules are in
* $BASEDIR/lib/modules/$VERSION
* -v: human readable deps to stdout
* -V: version (don't want to support it - people may depend
* on it as an indicator of "standard" depmod)
* -h: help (well duh)
* module1.o module2.o parameters (just ignored for now)
*/
getopt32(argv, "na" "AeF:qru" /* "b:vV", NULL */, NULL);
argv += optind;
/* if (argv[0] && argv[1]) bb_show_usage(); */
/* Goto $VERSION directory */
xchdir(argv[0] ? argv[0] : uts.release);
/* Force full module scan by asking to find a bogus module.
* This will generate modules.dep.bb as a side effect. */
process_module((char*)"/", NULL);
return !wrote_dep_bb_ok;
}
#if ENABLE_MODPROBE || ENABLE_INSMOD || ENABLE_RMMOD
/* modprobe, insmod, rmmod require at least one argument */
opt_complementary = "-1";
/* only -q (quiet) and -r (rmmod),
* the rest are accepted and ignored (compat) */
getopt32(argv, "qrfsvwb");
argv += optind;
if (!is_insmod) {
/* Goto $VERSION directory */
xchdir(uts.release);
}
/* are we rmmod? -> simulate modprobe -r, but don't bother the flag if
* there're no other applets here */
if (is_rmmod) {
if (!ONLY_APPLET)
option_mask32 |= OPT_r;
} else if (!ENABLE_MODPROBE || !(option_mask32 & OPT_r)) {
# if ENABLE_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE
/* If not rmmod/-r, parse possible module options given on command line.
* insmod/modprobe takes one module name, the rest are parameters. */
char **arg = argv;
while (*++arg) {
/* Enclose options in quotes */
char *s = options;
options = xasprintf("%s \"%s\"", s ? s : "", *arg);
free(s);
*arg = NULL;
}
# else
argv[1] = NULL;
# endif
}
if (is_insmod) {
size_t len;
void *map;
len = MAXINT(ssize_t);
map = xmalloc_open_zipped_read_close(*argv, &len);
if (!map)
bb_perror_msg_and_die("can't read '%s'", *argv);
if (init_module(map, len,
(IF_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE(options ? options : ) "")
) != 0
) {
bb_error_msg_and_die("can't insert '%s': %s",
*argv, moderror(errno));
}
return EXIT_SUCCESS;
}
/* Try to load modprobe.dep.bb */
if (!is_rmmod) {
load_dep_bb();
}
/* Load/remove modules.
* Only rmmod/modprobe -r loops here, insmod/modprobe has only argv[0] */
exitcode = EXIT_SUCCESS;
do {
exitcode |= process_module(*argv, options);
} while (*++argv);
if (ENABLE_FEATURE_CLEAN_UP) {
IF_FEATURE_MODPROBE_SMALL_OPTIONS_ON_CMDLINE(free(options);)
}
return exitcode;
#endif /* MODPROBE || INSMOD || RMMOD */
}