From: Lucas De Marchi Date: Sun, 29 Jan 2012 03:49:09 +0000 (-0200) Subject: libkmod-module: probe: change insertion to cover more use cases X-Git-Tag: v5~42 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b1a51256a9ed581cac9a3887ddb6aa316e8dbcd5;p=thirdparty%2Fkmod.git libkmod-module: probe: change insertion to cover more use cases Split kmod_module_probe_insert_module() in 2: 1) Get list of modules to be loaded 2) Iterate the list, loading the module With this in future we will be able to cover use cases of modprobe, that has a logic a bit more complicated. With this we also change the logic to detect dependency loops: instead of checking the recursion every STEP times, we now keep a field in kmod_module, marking it as visited. We simply ignore already visited modules and thus we break loops. --- diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c index e4adcc64..d81725f9 100644 --- a/libkmod/libkmod-module.c +++ b/libkmod/libkmod-module.c @@ -68,6 +68,11 @@ struct kmod_module { bool install_commands : 1; bool remove_commands : 1; } init; + + /* + * private field used by kmod_module_get_probe_list() to detect + * dependency loops + */ bool visited : 1; }; @@ -834,26 +839,6 @@ static bool module_is_blacklisted(struct kmod_module *mod) 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) { @@ -876,6 +861,11 @@ static int command_do(struct kmod_module *mod, const char *type, return err; } +struct probe_insert_cb { + int (*run_install)(struct kmod_module *m, const char *cmd, void *data); + void *data; +}; + static int module_do_install_commands(struct kmod_module *mod, const char *options, struct probe_insert_cb *cb) @@ -928,69 +918,6 @@ static int module_do_install_commands(struct kmod_module *mod, 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 @@ -1017,90 +944,108 @@ static char *module_options_concat(const char *opt, const char *xopt) 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) +static int __kmod_module_get_probe_list(struct kmod_module *mod, + struct kmod_list **list); + +/* re-entrant */ +static int __kmod_module_fill_softdep(struct kmod_module *mod, + struct kmod_list **list) { + struct kmod_list *pre = NULL, *post = NULL, *l; 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; + err = kmod_module_get_softdeps(mod, &pre, &post); + if (err < 0) { + ERR(mod->ctx, "could not get softdep: %s", strerror(-err)); + goto fail; } - 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); + kmod_list_foreach(l, pre) { + struct kmod_module *m = l->data; + err = __kmod_module_get_probe_list(m, list); + if (err < 0) + goto fail; + } - if (state == KMOD_MODULE_LIVE || - state == KMOD_MODULE_COMING || - state == KMOD_MODULE_BUILTIN) - return 0; + l = kmod_list_append(*list, kmod_module_ref(mod)); + if (l == NULL) { + kmod_module_unref(mod); + err = -ENOMEM; + goto fail; } + *list = l; + mod->visited = true; - err = kmod_module_get_softdeps(mod, &pre, &post); - if (err < 0) - return err; + kmod_list_foreach(l, post) { + struct kmod_module *m = l->data; + err = __kmod_module_get_probe_list(m, list); + if (err < 0) + goto fail; + } - err = module_do_insmod_dep(pre, flags, cb, rec, reccount); - RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish); +fail: + kmod_module_unref_list(pre); + kmod_module_unref_list(post); - dep = module_get_dependencies_noref(mod); - err = module_do_insmod_dep(dep, flags, cb, rec, reccount); - RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish); + return err; +} - options = module_options_concat(kmod_module_get_options(mod), - extra_options); +/* re-entrant */ +static int __kmod_module_get_probe_list(struct kmod_module *mod, + struct kmod_list **list) +{ + struct kmod_list *dep, *l; + int err = 0; - if (install_cmds != NULL) - err = module_do_install_commands(mod, options, cb); - else - err = kmod_module_insert_module(mod, flags, options); + if (mod->visited) { + DBG(mod->ctx, "Ignore module '%s': already visited\n", + mod->name); + return 0; + } - free(options); + dep = kmod_module_get_dependencies(mod); /* - * Ignore "already loaded" error. We need to check here because of - * race conditions. We checked first if module was already loaded but - * it may have been loaded between the check and the moment we try to - * insert it. + * Use its softdeps and commands: just put it in the end of the list */ - if (err < 0 && err != -EEXIST && (flags & KMOD_PROBE_STOP_ON_FAILURE)) + l = kmod_list_append(dep, kmod_module_ref(mod)); + if (l == NULL) { + kmod_module_unref(mod); + err = -ENOMEM; goto finish; + } + dep = l; - err = module_do_insmod_dep(post, flags, cb, rec, reccount); + kmod_list_foreach(l, dep) { + struct kmod_module *m = l->data; + err = __kmod_module_fill_softdep(m, list); + if (err < 0) + break; + } finish: - kmod_module_unref_list(pre); - kmod_module_unref_list(post); + kmod_module_unref_list(dep); + return err; +} + +static int kmod_module_get_probe_list(struct kmod_module *mod, + struct kmod_list **list) +{ + int err; + + assert(mod != NULL); + assert(list != NULL && *list == NULL); + + /* + * Make sure we don't get screwed by previous calls to this function + */ + kmod_set_modules_visited(mod->ctx, false); + + err = __kmod_module_get_probe_list(mod, list); + if (err < 0) { + kmod_module_unref_list(*list); + *list = NULL; + } return err; } @@ -1110,19 +1055,22 @@ finish: * @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. + * @extra_options: module's options to pass to Linux Kernel. It applies only + * to @mod, not to its dependencies. + * @run_install: function to run when @mod is backed by an install command. * @data: data to give back to @run_install callback * - * Insert a module in Linux kernel resolving dependencies, soft dependencies + * 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. + * given, this function will fork and exec by calling system(3). Don't pass a + * NULL argument in @run_install if your binary is setuid/setgid (see warning + * in system(3)). If you need control over the execution of an install + * command, give a callback function instead. * - * Returns: 0 on success or < 0 on failure. + * Returns: 0 on success, > 0 if stopped by a reason given in @flags or < 0 on + * failure. */ KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod, unsigned int flags, const char *extra_options, @@ -1130,18 +1078,88 @@ KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod, const char *cmd, void *data), const void *data) { + struct kmod_list *list = NULL, *l; struct probe_insert_cb cb; + int err; + + if (mod == NULL) + return -ENOENT; + + err = flags & (KMOD_PROBE_APPLY_BLACKLIST | + KMOD_PROBE_APPLY_BLACKLIST_ALL); + if (err != 0) { + if (module_is_blacklisted(mod)) + return err; + } + + err = kmod_module_get_probe_list(mod, &list); + if (err < 0) + return err; + + if (flags & KMOD_PROBE_APPLY_BLACKLIST_ALL) { + struct kmod_list *filtered = NULL; + + err = kmod_module_get_filtered_blacklist(mod->ctx, + list, &filtered); + if (err < 0) + return err; + + kmod_module_unref_list(list); + if (filtered == NULL) + return KMOD_PROBE_APPLY_BLACKLIST_ALL; + + list = filtered; + } cb.run_install = run_install; cb.data = (void *) data; - return module_probe_insert_module(mod, flags, extra_options, &cb, - NULL, 0); -} + kmod_list_foreach(l, list) { + struct kmod_module *m = l->data; + const char *moptions = kmod_module_get_options(m); + const char *cmd = kmod_module_get_install_commands(m); + char *options = module_options_concat(moptions, + m == mod ? extra_options : NULL); + + if (cmd != NULL) { + if (flags & KMOD_PROBE_STOP_ON_COMMAND) { + DBG(mod->ctx, "Stopping on '%s': " + "install command\n", m->name); + err = KMOD_PROBE_STOP_ON_COMMAND; + free(options); + break; + } + err = module_do_install_commands(m, options, &cb); + } else { + int state = kmod_module_get_initstate(m); + + if (state == KMOD_MODULE_LIVE || + state == KMOD_MODULE_COMING || + state == KMOD_MODULE_BUILTIN) { + DBG(mod->ctx, "Ignoring '%s': " + "module already loaded\n", m->name); + free(options); + continue; + } + err = kmod_module_insert_module(m, flags, options); + } -#undef RECURSION_CHECK_STEP -#undef RET_CHECK_NOLOOP_OR_FAIL + free(options); + /* + * Ignore "already loaded" error. We need to check here + * because of race conditions. We checked first if module was + * already loaded but it may have been loaded between the + * check and the moment we try to insert it. + */ + if (err < 0 && err != -EEXIST && + (flags & KMOD_PROBE_STOP_ON_DEP_FAILURE)) + break; + } + + kmod_module_unref_list(list); + return err; +} /** * kmod_module_get_options: diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h index b084011c..afc1c171 100644 --- a/libkmod/libkmod.h +++ b/libkmod/libkmod.h @@ -125,11 +125,14 @@ enum kmod_insert { /* 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_PROBE_FORCE_VERMAGIC = 0x01, + KMOD_PROBE_FORCE_MODVERSION = 0x02, + + /* codes below can be used in return value, too */ + KMOD_PROBE_APPLY_BLACKLIST_ALL = 0x04, + KMOD_PROBE_APPLY_BLACKLIST = 0x08, + KMOD_PROBE_STOP_ON_DEP_FAILURE = 0x0F, + KMOD_PROBE_STOP_ON_COMMAND = 0x10, }; /*