mirror of
https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
synced 2024-11-30 23:33:55 +08:00
05828b4a6e
It has been seen that for some network mac drivers (i.e. lan78xx) the related module for the phy is loaded dynamically depending on the current hardware. In this case, the associated phy is read using mdio bus and then the associated phy module is loaded during runtime (kernel function phy_request_driver_module). However, no software dependency is defined, so the user tools will no be able to get this dependency. For example, if dracut is used and the hardware is present, lan78xx will be included but no phy module will be added, and in the next restart the device will not work from boot because no related phy will be found during initramfs stage. In order to solve this, we could define a normal 'pre' software dependency in lan78xx module with all the possible phy modules (there may be some), but proceeding in that way, all the possible phy modules would be loaded while only one is necessary. The idea is to create a new type of dependency, that we are going to call 'weak' to be used only by the user tools that need to detect this situation. In that way, for example, dracut could check the 'weak' dependency of the modules involved in order to install these dependencies in initramfs too. That is, for the commented lan78xx module, defining the 'weak' dependency with the possible phy modules list, only the necessary phy would be loaded on demand keeping the same behavior, but all the possible phy modules would be available from initramfs. A new function 'kmod_module_get_weakdeps' in libkmod will be added for this to avoid breaking the API and maintain backward compatibility. This general procedure could be useful for other similar cases (not only for dynamic phy loading). Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com> Link: https://lore.kernel.org/r/20240327141116.97587-1-jtornosm@redhat.com
1056 lines
24 KiB
C
1056 lines
24 KiB
C
/*
|
|
* kmod-modprobe - manage linux kernel modules using libkmod.
|
|
*
|
|
* Copyright (C) 2011-2013 ProFUSION embedded systems
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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",
|
|
};
|