Add implementation of modprobe's insertion

Treat module insertion as modprobe does: look for (soft-)dependencies, run
install commands, apply blacklist.

The difference with the blacklist is that it's applied to all modules,
including the dependencies. If you want to apply a blacklist only on the
module it's better to call the filter function by yourself.

This implementation detects loops caused by poorly written
soft-dependencies and fail gracefully, printing the loop to the log.
This commit is contained in:
Lucas De Marchi 2011-12-27 11:40:10 -02:00
parent 2bd7cbf644
commit ddbda02286
3 changed files with 337 additions and 0 deletions

View File

@ -777,6 +777,325 @@ elf_failed:
return err;
}
static bool module_is_blacklisted(struct kmod_module *mod)
{
struct kmod_ctx *ctx = mod->ctx;
const struct kmod_list *bl = kmod_get_blacklists(ctx);
const struct kmod_list *l;
kmod_list_foreach(l, bl) {
const char *modname = kmod_blacklist_get_modname(l);
if (streq(modname, mod->name))
return true;
}
return false;
}
#define RECURSION_CHECK_STEP 10
#define RET_CHECK_NOLOOP_OR_FAIL(_ret, _flags, _label) \
do { \
if (_ret < 0) { \
if (_ret == -ELOOP || _ret == -ENOMEM \
|| (_flags & KMOD_PROBE_STOP_ON_FAILURE)) \
goto _label; \
} \
} while (0)
struct probe_insert_cb {
int (*run_install)(struct kmod_module *m, const char *cmd, void *data);
void *data;
};
int module_probe_insert_module(struct kmod_module *mod,
unsigned int flags, const char *extra_options,
struct probe_insert_cb *cb,
struct kmod_list *rec, unsigned int reccount);
static int command_do(struct kmod_module *mod, const char *type,
const char *cmd)
{
const char *modname = kmod_module_get_name(mod);
int err;
DBG(mod->ctx, "%s %s\n", type, cmd);
setenv("MODPROBE_MODULE", modname, 1);
err = system(cmd);
unsetenv("MODPROBE_MODULE");
if (err == -1 || WEXITSTATUS(err)) {
ERR(mod->ctx, "Error running %s command for %s\n",
type, modname);
if (err != -1)
err = -WEXITSTATUS(err);
}
return err;
}
static int module_do_install_commands(struct kmod_module *mod,
const char *options,
struct probe_insert_cb *cb)
{
const char *command = kmod_module_get_install_commands(mod);
char *p, *cmd;
int err;
size_t cmdlen, options_len, varlen;
assert(command);
if (options == NULL)
options = "";
options_len = strlen(options);
cmdlen = strlen(command);
varlen = sizeof("$CMDLINE_OPTS") - 1;
cmd = memdup(command, cmdlen + 1);
if (cmd == NULL)
return -ENOMEM;
while ((p = strstr(cmd, "$CMDLINE_OPTS")) != NULL) {
size_t prefixlen = p - cmd;
size_t suffixlen = cmdlen - prefixlen - varlen;
size_t slen = cmdlen - varlen + options_len;
char *suffix = p + varlen;
char *s = malloc(slen + 1);
if (s == NULL) {
free(cmd);
return -ENOMEM;
}
memcpy(s, cmd, p - cmd);
memcpy(s + prefixlen, options, options_len);
memcpy(s + prefixlen + options_len, suffix, suffixlen);
s[slen] = '\0';
free(cmd);
cmd = s;
cmdlen = slen;
}
if (cb->run_install != NULL)
err = cb->run_install(mod, cmd, cb->data);
else
err = command_do(mod, "install", cmd);
free(cmd);
return err;
}
static bool module_dep_has_loop(const struct kmod_list *deps,
struct kmod_list *rec,
unsigned int reccount)
{
struct kmod_list *l;
struct kmod_module *mod;
if (reccount < RECURSION_CHECK_STEP || deps == NULL)
return false;
mod = deps->data;
reccount = 0;
kmod_list_foreach(l, rec) {
struct kmod_list *loop;
if (l->data != mod)
continue;
ERR(mod->ctx, "Dependency loop detected while inserting '%s'. Operation aborted\n",
mod->name);
for (loop = l; loop != NULL;
loop = kmod_list_next(rec, loop)) {
struct kmod_module *m = loop->data;
ERR(mod->ctx, "%s\n", m->name);
}
return true;
}
return false;
}
static int module_do_insmod_dep(const struct kmod_list *deps,
unsigned int flags, struct probe_insert_cb *cb,
struct kmod_list *rec, unsigned int reccount)
{
const struct kmod_list *d;
int err = 0;
if (module_dep_has_loop(deps, rec, reccount))
return -ELOOP;
kmod_list_foreach(d, deps) {
struct kmod_module *dm = d->data;
struct kmod_list *tmp;
tmp = kmod_list_append(rec, dm);
if (tmp == NULL)
return -ENOMEM;
rec = tmp;
err = module_probe_insert_module(dm, flags, NULL, cb,
rec, reccount + 1);
rec = kmod_list_remove_n_latest(rec, 1);
RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish);
}
finish:
return err;
}
static char *module_options_concat(const char *opt, const char *xopt)
{
// TODO: we might need to check if xopt overrides options on opt
size_t optlen = opt == NULL ? 0 : strlen(opt);
size_t xoptlen = xopt == NULL ? 0 : strlen(xopt);
char *r;
if (optlen == 0 && xoptlen == 0)
return NULL;
r = malloc(optlen + xoptlen + 2);
if (opt != NULL) {
memcpy(r, opt, optlen);
r[optlen] = ' ';
optlen++;
}
if (xopt != NULL)
memcpy(r + optlen, xopt, xoptlen);
r[optlen + xoptlen] = '\0';
return r;
}
/*
* Do the probe_insert work recursively. We traverse the dependencies in
* depth-first order, checking the following conditions:
*
* - Is blacklisted?
* - Is install command?
* - Is already loaded?
*
* Then we insert the modules (calling module_do_insmod_dep(), which will
* re-enter this function) needed to load @mod in the following order:
*
* 1) pre-softdep
* 2) dependency
* 3) @mod
* 4) post-softdep
*/
int module_probe_insert_module(struct kmod_module *mod,
unsigned int flags, const char *extra_options,
struct probe_insert_cb *cb,
struct kmod_list *rec, unsigned int reccount)
{
int err;
const char *install_cmds;
const struct kmod_list *dep;
struct kmod_list *pre = NULL, *post = NULL;
char *options;
if ((flags & KMOD_PROBE_STOP_ON_BLACKLIST)
&& module_is_blacklisted(mod)) {
DBG(mod->ctx, "Stopping on '%s': blacklisted\n", mod->name);
return -EINVAL;
}
install_cmds = kmod_module_get_install_commands(mod);
if (install_cmds != NULL) {
if (flags & KMOD_PROBE_STOP_ON_COMMAND) {
DBG(mod->ctx, "Stopping on '%s': install command\n",
mod->name);
return -EINVAL;
}
} else {
int state = kmod_module_get_initstate(mod);
if (state == KMOD_MODULE_LIVE ||
state == KMOD_MODULE_COMING ||
state == KMOD_MODULE_BUILTIN)
return 0;
}
err = kmod_module_get_softdeps(mod, &pre, &post);
if (err < 0)
return err;
err = module_do_insmod_dep(pre, flags, cb, rec, reccount);
RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish);
dep = module_get_dependencies_noref(mod);
err = module_do_insmod_dep(dep, flags, cb, rec, reccount);
RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish);
options = module_options_concat(kmod_module_get_options(mod),
extra_options);
if (install_cmds != NULL)
err = module_do_install_commands(mod, options, cb);
else
err = kmod_module_insert_module(mod, flags, options);
free(options);
if (err < 0 && (flags & KMOD_PROBE_STOP_ON_FAILURE))
return err;
err = module_do_insmod_dep(post, flags, cb, rec, reccount);
finish:
kmod_module_unref_list(pre);
kmod_module_unref_list(post);
return err;
}
/**
* kmod_module_probe_insert_module:
* @mod: kmod module
* @flags: flags are not passed to Linux Kernel, but instead they dictate the
* behavior of this function.
* @extra_options: module's options to pass to Linux Kernel.
* @run_install: function to run when @mod is backed by a install command.
* @data: data to give back to @run_install callback
*
* Insert a module in Linux kernel resolving dependencies, soft dependencies
* install commands and applying blacklist.
*
* If @run_install is NULL, and the flag KMOD_PROBE_STOP_ON_COMMANDS is not
* given, this function will fork and exec by calling system(3). If you need
* control over the execution of an install command, give a callback function
* in @run_install.
*
* Returns: 0 on success or < 0 on failure.
*/
KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod,
unsigned int flags, const char *extra_options,
int (*run_install)(struct kmod_module *m,
const char *cmd, void *data),
const void *data)
{
struct probe_insert_cb cb;
cb.run_install = run_install;
cb.data = (void *) data;
return module_probe_insert_module(mod, flags, extra_options, &cb,
NULL, 0);
}
#undef RECURSION_CHECK_STEP
#undef RET_CHECK_NOLOOP_OR_FAIL
/**
* kmod_module_get_options:
* @mod: kmod module
@ -1149,6 +1468,9 @@ KMOD_EXPORT int kmod_module_get_initstate(const struct kmod_module *mod)
if (fd < 0) {
err = -errno;
DBG(mod->ctx, "could not open '%s': %s\n",
path, strerror(-err));
if (pathlen > (int)sizeof("/initstate") - 1) {
struct stat st;
path[pathlen - (sizeof("/initstate") - 1)] = '\0';

View File

@ -86,6 +86,15 @@ enum kmod_insert {
KMOD_INSERT_FORCE_MODVERSION = 0x2,
};
/* Flags to kmod_module_probe_insert_module() */
enum kmod_probe {
KMOD_PROBE_FORCE_VERMAGIC = 0x1,
KMOD_PROBE_FORCE_MODVERSION = 0x2,
KMOD_PROBE_STOP_ON_BLACKLIST = 0x4,
KMOD_PROBE_STOP_ON_FAILURE = 0x8,
KMOD_PROBE_STOP_ON_COMMAND = 0x16,
};
/*
* kmod_module
*
@ -110,6 +119,10 @@ int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx, const struct
int kmod_module_remove_module(struct kmod_module *mod, unsigned int flags);
int kmod_module_insert_module(struct kmod_module *mod, unsigned int flags, const char *options);
int kmod_module_probe_insert_module(struct kmod_module *mod,
unsigned int flags, const char *options,
int (*run_install)(struct kmod_module *m, const char *cmdline, void *data),
const void *data);
const char *kmod_module_get_name(const struct kmod_module *mod);
const char *kmod_module_get_path(const struct kmod_module *mod);

View File

@ -76,4 +76,6 @@ global:
kmod_module_dependency_symbol_get_crc;
kmod_module_dependency_symbol_get_bind;
kmod_module_dependency_symbols_free_list;
kmod_module_probe_insert_module;
} LIBKMOD_2;