From: Lucas De Marchi Date: Tue, 27 Dec 2011 13:40:10 +0000 (-0200) Subject: Add implementation of modprobe's insertion X-Git-Tag: v3~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ddbda02286f3b8fa58fb587403f2f9f0e9b4a147;p=thirdparty%2Fkmod.git 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. --- diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c index 9ad3f827..5e7374f7 100644 --- a/libkmod/libkmod-module.c +++ b/libkmod/libkmod-module.c @@ -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'; diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h index ab470956..9f48cf02 100644 --- a/libkmod/libkmod.h +++ b/libkmod/libkmod.h @@ -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); diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym index 1fd755b8..f5bebf90 100644 --- a/libkmod/libkmod.sym +++ b/libkmod/libkmod.sym @@ -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;