From: Gustavo Sverzut Barbieri Date: Sun, 11 Dec 2011 21:36:18 +0000 (-0200) Subject: tools: add modprobe X-Git-Tag: v1~55^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3d0a5f2ef7b9eba319ddb97ace2105000f10b07;p=thirdparty%2Fkmod.git tools: add modprobe try to mimic original module-init-tools' modprobe as much as possible, but this exposed some missing features in libkmod, these are now listed in TODO. --- diff --git a/Makefile.am b/Makefile.am index f6520cc4..db51ae8b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -49,11 +49,13 @@ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libkmod/libkmod.pc if BUILD_TOOLS -bin_PROGRAMS = tools/kmod-insmod tools/kmod-rmmod tools/kmod-lsmod +bin_PROGRAMS = tools/kmod-insmod tools/kmod-rmmod tools/kmod-lsmod \ + tools/kmod-modprobe tools_kmod_insmod_LDADD = libkmod/libkmod.la tools_kmod_rmmod_LDADD = libkmod/libkmod.la tools_kmod_lsmod_LDADD = libkmod/libkmod.la +tools_kmod_modprobe_LDADD = libkmod/libkmod.la endif TESTS = test/test-init test/test-loaded diff --git a/TODO b/TODO index a991086d..47fca61e 100644 --- a/TODO +++ b/TODO @@ -14,5 +14,17 @@ Features: binary, and it's sufficient to return only the lists from lookups. ^-- investigate the best API -* create test-mock library to be LD_PRELOA'ed before running the binaries +* create test-mock library to be LD_PRELOAD'ed before running the binaries so we're able to create unit tests + +* provide ELF manipulation to implement modinfo + +* provide 1:1 compatibility with module-init-tools's modprobe, missing: + - parse options from kernel command line (modname.opt=val) + - provide softdeps + - show modversions (needs elf manipulation) + - show config (list all known options, install, remove, softdep...) + - show list (lists all modules known by modules.dep) + - return install/remove commands for non-modules: + install nonexistentmodule somecommand + modprobe nonexistentmodule -> runs somecommand diff --git a/tools/.gitignore b/tools/.gitignore index efd73dc2..807d6458 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -2,3 +2,4 @@ kmod-insmod kmod-rmmod kmod-lsmod +kmod-modprobe diff --git a/tools/kmod-modprobe.c b/tools/kmod-modprobe.c new file mode 100644 index 00000000..7b78e5ba --- /dev/null +++ b/tools/kmod-modprobe.c @@ -0,0 +1,1150 @@ +/* + * kmod-modprob - manage linux kernel modules using libkmod. + * + * Copyright (C) 2011 ProFUSION embedded systems + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libkmod.h" + +static int log_priority = LOG_CRIT; +static int use_syslog = 0; + +#define DEFAULT_VERBOSE LOG_WARNING +static int verbose = DEFAULT_VERBOSE; +static int dry_run = 0; +static int ignore_loaded = 0; +static int show_resolved_aliases = 0; +static int first_time = 0; +static int ignore_commands = 0; +static int use_blacklist = 0; +static int force = 0; +static int strip_modversion = 0; +static int strip_vermagic = 0; +static int remove_dependencies = 0; + +static const char cmdopts_s[] = "arRibft:lDcnC:d:S:sqvVh"; +static const struct option cmdopts[] = { + {"all", no_argument, 0, 'a'}, + {"remove", no_argument, 0, 'r'}, + {"remove-dependencies", no_argument, 0, 5}, + {"resolve-alias", no_argument, 0, 'R'}, + {"first-time", no_argument, 0, 3}, + {"ignore-install", no_argument, 0, 'i'}, + {"ignore-remove", no_argument, 0, 'i'}, + {"use-blacklist", no_argument, 0, 'b'}, + {"force", no_argument, 0, 'f'}, + {"force-modversion", no_argument, 0, 2}, + {"force-vermagic", no_argument, 0, 1}, + + {"type", required_argument, 0, 't'}, + {"list", no_argument, 0, 'l'}, + {"show-depends", no_argument, 0, 'D'}, + {"showconfig", no_argument, 0, 'c'}, + {"show-config", no_argument, 0, 'c'}, + {"show-modversions", no_argument, 0, 4}, + {"dump-modversions", no_argument, 0, 4}, + + {"dry-run", no_argument, 0, 'n'}, + {"show", no_argument, 0, 'n'}, + + {"config", required_argument, 0, 'C'}, + {"dirname", required_argument, 0, 'd'}, + {"set-version", required_argument, 0, 'S'}, + + {"syslog", no_argument, 0, 's'}, + {"quiet", no_argument, 0, 'q'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {NULL, 0, 0, 0} +}; + +static void help(const char *progname) +{ + fprintf(stderr, + "Usage:\n" + "\t%s [options] [-i] [-b] modulename\n" + "\t%s [options] -a [-i] [-b] modulename [modulename...]\n" + "\t%s [options] -r [-i] modulename\n" + "\t%s [options] -r -a [-i] modulename [modulename...]\n" + "\t%s [options] -l [-t dirname] [wildcard]\n" + "\t%s [options] -c\n" + "\t%s [options] --dump-modversions filename\n" + "Management Options:\n" + "\t-a, --all \n" + "\t-r, --remove \n" + "\t --remove-dependencies \n" + "\t-R, --resolve-alias \n" + "\t --first-time \n" + "\t-i, --ignore-install \n" + "\t-i, --ignore-remove \n" + "\t-b, --use-blacklist \n" + "\t-f, --force \n" + "\t --force-modversion \n" + "\t --force-vermagic \n" + "\n" + "Query Options:\n" + "\t-t, --type=DIR \n" + "\t-l, --list \n" + "\t-D, --show-depends \n" + "\t-c, --showconfig \n" + "\t-c, --show-config \n" + "\t --show-modversions \n" + "\t --dump-modversions \n" + "\n" + "General Options:\n" + "\t-n, --dry-run \n" + "\t-n, --show \n" + + "\t-C, --config=FILE \n" + "\t-d, --dirname=DIR \n" + "\t-S, --set-version=VERSION \n" + + "\t-s, --syslog print to syslog, not stderr\n" + "\t-q, --quiet disable messages\n" + "\t-v, --verbose enables more messages\n" + "\t-V, --version show version\n" + "\t-h, --help show this help\n", + progname, progname, progname, progname, progname, progname, + progname); +} + +static inline void _show(const char *fmt, ...) +{ + va_list args; + + if (verbose <= DEFAULT_VERBOSE) + return; + + va_start(args, fmt); + vfprintf(stdout, fmt, args); + fflush(stdout); + va_end(args); +} + +static inline void _log(int prio, const char *fmt, ...) +{ + const char *prioname; + char buf[32], *msg; + va_list args; + + if (prio > verbose) + return; + + va_start(args, fmt); + if (vasprintf(&msg, fmt, args) < 0) + msg = NULL; + va_end(args); + if (msg == NULL) + return; + + switch (prio) { + case LOG_CRIT: + prioname = "FATAL"; + break; + case LOG_ERR: + prioname = "ERROR"; + break; + case LOG_WARNING: + prioname = "WARNING"; + break; + case LOG_NOTICE: + prioname = "NOTICE"; + break; + case LOG_INFO: + prioname = "INFO"; + break; + case LOG_DEBUG: + prioname = "DEBUG"; + break; + default: + snprintf(buf, sizeof(buf), "LOG-%03d", prio); + prioname = buf; + } + + if (use_syslog) + syslog(LOG_NOTICE, "%s: %s", prioname, msg); + else + fprintf(stderr, "%s: %s", prioname, msg); + free(msg); + + if (prio <= LOG_CRIT) + exit(EXIT_FAILURE); +} +#define ERR(...) _log(LOG_ERR, __VA_ARGS__) +#define WRN(...) _log(LOG_WARNING, __VA_ARGS__) +#define INF(...) _log(LOG_INFO, __VA_ARGS__) +#define DBG(...) _log(LOG_DEBUG, __VA_ARGS__) +#define LOG(...) _log(log_priority, __VA_ARGS__) +#define SHOW(...) _show(__VA_ARGS__) + +static int show_list(struct kmod_ctx *ctx, const char *list_type, const char *pattern) +{ + ERR("TODO - list is missing in kmod.\n"); + /* + needs: + struct kmod_list *kmod_get_dependencies(struct kmod_ctx *ctx); + kmod_dependency_get_name() + kmod_dependency_get_dependencies() + kmod_dependency_unref_list() + */ + return -ENOENT; +} + +static int show_config(struct kmod_ctx *ctx) +{ + ERR("TODO - config is missing in kmod.\n"); + /* + needs: + struct kmod_list *kmod_get_config(struct kmod_ctx *ctx); + kmod_config_get_type() {alias,options,blacklist,install,remove,softdeps} + kmod_config_get_key() + kmod_config_get_value() + kmod_config_unref_list() + */ + return -ENOENT; +} + +static int show_modversions(struct kmod_ctx *ctx, const char *filename) +{ + ERR("TODO - modversions is missing in kmod.\n"); + /* + needs: + struct kmod_list *kmod_module_get_modversions(struct kmod_module *mod); + kmod_module_modversion_get_address() [needs better naming?] + kmod_module_modversion_get_name() [needs better naming?] + kmod_module_modversion_unref_list() + */ + return -ENOENT; +} + +static int command_do(struct kmod_module *module, const char *type, const char *command, const char *cmdline_opts) +{ + const char *modname = kmod_module_get_name(module); + char *p, *cmd = NULL; + size_t cmdlen, cmdline_opts_len, varlen; + int ret = 0; + + if (cmdline_opts == NULL) + cmdline_opts = ""; + cmdline_opts_len = strlen(cmdline_opts); + + cmd = strdup(command); + if (cmd == NULL) + return -ENOMEM; + cmdlen = strlen(cmd); + varlen = sizeof("$CMDLINE_OPTS") - 1; + while ((p = strstr(cmd, "$CMDLINE_OPTS")) != NULL) { + size_t prefixlen = p - cmd; + size_t suffixlen = cmdlen - prefixlen - varlen; + size_t slen = cmdlen - varlen + cmdline_opts_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, cmdline_opts, cmdline_opts_len); + memcpy(s + prefixlen + cmdline_opts_len, suffix, suffixlen); + s[slen] = '\0'; + + free(cmd); + cmd = s; + cmdlen = slen; + } + + SHOW("%s %s\n", type, cmd); + if (dry_run) + goto end; + + setenv("MODPROBE_MODULE", modname, 1); + ret = system(cmd); + unsetenv("MODPROBE_MODULE"); + if (ret == -1 || WEXITSTATUS(ret)) { + LOG("Error running %s command for %s\n", type, modname); + if (ret != -1) + ret = -WEXITSTATUS(ret); + } + +end: + free(cmd); + return ret; +} + +static int rmmod_do_soft_dependencies(struct kmod_module *mod) +{ + ERR("TODO - implement soft dependencies!\n"); + /* remember to reverse soft dependencies! */ + return 0; +} + +static int rmmod_do_dependencies(struct kmod_module *parent) +{ + int err = 0; + struct kmod_list *d, *deps = kmod_module_get_holders(parent); + kmod_list_foreach(d, deps) { + struct kmod_module *dm = kmod_module_get_module(d); + const char *cmd, *dmname = kmod_module_get_name(dm); + int r; + + r = rmmod_do_dependencies(dm); + if (r < 0) { + WRN("could not remove dependencies of '%s': %s\n", + dmname, strerror(-r)); + goto dep_error; + } + + if (!ignore_loaded) { + int state = kmod_module_get_initstate(dm); + if (state != KMOD_MODULE_LIVE && + state != KMOD_MODULE_COMING) + goto dep_done; + } + + r = rmmod_do_soft_dependencies(dm); + if (r < 0) { + WRN("could not remove soft dependencies of '%s': %s\n", + dmname, strerror(-r)); + goto dep_error; + } + + cmd = kmod_module_get_remove_commands(dm); + if (cmd) { + r = command_do(dm, "remove", cmd, NULL); + if (r < 0) { + WRN("failed to execute remove command of '%s': " + "%s\n", dmname, strerror(-r)); + goto dep_error; + } else + goto dep_done; + } + + r = kmod_module_get_refcnt(dm); + if (r < 0) { + WRN("could not get module '%s' refcnt: %s\n", + dmname, strerror(-r)); + goto dep_error; + } else if (r > 0 && !ignore_loaded) { + LOG("Module %s is in use.\n", dmname); + r = -EBUSY; + goto dep_error; + } + + SHOW("rmmod %s\n", dmname); + + if (!dry_run) { + r = kmod_module_remove_module(dm, 0); + if (r < 0) { + WRN("could not remove '%s': %s\n", + dmname, strerror(-r)); + goto dep_error; + } + } + + dep_done: + kmod_module_unref(dm); + continue; + dep_error: + err = r; + kmod_module_unref(dm); + continue; + } + kmod_module_unref_list(deps); + return err; +} + +static int rmmod_do(struct kmod_module *mod) +{ + const char *modname = kmod_module_get_name(mod); + int err; + + if (!ignore_loaded) { + int state = kmod_module_get_initstate(mod); + if (state == KMOD_MODULE_BUILTIN) { + LOG("Module %s is builtin.\n", modname); + return -ENOENT; + } else if (state != KMOD_MODULE_LIVE) { + if (first_time) { + LOG("Module %s is not in kernel.\n", modname); + return -ENOENT; + } else + return 0; + } + } + + /* not in original modprobe -r, but helpful */ + if (remove_dependencies) { + err = rmmod_do_dependencies(mod); + if (err < 0) + return err; + } + + if (!ignore_commands) { + const char *cmd; + + err = rmmod_do_soft_dependencies(mod); + if (err < 0) + return err; + + cmd = kmod_module_get_remove_commands(mod); + if (cmd) + return command_do(mod, "remove", cmd, NULL); + } + + if (!ignore_loaded) { + int usage = kmod_module_get_refcnt(mod); + if (usage > 0) { + LOG("Module %s is in use.\n", modname); + return -EBUSY; + } + } + + SHOW("rmmod %s\n", modname); + + if (dry_run) + err = 0; + else { + int flags = 0; + + if (force) + flags |= KMOD_REMOVE_FORCE; + + err = kmod_module_remove_module(mod, flags); + if (err == -EEXIST) { + if (!first_time) + err = 0; + else + LOG("Module %s is not in kernel.\n", modname); + } + } + return err; +} + +static int rmmod_path(struct kmod_ctx *ctx, const char *path) +{ + struct kmod_module *mod; + int err; + + err = kmod_module_new_from_path(ctx, path, &mod); + if (err < 0) { + LOG("Module %s not found.\n", path); + return err; + } + err = rmmod_do(mod); + kmod_module_unref(mod); + return err; +} + +static int rmmod_do_commands(struct kmod_ctx *ctx, const char *name) +{ + ERR("TODO - get commands for non modules!\n"); + return -ENOENT; +} + +static int rmmod_alias(struct kmod_ctx *ctx, const char *alias) +{ + struct kmod_list *l, *list = NULL; + int err; + + err = kmod_module_new_from_lookup(ctx, alias, &list); + if (err < 0 || list == NULL) { + if (list == NULL) { + err = rmmod_do_commands(ctx, alias); + if (err < 0) + LOG("Module %s not found.\n", alias); + } + return err; + } + + kmod_list_foreach(l, list) { + struct kmod_module *mod = kmod_module_get_module(l); + err = rmmod_do(mod); + kmod_module_unref(mod); + if (err < 0) + break; + } + + kmod_module_unref_list(list); + return err; +} + +static int rmmod(struct kmod_ctx *ctx, const char *name) +{ + struct stat st; + if (stat(name, &st) == 0) + return rmmod_path(ctx, name); + else + return rmmod_alias(ctx, name); +} + +static int rmmod_all(struct kmod_ctx *ctx, char **args, int nargs) +{ + int i, err = 0; + + for (i = 0; i < nargs; i++) { + int r = rmmod(ctx, args[i]); + if (r < 0) + err = r; + } + + return err; +} + +static int insmod_do_soft_dependencies(struct kmod_module *mod) +{ + ERR("TODO - implement soft dependencies!\n"); + return 0; +} + +static int insmod_do_dependencies(struct kmod_module *parent) +{ + int err = 0; + struct kmod_list *d, *deps = kmod_module_get_dependencies(parent); + kmod_list_foreach(d, deps) { + struct kmod_module *dm = kmod_module_get_module(d); + const char *cmd, *opts, *dmname = kmod_module_get_name(dm); + int r; + + r = insmod_do_dependencies(dm); + if (r < 0) { + WRN("could not insert dependencies of '%s': %s\n", + dmname, strerror(-r)); + goto dep_error; + } + + if (!ignore_loaded) { + int state = kmod_module_get_initstate(dm); + if (state == KMOD_MODULE_LIVE || + state == KMOD_MODULE_COMING || + state == KMOD_MODULE_BUILTIN) + goto dep_done; + } + + r = insmod_do_soft_dependencies(dm); + if (r < 0) { + WRN("could not insert soft dependencies of '%s': %s\n", + dmname, strerror(-r)); + goto dep_error; + } + + cmd = kmod_module_get_install_commands(dm); + if (cmd) { + r = command_do(dm, "install", cmd, NULL); + if (r < 0) { + WRN("failed to execute install command of '%s':" + " %s\n", dmname, strerror(-r)); + goto dep_error; + } else + goto dep_done; + } + + opts = kmod_module_get_options(dm); + SHOW("insmod %s %s\n", + kmod_module_get_path(dm), opts ? opts : ""); + + if (!dry_run) { + int flags = 0; + + if (strip_modversion || force) + flags |= KMOD_INSERT_FORCE_MODVERSION; + if (strip_vermagic || force) + flags |= KMOD_INSERT_FORCE_VERMAGIC; + + r = kmod_module_insert_module(dm, flags, opts); + if (r < 0) { + WRN("could not insert '%s': %s\n", + dmname, strerror(-r)); + goto dep_error; + } + } + + dep_done: + kmod_module_unref(dm); + continue; + dep_error: + err = r; + kmod_module_unref(dm); + continue; + } + kmod_module_unref_list(deps); + return err; +} + +static int insmod_do(struct kmod_module *mod, const char *extra_opts) +{ + const char *modname = kmod_module_get_name(mod); + const char *conf_opts = kmod_module_get_options(mod); + char *opts = NULL; + int err; + + if (!ignore_loaded) { + int state = kmod_module_get_initstate(mod); + + if (state == KMOD_MODULE_BUILTIN) { + if (first_time) { + LOG("Module %s already in kernel (builtin).\n", + modname); + return -EEXIST; + } + return 0; + } else if (state == KMOD_MODULE_LIVE) { + if (first_time) { + LOG("Module %s already in kernel.\n", modname); + return -EEXIST; + } + return 0; + } + } + + err = insmod_do_dependencies(mod); + if (err < 0) + return err; + + if (!ignore_commands) { + const char *cmd; + + err = insmod_do_soft_dependencies(mod); + if (err < 0) + return err; + + cmd = kmod_module_get_install_commands(mod); + if (cmd) + return command_do(mod, "install", cmd, extra_opts); + } + + if (conf_opts || extra_opts) { + if (conf_opts == NULL) + opts = strdup(extra_opts); + else if (extra_opts == NULL) + opts = strdup(conf_opts); + else if (asprintf(&opts, "%s %s", conf_opts, extra_opts) < 0) + opts = NULL; + + if (opts == NULL) + return -ENOMEM; + } + + SHOW("insmod %s %s\n", kmod_module_get_path(mod), opts ? opts : ""); + + if (dry_run) + err = 0; + else { + int flags = 0; + + if (strip_modversion || force) + flags |= KMOD_INSERT_FORCE_MODVERSION; + if (strip_vermagic || force) + flags |= KMOD_INSERT_FORCE_VERMAGIC; + + err = kmod_module_insert_module(mod, flags, opts); + if (err == -EEXIST) { + if (!first_time) + err = 0; + else + ERR("Module %s already in kernel.\n", + kmod_module_get_name(mod)); + } + } + free(opts); + return err; +} + +static int insmod_path(struct kmod_ctx *ctx, const char *path, const char *extra_options) +{ + struct kmod_module *mod; + int err; + + err = kmod_module_new_from_path(ctx, path, &mod); + if (err < 0) { + LOG("Module %s not found.\n", path); + return err; + } + err = insmod_do(mod, extra_options); + kmod_module_unref(mod); + return err; +} + +static int insmod_do_commands(struct kmod_ctx *ctx, const char *name) +{ + ERR("TODO - get commands for non modules!\n"); + return -ENOENT; +} + +static int insmod_alias(struct kmod_ctx *ctx, const char *alias, const char *extra_options) +{ + struct kmod_list *l, *list = NULL; + int err; + + err = kmod_module_new_from_lookup(ctx, alias, &list); + if (err < 0 || list == NULL) { + if (list == NULL) { + err = insmod_do_commands(ctx, alias); + if (err < 0) + LOG("Module %s not found.\n", alias); + } + return err; + } + + if (use_blacklist) { + struct kmod_list *filtered = NULL; + err = kmod_module_get_filtered_blacklist(ctx, list, &filtered); + DBG("using blacklist: input %p, output=%p\n", list, filtered); + kmod_module_unref_list(list); + if (err < 0) { + LOG("Could not filter alias list!\n"); + return err; + } + list = filtered; + } + + kmod_list_foreach(l, list) { + struct kmod_module *mod = kmod_module_get_module(l); + err = insmod_do(mod, extra_options); + kmod_module_unref(mod); + if (err < 0) + break; + } + + kmod_module_unref_list(list); + return err; +} + +static int insmod(struct kmod_ctx *ctx, const char *name, const char *extra_options) +{ + struct stat st; + if (stat(name, &st) == 0) + return insmod_path(ctx, name, extra_options); + else + return insmod_alias(ctx, name, extra_options); +} + +static int insmod_all(struct kmod_ctx *ctx, char **args, int nargs) +{ + int i, err = 0; + + for (i = 0; i < nargs; i++) { + int r = insmod(ctx, args[i], NULL); + if (r < 0) + err = r; + } + + return err; +} + +static void env_modprobe_options_append(const char *value) +{ + const char *old = getenv("MODPROBE_OPTIONS"); + char *env; + + if (old == NULL) { + setenv("MODPROBE_OPTIONS", value, 1); + return; + } + + if (asprintf(&env, "%s %s", old, value) < 0) { + ERR("could not append value to $MODPROBE_OPTIONS\n"); + return; + } + + if (setenv("MODPROBE_OPTIONS", env, 1) < 0) + ERR("could not setenv(MODPROBE_OPTIONS, \"%s\")\n", env); + free(env); +} + +static int options_from_array(char **args, int nargs, char **output) +{ + char *opts = NULL; + size_t optslen = 0; + int i, err = 0; + + for (i = 1; i < nargs; i++) { + size_t len = strlen(args[i]); + size_t qlen = 0; + const char *value; + void *tmp; + + value = strchr(args[i], '='); + if (value) { + value++; + if (*value != '"' && *value != '\'') { + if (strchr(value, ' ')) + qlen = 2; + } + } + + tmp = realloc(opts, optslen + len + qlen + 2); + if (!tmp) { + err = -errno; + free(opts); + opts = NULL; + ERR("could not gather module options: out-of-memory\n"); + break; + } + opts = tmp; + if (optslen > 0) { + opts[optslen] = ' '; + optslen++; + } + if (qlen == 0) { + memcpy(opts + optslen, args[i], len + 1); + optslen += len; + } else { + size_t keylen = value - args[i]; + size_t valuelen = len - keylen; + memcpy(opts + optslen, args[i], keylen); + optslen += keylen; + opts[optslen] = '"'; + optslen++; + memcpy(opts + optslen, value, valuelen); + optslen += valuelen; + opts[optslen] = '"'; + optslen++; + opts[optslen] = '\0'; + } + } + + *output = opts; + return err; +} + +static char **prepend_options_from_env(int *p_argc, char **orig_argv) +{ + const char *p, *env = getenv("MODPROBE_OPTIONS"); + char **new_argv, *str_start, *str_end, *str, *s, *quote; + int i, argc = *p_argc; + size_t envlen, space_count = 0; + + if (env == NULL) + return orig_argv; + + for (p = env; *p != '\0'; p++) { + if (*p == ' ') + space_count++; + } + + envlen = p - env; + new_argv = malloc(sizeof(char *) * (argc + space_count + 3 + envlen)); + if (new_argv == NULL) + return NULL; + + new_argv[0] = orig_argv[0]; + str_start = str = (char *) (new_argv + argc + space_count + 3); + memcpy(str, env, envlen + 1); + + str_end = str_start + envlen; + + quote = NULL; + for (i = 1, s = str; *s != '\0'; s++) { + if (quote == NULL) { + if (*s == ' ') { + new_argv[i] = str; + i++; + *s = '\0'; + str = s + 1; + } else if (*s == '"' || *s == '\'') + quote = s; + } else { + if (*s == *quote) { + if (quote == str) { + new_argv[i] = str + 1; + i++; + *s = '\0'; + str = s + 1; + } else { + char *it; + for (it = quote; it < s - 1; it++) + it[0] = it[1]; + for (it = s - 1; it < str_end - 2; it++) + it[0] = it[2]; + str_end -= 2; + *str_end = '\0'; + s -= 2; + } + quote = NULL; + } + } + } + if (str < s) { + new_argv[i] = str; + i++; + } + + memcpy(new_argv + i, orig_argv + 1, sizeof(char *) * (argc - 1)); + new_argv[i + argc] = NULL; + *p_argc = i + argc - 1; + + return new_argv; +} + +static void log_syslog(void *data, int priority, const char *file, int line, + const char *fn, const char *format, + va_list args) +{ + char *str, buf[32]; + const char *prioname; + + switch (priority) { + case LOG_CRIT: + prioname = "FATAL"; + break; + case LOG_ERR: + prioname = "ERROR"; + break; + case LOG_WARNING: + prioname = "WARNING"; + break; + case LOG_NOTICE: + prioname = "NOTICE"; + break; + case LOG_INFO: + prioname = "INFO"; + break; + case LOG_DEBUG: + prioname = "DEBUG"; + break; + default: + snprintf(buf, sizeof(buf), "LOG-%03d", priority); + prioname = buf; + } + + if (vasprintf(&str, format, args) < 0) + return; +#ifdef ENABLE_DEBUG + syslog(LOG_NOTICE, "%s: %s:%d %s() %s", prioname, file, line, fn, str); +#else + syslog(LOG_NOTICE, "%s: %s", prioname, str); +#endif + free(str); + (void)data; +} + +int main(int argc, char **orig_argv) +{ + struct kmod_ctx *ctx; + char **args = NULL, **argv; + int nargs = 0; + char dirname_buf[PATH_MAX]; + const char *dirname = NULL; + const char *root = NULL; + const char *config = NULL; + const char *kversion = NULL; + const char *list_type = NULL; + int use_all = 0; + int do_remove = 0; + int do_show_config = 0; + int do_show_modversions = 0; + int do_show_list = 0; + int err; + + argv = prepend_options_from_env(&argc, orig_argv); + if (argv == NULL) { + fputs("Error: could not prepend options from command line\n", + stderr); + return EXIT_FAILURE; + } + + for (;;) { + int c, idx = 0; + c = getopt_long(argc, argv, cmdopts_s, cmdopts, &idx); + if (c == -1) + break; + switch (c) { + case 'a': + log_priority = LOG_WARNING; + use_all = 1; + break; + case 'r': + do_remove = 1; + break; + case 5: + remove_dependencies = 1; + break; + case 'R': + show_resolved_aliases = 1; + break; + case 3: + first_time = 1; + break; + case 'i': + ignore_commands = 1; + break; + case 'b': + use_blacklist = 1; + break; + case 'f': + force = 1; + break; + case 2: + strip_modversion = 1; + break; + case 1: + strip_vermagic = 1; + break; + case 't': + list_type = optarg; + break; + case 'l': + do_show_list = 1; + break; + case 'D': + ignore_loaded = 1; + dry_run = 1; + verbose++; + break; + case 'c': + do_show_config = 1; + break; + case 4: + do_show_modversions = 1; + break; + case 'n': + dry_run = 1; + break; + case 'C': + env_modprobe_options_append("-C"); + env_modprobe_options_append(optarg); + config = optarg; + break; + case 'd': + root = optarg; + break; + case 'S': + kversion = optarg; + break; + case 's': + env_modprobe_options_append("-s"); + use_syslog = 1; + break; + case 'q': + env_modprobe_options_append("-q"); + verbose--; + break; + case 'v': + env_modprobe_options_append("-v"); + verbose++; + break; + case 'V': + puts(PACKAGE " version " VERSION); + if (argv != orig_argv) + free(argv); + return EXIT_SUCCESS; + case 'h': + help(argv[0]); + if (argv != orig_argv) + free(argv); + return EXIT_SUCCESS; + case '?': + goto cmdline_failed; + default: + fprintf(stderr, + "Error: unexpected getopt_long() value '%c'.\n", + c); + goto cmdline_failed; + } + } + + args = argv + optind; + nargs = argc - optind; + + if (!do_show_config && !do_show_list) { + if (nargs == 0) { + fputs("Error: missing parameters. See -h.\n", stderr); + goto cmdline_failed; + } + } + + if (!do_show_list && list_type != NULL) { + fputs("Error: -t (--type) only supported with -l (--list).\n", + stderr); + goto cmdline_failed; + } + + if (root != NULL || kversion != NULL) { + struct utsname u; + if (root == NULL) + root = ""; + if (kversion == NULL) { + if (uname(&u) < 0) { + fprintf(stderr, "Error: uname() failed: %s\n", + strerror(errno)); + goto cmdline_failed; + } + kversion = u.release; + } + snprintf(dirname_buf, sizeof(dirname_buf), "%s/lib/modules/%s", + root, kversion); + dirname = dirname_buf; + } + + ctx = kmod_new(dirname); + if (!ctx) { + fputs("Error: kmod_new() failed!\n", stderr); + goto cmdline_failed; + } + kmod_load_resources(ctx); + + kmod_set_log_priority(ctx, verbose); + if (use_syslog) { + openlog("modprobe", LOG_CONS, LOG_DAEMON); + kmod_set_log_fn(ctx, log_syslog, NULL); + } + + if (do_show_list) + err = show_list(ctx, list_type, nargs > 0 ? args[0] : NULL); + else if (do_show_config) + err = show_config(ctx); + else if (do_show_modversions) + err = show_modversions(ctx, args[0]); + else if (do_remove) + err = rmmod_all(ctx, args, use_all ? nargs : 1); + else if (use_all) + err = insmod_all(ctx, args, nargs); + else { + char *opts; + err = options_from_array(args, nargs, &opts); + if (err == 0) { + err = insmod(ctx, args[0], opts); + free(opts); + } + } + + kmod_unref(ctx); + + if (use_syslog) + closelog(); + + if (argv != orig_argv) + free(argv); + + return err >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; + +cmdline_failed: + if (argv != orig_argv) + free(argv); + return EXIT_FAILURE; +}