mirror of
https://github.com/AuxXxilium/kmod.git
synced 2024-11-23 15:00:52 +07:00
b5a2cd070d
Drop the lengthy license from each file and just use SPDX like most projects nowadays. This doesn't have any change to license, just how they are recorded in each file. This follows the kernel approach: header files use '/*' for comments while .c files use '//'. For .m4, use "#". Reviewed-by: Emil Velikov <emil.l.velikov@gmail.com> Link: https://lore.kernel.org/r/20240723185921.1005569-2-lucas.de.marchi@gmail.com Signed-off-by: Lucas De Marchi <lucas.de.marchi@gmail.com>
1044 lines
24 KiB
C
1044 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* kmod-modprobe - manage linux kernel modules using libkmod.
|
|
*
|
|
* Copyright (C) 2011-2013 ProFUSION embedded systems
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <shared/array.h>
|
|
#include <shared/util.h>
|
|
#include <shared/macro.h>
|
|
|
|
#include <libkmod/libkmod.h>
|
|
|
|
#include "kmod.h"
|
|
|
|
static int log_priority = LOG_CRIT;
|
|
static int use_syslog = 0;
|
|
#define LOG(...) log_printf(log_priority, __VA_ARGS__)
|
|
|
|
#define DEFAULT_VERBOSE LOG_WARNING
|
|
static int verbose = DEFAULT_VERBOSE;
|
|
static int do_show = 0;
|
|
static int dry_run = 0;
|
|
static int ignore_loaded = 0;
|
|
static int lookup_only = 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_holders = 0;
|
|
static unsigned long long wait_msec = 0;
|
|
static int quiet_inuse = 0;
|
|
|
|
static const char cmdopts_s[] = "arw:RibfDcnC:d:S:sqvVh";
|
|
static const struct option cmdopts[] = {
|
|
{"all", no_argument, 0, 'a'},
|
|
|
|
{"remove", no_argument, 0, 'r'},
|
|
{"remove-dependencies", no_argument, 0, 5},
|
|
{"remove-holders", no_argument, 0, 5},
|
|
{"wait", required_argument, 0, 'w'},
|
|
|
|
{"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},
|
|
|
|
{"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},
|
|
{"show-exports", no_argument, 0, 6},
|
|
|
|
{"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(void)
|
|
{
|
|
printf("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] -c\n"
|
|
"\t%s [options] --dump-modversions filename\n"
|
|
"Management Options:\n"
|
|
"\t-a, --all Consider every non-argument to\n"
|
|
"\t be a module name to be inserted\n"
|
|
"\t or removed (-r)\n"
|
|
"\t-r, --remove Remove modules instead of inserting\n"
|
|
"\t --remove-dependencies Deprecated: use --remove-holders\n"
|
|
"\t --remove-holders Also remove module holders (use together with -r)\n"
|
|
"\t-w, --wait <MSEC> When removing a module, wait up to MSEC for\n"
|
|
"\t module's refcount to become 0 so it can be\n"
|
|
"\t removed (use together with -r)\n"
|
|
"\t --first-time Fail if module already inserted or removed\n"
|
|
"\t-i, --ignore-install Ignore install commands\n"
|
|
"\t-i, --ignore-remove Ignore remove commands\n"
|
|
"\t-b, --use-blacklist Apply blacklist to resolved alias.\n"
|
|
"\t-f, --force Force module insertion or removal.\n"
|
|
"\t implies --force-modversions and\n"
|
|
"\t --force-vermagic\n"
|
|
"\t --force-modversion Ignore module's version\n"
|
|
"\t --force-vermagic Ignore module's version magic\n"
|
|
"\n"
|
|
"Query Options:\n"
|
|
"\t-R, --resolve-alias Only lookup and print alias and exit\n"
|
|
"\t-D, --show-depends Only print module dependencies and exit\n"
|
|
"\t-c, --showconfig Print out known configuration and exit\n"
|
|
"\t-c, --show-config Same as --showconfig\n"
|
|
"\t --show-modversions Dump module symbol version and exit\n"
|
|
"\t --dump-modversions Same as --show-modversions\n"
|
|
"\t --show-exports Only print module exported symbol versions and exit\n"
|
|
"\n"
|
|
"General Options:\n"
|
|
"\t-n, --dry-run Do not execute operations, just print out\n"
|
|
"\t-n, --show Same as --dry-run\n"
|
|
|
|
"\t-C, --config=FILE Use FILE instead of default search paths\n"
|
|
"\t-d, --dirname=DIR Use DIR as filesystem root for " MODULE_DIRECTORY "\n"
|
|
"\t-S, --set-version=VERSION Use VERSION instead of `uname -r`\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",
|
|
program_invocation_short_name, program_invocation_short_name,
|
|
program_invocation_short_name, program_invocation_short_name,
|
|
program_invocation_short_name, program_invocation_short_name);
|
|
}
|
|
|
|
_printf_format_(1, 2)
|
|
static inline void _show(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (!do_show && verbose <= DEFAULT_VERBOSE)
|
|
return;
|
|
|
|
va_start(args, fmt);
|
|
vfprintf(stdout, fmt, args);
|
|
fflush(stdout);
|
|
va_end(args);
|
|
}
|
|
#define SHOW(...) _show(__VA_ARGS__)
|
|
|
|
static int show_config(struct kmod_ctx *ctx)
|
|
{
|
|
struct config_iterators {
|
|
const char *name;
|
|
struct kmod_config_iter *(*get_iter)(const struct kmod_ctx *ctx);
|
|
} ci[] = {
|
|
{ "blacklist", kmod_config_get_blacklists },
|
|
{ "install", kmod_config_get_install_commands },
|
|
{ "remove", kmod_config_get_remove_commands },
|
|
{ "alias", kmod_config_get_aliases },
|
|
{ "options", kmod_config_get_options },
|
|
{ "softdep", kmod_config_get_softdeps },
|
|
{ "weakdep", kmod_config_get_weakdeps },
|
|
};
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ci); i++) {
|
|
struct kmod_config_iter *iter = ci[i].get_iter(ctx);
|
|
|
|
if (iter == NULL)
|
|
continue;
|
|
|
|
while (kmod_config_iter_next(iter)) {
|
|
const char *val;
|
|
|
|
printf("%s %s", ci[i].name,
|
|
kmod_config_iter_get_key(iter));
|
|
val = kmod_config_iter_get_value(iter);
|
|
if (val != NULL) {
|
|
putchar(' ');
|
|
puts(val);
|
|
} else
|
|
putchar('\n');
|
|
}
|
|
|
|
kmod_config_iter_free_iter(iter);
|
|
}
|
|
|
|
puts("\n# End of configuration files. Dumping indexes now:\n");
|
|
fflush(stdout);
|
|
|
|
kmod_dump_index(ctx, KMOD_INDEX_MODULES_ALIAS, STDOUT_FILENO);
|
|
kmod_dump_index(ctx, KMOD_INDEX_MODULES_SYMBOL, STDOUT_FILENO);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int show_modversions(struct kmod_ctx *ctx, const char *filename)
|
|
{
|
|
struct kmod_list *l, *list = NULL;
|
|
struct kmod_module *mod;
|
|
int err = kmod_module_new_from_path(ctx, filename, &mod);
|
|
if (err < 0) {
|
|
LOG("Module %s not found.\n", filename);
|
|
return err;
|
|
}
|
|
|
|
err = kmod_module_get_versions(mod, &list);
|
|
if (err < 0) {
|
|
LOG("could not get modversions of %s: %s\n",
|
|
filename, strerror(-err));
|
|
kmod_module_unref(mod);
|
|
return err;
|
|
}
|
|
|
|
kmod_list_foreach(l, list) {
|
|
const char *symbol = kmod_module_version_get_symbol(l);
|
|
uint64_t crc = kmod_module_version_get_crc(l);
|
|
printf("0x%08"PRIx64"\t%s\n", crc, symbol);
|
|
}
|
|
kmod_module_versions_free_list(list);
|
|
kmod_module_unref(mod);
|
|
return 0;
|
|
}
|
|
|
|
static int show_exports(struct kmod_ctx *ctx, const char *filename)
|
|
{
|
|
struct kmod_list *l, *list = NULL;
|
|
struct kmod_module *mod;
|
|
int err = kmod_module_new_from_path(ctx, filename, &mod);
|
|
if (err < 0) {
|
|
LOG("Module %s not found.\n", filename);
|
|
return err;
|
|
}
|
|
|
|
err = kmod_module_get_symbols(mod, &list);
|
|
if (err < 0) {
|
|
LOG("could not get symbols of %s: %s\n",
|
|
filename, strerror(-err));
|
|
kmod_module_unref(mod);
|
|
return err;
|
|
}
|
|
|
|
kmod_list_foreach(l, list) {
|
|
const char *symbol = kmod_module_symbol_get_symbol(l);
|
|
uint64_t crc = kmod_module_symbol_get_crc(l);
|
|
printf("0x%08"PRIx64"\t%s\n", crc, symbol);
|
|
}
|
|
kmod_module_symbols_free_list(list);
|
|
kmod_module_unref(mod);
|
|
return 0;
|
|
}
|
|
|
|
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_remove_module(struct kmod_module *mod)
|
|
{
|
|
const char *modname = kmod_module_get_name(mod);
|
|
unsigned long long interval_msec = 0, t0_msec = 0,
|
|
tend_msec = 0;
|
|
int flags = 0, err;
|
|
|
|
SHOW("rmmod %s\n", modname);
|
|
|
|
if (dry_run)
|
|
return 0;
|
|
|
|
if (force)
|
|
flags |= KMOD_REMOVE_FORCE;
|
|
|
|
if (wait_msec)
|
|
flags |= KMOD_REMOVE_NOLOG;
|
|
|
|
do {
|
|
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);
|
|
break;
|
|
} else if (err == -EAGAIN && wait_msec) {
|
|
unsigned long long until_msec;
|
|
|
|
if (!t0_msec) {
|
|
t0_msec = now_msec();
|
|
tend_msec = t0_msec + wait_msec;
|
|
interval_msec = 1;
|
|
}
|
|
|
|
until_msec = get_backoff_delta_msec(t0_msec, tend_msec,
|
|
&interval_msec);
|
|
err = sleep_until_msec(until_msec);
|
|
|
|
if (!t0_msec)
|
|
err = -ENOTSUP;
|
|
|
|
if (err < 0) {
|
|
ERR("Failed to sleep: %s\n", strerror(-err));
|
|
err = -EAGAIN;
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} while (interval_msec);
|
|
|
|
if (err < 0 && wait_msec)
|
|
ERR("could not remove '%s': %s\n", modname, strerror(-err));
|
|
|
|
return err;
|
|
}
|
|
|
|
#define RMMOD_FLAG_REMOVE_HOLDERS 0x1
|
|
#define RMMOD_FLAG_IGNORE_BUILTIN 0x2
|
|
static int rmmod_do_module(struct kmod_module *mod, int flags);
|
|
|
|
/* Remove modules in reverse order */
|
|
static int rmmod_do_modlist(struct kmod_list *list, bool stop_on_errors)
|
|
{
|
|
struct kmod_list *l;
|
|
|
|
kmod_list_foreach_reverse(l, list) {
|
|
struct kmod_module *m = kmod_module_get_module(l);
|
|
int r = rmmod_do_module(m, RMMOD_FLAG_IGNORE_BUILTIN);
|
|
kmod_module_unref(m);
|
|
|
|
if (r < 0 && stop_on_errors)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rmmod_do_module(struct kmod_module *mod, int flags)
|
|
{
|
|
const char *modname = kmod_module_get_name(mod);
|
|
struct kmod_list *pre = NULL, *post = NULL;
|
|
const char *cmd = NULL;
|
|
int err;
|
|
|
|
if (!ignore_commands) {
|
|
err = kmod_module_get_softdeps(mod, &pre, &post);
|
|
if (err < 0) {
|
|
WRN("could not get softdeps of '%s': %s\n",
|
|
modname, strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
cmd = kmod_module_get_remove_commands(mod);
|
|
}
|
|
|
|
/* Quick check if module is loaded, otherwise there's nothing to do */
|
|
if (!cmd && !ignore_loaded) {
|
|
int state = kmod_module_get_initstate(mod);
|
|
|
|
if (state < 0) {
|
|
if (first_time) {
|
|
LOG("Module %s is not in kernel.\n", modname);
|
|
err = -ENOENT;
|
|
} else {
|
|
err = 0;
|
|
}
|
|
goto error;
|
|
} else if (state == KMOD_MODULE_BUILTIN) {
|
|
if (flags & RMMOD_FLAG_IGNORE_BUILTIN) {
|
|
err = 0;
|
|
} else {
|
|
LOG("Module %s is builtin.\n", modname);
|
|
err = -ENOENT;
|
|
}
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* 1. @mod's post-softdeps in reverse order */
|
|
rmmod_do_modlist(post, false);
|
|
|
|
/* 2. Other modules holding @mod */
|
|
if (flags & RMMOD_FLAG_REMOVE_HOLDERS) {
|
|
struct kmod_list *holders = kmod_module_get_holders(mod);
|
|
|
|
err = rmmod_do_modlist(holders, true);
|
|
kmod_module_unref_list(holders);
|
|
if (err < 0)
|
|
goto error;
|
|
}
|
|
|
|
/* 3. @mod itself, but check for refcnt first */
|
|
if (!cmd && !ignore_loaded && !wait_msec) {
|
|
int usage = kmod_module_get_refcnt(mod);
|
|
|
|
if (usage > 0) {
|
|
if (!quiet_inuse)
|
|
LOG("Module %s is in use.\n", modname);
|
|
|
|
err = -EBUSY;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (!cmd)
|
|
err = rmmod_do_remove_module(mod);
|
|
else
|
|
err = command_do(mod, "remove", cmd, NULL);
|
|
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
/* 4. Other modules that became unused: errors are non-fatal */
|
|
if (!cmd) {
|
|
struct kmod_list *deps, *itr;
|
|
|
|
deps = kmod_module_get_dependencies(mod);
|
|
kmod_list_foreach(itr, deps) {
|
|
struct kmod_module *dep = kmod_module_get_module(itr);
|
|
if (kmod_module_get_refcnt(dep) == 0)
|
|
rmmod_do_remove_module(dep);
|
|
kmod_module_unref(dep);
|
|
}
|
|
kmod_module_unref_list(deps);
|
|
}
|
|
|
|
/* 5. @mod's pre-softdeps in reverse order: errors are non-fatal */
|
|
rmmod_do_modlist(pre, false);
|
|
|
|
error:
|
|
kmod_module_unref_list(pre);
|
|
kmod_module_unref_list(post);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int rmmod(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)
|
|
return err;
|
|
|
|
if (list == NULL) {
|
|
LOG("Module %s not found.\n", alias);
|
|
err = -ENOENT;
|
|
}
|
|
|
|
kmod_list_foreach(l, list) {
|
|
struct kmod_module *mod = kmod_module_get_module(l);
|
|
int flags = remove_holders ? RMMOD_FLAG_REMOVE_HOLDERS : 0;
|
|
|
|
err = rmmod_do_module(mod, flags);
|
|
kmod_module_unref(mod);
|
|
if (err < 0)
|
|
break;
|
|
}
|
|
|
|
kmod_module_unref_list(list);
|
|
return err;
|
|
}
|
|
|
|
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 void print_action(struct kmod_module *m, bool install,
|
|
const char *options)
|
|
{
|
|
const char *path;
|
|
|
|
if (install) {
|
|
printf("install %s %s\n", kmod_module_get_install_commands(m),
|
|
options);
|
|
return;
|
|
}
|
|
|
|
path = kmod_module_get_path(m);
|
|
|
|
if (path == NULL) {
|
|
/*
|
|
* Either a builtin module, or an alias, print only for
|
|
* builtin
|
|
*/
|
|
if (kmod_module_get_initstate(m) == KMOD_MODULE_BUILTIN)
|
|
printf("builtin %s\n", kmod_module_get_name(m));
|
|
} else
|
|
printf("insmod %s %s\n", kmod_module_get_path(m), options);
|
|
}
|
|
|
|
static int insmod_insert(struct kmod_module *mod, int flags,
|
|
const char *extra_options)
|
|
{
|
|
int err = 0;
|
|
void (*show)(struct kmod_module *m, bool install,
|
|
const char *options) = NULL;
|
|
|
|
if (do_show || verbose > DEFAULT_VERBOSE)
|
|
show = &print_action;
|
|
|
|
if (lookup_only)
|
|
printf("%s\n", kmod_module_get_name(mod));
|
|
else
|
|
err = kmod_module_probe_insert_module(mod, flags,
|
|
extra_options, NULL, NULL, show);
|
|
|
|
if (err >= 0)
|
|
/* ignore flag return values such as a mod being blacklisted */
|
|
err = 0;
|
|
else {
|
|
switch (err) {
|
|
case -EEXIST:
|
|
ERR("could not insert '%s': Module already in kernel\n",
|
|
kmod_module_get_name(mod));
|
|
break;
|
|
case -ENOENT:
|
|
ERR("could not insert '%s': Unknown symbol in module, "
|
|
"or unknown parameter (see dmesg)\n",
|
|
kmod_module_get_name(mod));
|
|
break;
|
|
default:
|
|
ERR("could not insert '%s': %s\n",
|
|
kmod_module_get_name(mod),
|
|
strerror(-err));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int insmod(struct kmod_ctx *ctx, const char *alias,
|
|
const char *extra_options)
|
|
{
|
|
struct kmod_list *l, *list = NULL;
|
|
struct kmod_module *mod = NULL;
|
|
int err, flags = 0;
|
|
|
|
if (strncmp(alias, "/", 1) == 0 || strncmp(alias, "./", 2) == 0) {
|
|
err = kmod_module_new_from_path(ctx, alias, &mod);
|
|
if (err < 0) {
|
|
LOG("Failed to get module from path %s: %s\n", alias,
|
|
strerror(-err));
|
|
return -ENOENT;
|
|
}
|
|
} else {
|
|
err = kmod_module_new_from_lookup(ctx, alias, &list);
|
|
if (list == NULL || err < 0) {
|
|
LOG("Module %s not found in directory %s\n", alias,
|
|
ctx ? kmod_get_dirname(ctx) : "(missing)");
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
if (strip_modversion || force)
|
|
flags |= KMOD_PROBE_FORCE_MODVERSION;
|
|
if (strip_vermagic || force)
|
|
flags |= KMOD_PROBE_FORCE_VERMAGIC;
|
|
if (ignore_commands)
|
|
flags |= KMOD_PROBE_IGNORE_COMMAND;
|
|
if (ignore_loaded)
|
|
flags |= KMOD_PROBE_IGNORE_LOADED;
|
|
if (dry_run)
|
|
flags |= KMOD_PROBE_DRY_RUN;
|
|
|
|
flags |= KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY;
|
|
|
|
if (use_blacklist)
|
|
flags |= KMOD_PROBE_APPLY_BLACKLIST;
|
|
if (first_time)
|
|
flags |= KMOD_PROBE_FAIL_ON_LOADED;
|
|
|
|
/* If module is loaded from path */
|
|
if (mod != NULL) {
|
|
err = insmod_insert(mod, flags, extra_options);
|
|
kmod_module_unref(mod);
|
|
} else {
|
|
kmod_list_foreach(l, list) {
|
|
mod = kmod_module_get_module(l);
|
|
err = insmod_insert(mod, flags, extra_options);
|
|
kmod_module_unref(mod);
|
|
}
|
|
kmod_module_unref_list(list);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
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_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 = (char *) (new_argv + argc + space_count + 3);
|
|
memcpy(str, env, envlen + 1);
|
|
|
|
str_end = str + 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 - 1] = NULL;
|
|
*p_argc = i + argc - 1;
|
|
|
|
return new_argv;
|
|
}
|
|
|
|
static int do_modprobe(int argc, char **orig_argv)
|
|
{
|
|
struct kmod_ctx *ctx;
|
|
char **args = NULL, **argv;
|
|
const char **config_paths = NULL;
|
|
int nargs = 0, n_config_paths = 0;
|
|
char dirname_buf[PATH_MAX];
|
|
const char *dirname = NULL;
|
|
const char *root = NULL;
|
|
const char *kversion = NULL;
|
|
int use_all = 0;
|
|
int do_remove = 0;
|
|
int do_show_config = 0;
|
|
int do_show_modversions = 0;
|
|
int do_show_exports = 0;
|
|
int err;
|
|
struct stat stat_buf;
|
|
|
|
argv = prepend_options_from_env(&argc, orig_argv);
|
|
if (argv == NULL) {
|
|
ERR("Could not prepend options from command line\n");
|
|
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_holders = 1;
|
|
break;
|
|
case 'w': {
|
|
char *endptr = NULL;
|
|
wait_msec = strtoul(optarg, &endptr, 0);
|
|
if (!*optarg || *endptr) {
|
|
ERR("unexpected wait value '%s'.\n", optarg);
|
|
err = -1;
|
|
goto done;
|
|
}
|
|
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 'D':
|
|
ignore_loaded = 1;
|
|
dry_run = 1;
|
|
do_show = 1;
|
|
break;
|
|
case 'R':
|
|
lookup_only = 1;
|
|
break;
|
|
case 'c':
|
|
do_show_config = 1;
|
|
break;
|
|
case 4:
|
|
do_show_modversions = 1;
|
|
break;
|
|
case 6:
|
|
do_show_exports = 1;
|
|
break;
|
|
case 'n':
|
|
dry_run = 1;
|
|
break;
|
|
case 'C': {
|
|
size_t bytes = sizeof(char *) * (n_config_paths + 2);
|
|
void *tmp = realloc(config_paths, bytes);
|
|
if (!tmp) {
|
|
ERR("out-of-memory\n");
|
|
err = -1;
|
|
goto done;
|
|
}
|
|
config_paths = tmp;
|
|
config_paths[n_config_paths] = optarg;
|
|
n_config_paths++;
|
|
config_paths[n_config_paths] = NULL;
|
|
|
|
env_modprobe_options_append("-C");
|
|
env_modprobe_options_append(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 = LOG_EMERG;
|
|
break;
|
|
case 'v':
|
|
env_modprobe_options_append("-v");
|
|
verbose++;
|
|
break;
|
|
case 'V':
|
|
puts(PACKAGE " version " VERSION);
|
|
puts(KMOD_FEATURES);
|
|
err = 0;
|
|
goto done;
|
|
case 'h':
|
|
help();
|
|
err = 0;
|
|
goto done;
|
|
case '?':
|
|
err = -1;
|
|
goto done;
|
|
default:
|
|
ERR("unexpected getopt_long() value '%c'.\n", c);
|
|
err = -1;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
args = argv + optind;
|
|
nargs = argc - optind;
|
|
|
|
if (!use_syslog &&
|
|
(!stderr ||
|
|
fileno(stderr) == -1 ||
|
|
fstat(fileno(stderr), &stat_buf)))
|
|
use_syslog = 1;
|
|
|
|
log_open(use_syslog);
|
|
|
|
if (!do_show_config) {
|
|
if (nargs == 0) {
|
|
ERR("missing parameters. See -h.\n");
|
|
err = -1;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (root != NULL || kversion != NULL) {
|
|
struct utsname u;
|
|
if (root == NULL)
|
|
root = "";
|
|
if (kversion == NULL) {
|
|
if (uname(&u) < 0) {
|
|
ERR("uname() failed: %m\n");
|
|
err = -1;
|
|
goto done;
|
|
}
|
|
kversion = u.release;
|
|
}
|
|
snprintf(dirname_buf, sizeof(dirname_buf),
|
|
"%s" MODULE_DIRECTORY "/%s", root,
|
|
kversion);
|
|
dirname = dirname_buf;
|
|
}
|
|
|
|
ctx = kmod_new(dirname, config_paths);
|
|
if (!ctx) {
|
|
ERR("kmod_new() failed!\n");
|
|
err = -1;
|
|
goto done;
|
|
}
|
|
|
|
log_setup_kmod_log(ctx, verbose);
|
|
|
|
kmod_load_resources(ctx);
|
|
|
|
if (do_show_config)
|
|
err = show_config(ctx);
|
|
else if (do_show_modversions)
|
|
err = show_modversions(ctx, args[0]);
|
|
else if (do_show_exports)
|
|
err = show_exports(ctx, args[0]);
|
|
else if (do_remove)
|
|
err = rmmod_all(ctx, args, nargs);
|
|
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);
|
|
|
|
done:
|
|
log_close();
|
|
|
|
if (argv != orig_argv)
|
|
free(argv);
|
|
|
|
free(config_paths);
|
|
|
|
return err >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|
|
|
|
const struct kmod_cmd kmod_cmd_compat_modprobe = {
|
|
.name = "modprobe",
|
|
.cmd = do_modprobe,
|
|
.help = "compat modprobe command",
|
|
};
|