166 changes: 113 additions & 53 deletions libkmod/libkmod-module.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,17 +431,18 @@ KMOD_EXPORT int kmod_module_new_from_path(struct kmod_ctx *ctx,
return -EEXIST;
}

*mod = kmod_module_ref(m);
return 0;
}
kmod_module_ref(m);
} else {
err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m);
if (err < 0) {
free(abspath);
return err;
}

err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m);
if (err < 0) {
free(abspath);
return err;
m->path = abspath;
}

m->path = abspath;
m->builtin = KMOD_MODULE_BUILTIN_NO;
*mod = m;

return 0;
Expand Down Expand Up @@ -498,13 +499,26 @@ KMOD_EXPORT struct kmod_module *kmod_module_ref(struct kmod_module *mod)
return mod;
}

#define CHECK_ERR_AND_FINISH(_err, _label_err, _list, label_finish) \
do { \
if ((_err) < 0) \
goto _label_err; \
if (*(_list) != NULL) \
goto finish; \
} while (0)
typedef int (*lookup_func)(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3)));

static int __kmod_module_new_from_lookup(struct kmod_ctx *ctx, const lookup_func lookup[],
size_t lookup_count, const char *s,
struct kmod_list **list)
{
unsigned int i;

for (i = 0; i < lookup_count; i++) {
int err;

err = lookup[i](ctx, s, list);
if (err < 0 && err != -ENOSYS)
return err;
else if (*list != NULL)
return 0;
}

return 0;
}

/**
* kmod_module_new_from_lookup:
Expand All @@ -520,7 +534,7 @@ KMOD_EXPORT struct kmod_module *kmod_module_ref(struct kmod_module *mod)
*
* The search order is: 1. aliases in configuration file; 2. module names in
* modules.dep index; 3. symbol aliases in modules.symbols index; 4. aliases
* in modules.alias index.
* from install commands; 5. builtin indexes from kernel.
*
* The initial refcount is 1, and needs to be decremented to release the
* resources of the kmod_module. The returned @list must be released by
Expand All @@ -537,8 +551,17 @@ KMOD_EXPORT int kmod_module_new_from_lookup(struct kmod_ctx *ctx,
const char *given_alias,
struct kmod_list **list)
{
int err;
const lookup_func lookup[] = {
kmod_lookup_alias_from_config,
kmod_lookup_alias_from_moddep_file,
kmod_lookup_alias_from_symbols_file,
kmod_lookup_alias_from_commands,
kmod_lookup_alias_from_aliases_file,
kmod_lookup_alias_from_builtin_file,
kmod_lookup_alias_from_kernel_builtin_file,
};
char alias[PATH_MAX];
int err;

if (ctx == NULL || given_alias == NULL)
return -ENOENT;
Expand All @@ -555,46 +578,75 @@ KMOD_EXPORT int kmod_module_new_from_lookup(struct kmod_ctx *ctx,

DBG(ctx, "input alias=%s, normalized=%s\n", given_alias, alias);

/* Aliases from config file override all the others */
err = kmod_lookup_alias_from_config(ctx, alias, list);
CHECK_ERR_AND_FINISH(err, fail, list, finish);
err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup),
alias, list);

DBG(ctx, "lookup modules.dep %s\n", alias);
err = kmod_lookup_alias_from_moddep_file(ctx, alias, list);
CHECK_ERR_AND_FINISH(err, fail, list, finish);
DBG(ctx, "lookup=%s found=%d\n", alias, err >= 0 && *list);

DBG(ctx, "lookup modules.symbols %s\n", alias);
err = kmod_lookup_alias_from_symbols_file(ctx, alias, list);
CHECK_ERR_AND_FINISH(err, fail, list, finish);
if (err < 0) {
kmod_module_unref_list(*list);
*list = NULL;
}

DBG(ctx, "lookup install and remove commands %s\n", alias);
err = kmod_lookup_alias_from_commands(ctx, alias, list);
CHECK_ERR_AND_FINISH(err, fail, list, finish);
return err;
}

DBG(ctx, "lookup modules.aliases %s\n", alias);
err = kmod_lookup_alias_from_aliases_file(ctx, alias, list);
CHECK_ERR_AND_FINISH(err, fail, list, finish);
/**
* kmod_module_new_from_name_lookup:
* @ctx: kmod library context
* @modname: module name to look for
* @mod: returned module on success
*
* Lookup by module name, without considering possible aliases. This is similar
* to kmod_module_new_from_lookup(), but don't consider as source indexes and
* configurations that work with aliases. When succesful, this always resolves
* to one and only one module.
*
* The search order is: 1. module names in modules.dep index;
* 2. builtin indexes from kernel.
*
* The initial refcount is 1, and needs to be decremented to release the
* resources of the kmod_module. Since libkmod keeps track of all
* kmod_modules created, they are all released upon @ctx destruction too. Do
* not unref @ctx before all the desired operations with the returned list are
* completed.
*
* Returns: 0 on success or < 0 otherwise. It fails if any of the lookup
* methods failed, which is basically due to memory allocation failure. If
* module is not found, it still returns 0, but @mod is left untouched.
*/
KMOD_EXPORT int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx,
const char *modname,
struct kmod_module **mod)
{
const lookup_func lookup[] = {
kmod_lookup_alias_from_moddep_file,
kmod_lookup_alias_from_builtin_file,
kmod_lookup_alias_from_kernel_builtin_file,
};
char name_norm[PATH_MAX];
struct kmod_list *list = NULL;
int err;

DBG(ctx, "lookup modules.builtin.modinfo %s\n", alias);
err = kmod_lookup_alias_from_kernel_builtin_file(ctx, alias, list);
if (err == -ENOSYS) {
/* Optional index missing, try the old one */
DBG(ctx, "lookup modules.builtin %s\n", alias);
err = kmod_lookup_alias_from_builtin_file(ctx, alias, list);
}
CHECK_ERR_AND_FINISH(err, fail, list, finish);
if (ctx == NULL || modname == NULL || mod == NULL)
return -ENOENT;

modname_normalize(modname, name_norm, NULL);

DBG(ctx, "input modname=%s, normalized=%s\n", modname, name_norm);

err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup),
name_norm, &list);

DBG(ctx, "lookup=%s found=%d\n", name_norm, err >= 0 && list);

if (err >= 0 && list != NULL)
*mod = kmod_module_get_module(list);

kmod_module_unref_list(list);

finish:
DBG(ctx, "lookup %s=%d, list=%p\n", alias, err, *list);
return err;
fail:
DBG(ctx, "Failed to lookup %s\n", alias);
kmod_module_unref_list(*list);
*list = NULL;
return err;
}
#undef CHECK_ERR_AND_FINISH

/**
* kmod_module_unref_list:
Expand Down Expand Up @@ -771,11 +823,13 @@ extern long delete_module(const char *name, unsigned int flags);
/**
* kmod_module_remove_module:
* @mod: kmod module
* @flags: flags to pass to Linux kernel when removing the module. The only valid flag is
* @flags: flags used when removing the module.
* KMOD_REMOVE_FORCE: force remove module regardless if it's still in
* use by a kernel subsystem or other process;
* KMOD_REMOVE_NOWAIT is always enforced, causing us to pass O_NONBLOCK to
* use by a kernel subsystem or other process; passed directly to Linux kernel
* KMOD_REMOVE_NOWAIT: is always enforced, causing us to pass O_NONBLOCK to
* delete_module(2).
* KMOD_REMOVE_NOLOG: when module removal fails, do not log anything as the
* caller may want to handle retries and log when appropriate.
*
* Remove a module from Linux kernel.
*
Expand All @@ -784,6 +838,8 @@ extern long delete_module(const char *name, unsigned int flags);
KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod,
unsigned int flags)
{
unsigned int libkmod_flags = flags & 0xff;

int err;

if (mod == NULL)
Expand All @@ -796,7 +852,8 @@ KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod,
err = delete_module(mod->name, flags);
if (err != 0) {
err = -errno;
ERR(mod->ctx, "could not remove '%s': %m\n", mod->name);
if (!(libkmod_flags & KMOD_REMOVE_NOLOG))
ERR(mod->ctx, "could not remove '%s': %m\n", mod->name);
}

return err;
Expand Down Expand Up @@ -2912,7 +2969,10 @@ int kmod_module_get_builtin(struct kmod_ctx *ctx, struct kmod_list **list)
goto fail;
}

kmod_module_new_from_name(ctx, modname, &mod);
err = kmod_module_new_from_name(ctx, modname, &mod);
if (err < 0)
goto fail;

kmod_module_set_builtin(mod, true);

*list = kmod_list_append(*list, mod);
Expand Down
6 changes: 6 additions & 0 deletions libkmod/libkmod-signature.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ enum pkey_hash_algo {
PKEY_HASH_SHA384,
PKEY_HASH_SHA512,
PKEY_HASH_SHA224,
PKEY_HASH_SM3,
PKEY_HASH__LAST
};

Expand All @@ -68,6 +69,7 @@ const char *const pkey_hash_algo[PKEY_HASH__LAST] = {
[PKEY_HASH_SHA384] = "sha384",
[PKEY_HASH_SHA512] = "sha512",
[PKEY_HASH_SHA224] = "sha224",
[PKEY_HASH_SM3] = "sm3",
};

enum pkey_id_type {
Expand Down Expand Up @@ -161,6 +163,10 @@ static int obj_to_hash_algo(const ASN1_OBJECT *o)
return PKEY_HASH_SHA512;
case NID_sha224:
return PKEY_HASH_SHA224;
# ifndef OPENSSL_NO_SM3
case NID_sm3:
return PKEY_HASH_SM3;
# endif
default:
return -1;
}
Expand Down
5 changes: 5 additions & 0 deletions libkmod/libkmod.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ int kmod_module_new_from_path(struct kmod_ctx *ctx, const char *path,
struct kmod_module **mod);
int kmod_module_new_from_lookup(struct kmod_ctx *ctx, const char *given_alias,
struct kmod_list **list);
int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx,
const char *modname,
struct kmod_module **mod);
int kmod_module_new_from_loaded(struct kmod_ctx *ctx,
struct kmod_list **list);

Expand All @@ -142,6 +145,8 @@ struct kmod_module *kmod_module_get_module(const struct kmod_list *entry);
enum kmod_remove {
KMOD_REMOVE_FORCE = O_TRUNC,
KMOD_REMOVE_NOWAIT = O_NONBLOCK, /* always set */
/* libkmod-only defines, not passed to kernel */
KMOD_REMOVE_NOLOG = 1,
};

/* Insertion flags */
Expand Down
1 change: 1 addition & 0 deletions libkmod/libkmod.sym
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ global:
kmod_module_new_from_name;
kmod_module_new_from_path;
kmod_module_new_from_lookup;
kmod_module_new_from_name_lookup;
kmod_module_new_from_loaded;
kmod_module_ref;
kmod_module_unref;
Expand Down
14 changes: 14 additions & 0 deletions man/depmod.d.xml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>exclude <replaceable>excludedir</replaceable>
</term>
<listitem>
<para>
This specifies the trailing directories that will be excluded
during the search for kernel modules.
</para>
<para>
The <replaceable>excludedir</replaceable> is the trailing directory
to exclude
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

Expand Down
17 changes: 17 additions & 0 deletions man/modprobe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,23 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-w</option>
</term>
<term>
<option>--wait=</option>TIMEOUT_MSEC
</term>
<listitem>
<para>
This option causes <command>modprobe -r</command> to continue trying to
remove a module if it fails due to the module being busy, i.e. its refcount
is not 0 at the time the call is made. Modprobe tries to remove the module
with an incremental sleep time between each tentative up until the maximum
wait time in milliseconds passed in this option.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-S</option>
Expand Down
72 changes: 72 additions & 0 deletions shared/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,78 @@ unsigned long long ts_usec(const struct timespec *ts)
(unsigned long long) ts->tv_nsec / NSEC_PER_USEC;
}

unsigned long long ts_msec(const struct timespec *ts)
{
return (unsigned long long) ts->tv_sec * MSEC_PER_SEC +
(unsigned long long) ts->tv_nsec / NSEC_PER_MSEC;
}

static struct timespec msec_ts(unsigned long long msec)
{
struct timespec ts = {
.tv_sec = msec / MSEC_PER_SEC,
.tv_nsec = (msec % MSEC_PER_SEC) * NSEC_PER_MSEC,
};

return ts;
}

int sleep_until_msec(unsigned long long msec)
{
struct timespec ts = msec_ts(msec);

if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) < 0 &&
errno != EINTR)
return -errno;

return 0;
}

/*
* Exponential retry backoff with tail
*/
unsigned long long get_backoff_delta_msec(unsigned long long t0,
unsigned long long tend,
unsigned long long *delta)
{
unsigned long long t;

t = now_msec();

if (!*delta)
*delta = 1;
else
*delta <<= 1;

while (t + *delta > tend)
*delta >>= 1;

if (!*delta && tend > t)
*delta = tend - t;

return t + *delta;
}

unsigned long long now_usec(void)
{
struct timespec ts;

if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return 0;

return ts_usec(&ts);
}

unsigned long long now_msec(void)
{
struct timespec ts;

if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return 0;

return ts_msec(&ts);
}

unsigned long long stat_mstamp(const struct stat *st)
{
#ifdef HAVE_STRUCT_STAT_ST_MTIM
Expand Down
17 changes: 17 additions & 0 deletions shared/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>

#include <shared/macro.h>

Expand Down Expand Up @@ -42,7 +43,23 @@ char *path_make_absolute_cwd(const char *p) _must_check_ __attribute__((nonnull(
int mkdir_p(const char *path, int len, mode_t mode);
int mkdir_parents(const char *path, mode_t mode);
unsigned long long stat_mstamp(const struct stat *st);

/* time-related functions
* ************************************************************************ */
#define USEC_PER_SEC 1000000ULL
#define USEC_PER_MSEC 1000ULL
#define MSEC_PER_SEC 1000ULL
#define NSEC_PER_MSEC 1000000ULL

unsigned long long ts_usec(const struct timespec *ts);
unsigned long long ts_msec(const struct timespec *ts);
unsigned long long now_usec(void);
unsigned long long now_msec(void);
int sleep_until_msec(unsigned long long msec);
unsigned long long get_backoff_delta_msec(unsigned long long t0,
unsigned long long tend,
unsigned long long *delta);


/* endianess and alignments */
/* ************************************************************************ */
Expand Down
18 changes: 17 additions & 1 deletion testsuite/module-playground/mod-simple.c
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/module.h>

static struct dentry *debugfs_dir;

static int test_show(struct seq_file *s, void *data)
{
seq_puts(s, "test");
return 0;
}

DEFINE_SHOW_ATTRIBUTE(test);

static int __init test_module_init(void)
{
debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL);
debugfs_create_file("test", 0444, debugfs_dir, NULL, &test_fops);

return 0;
}

static void test_module_exit(void)
{
debugfs_remove_recursive(debugfs_dir);
}

module_init(test_module_init);
module_exit(test_module_exit);

MODULE_AUTHOR("Lucas De Marchi <lucas.demarchi@intel.com>");
MODULE_LICENSE("LGPL");
MODULE_LICENSE("GPL");
2 changes: 1 addition & 1 deletion testsuite/test-initstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ static noreturn int test_initstate_from_lookup(const struct test *t)
exit(EXIT_FAILURE);

err = kmod_module_new_from_lookup(ctx, "fake-builtin", &list);
if (err != 0) {
if (err < 0) {
ERR("could not create module from lookup: %s\n", strerror(-err));
exit(EXIT_FAILURE);
}
Expand Down
41 changes: 41 additions & 0 deletions testsuite/test-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,45 @@ DEFINE_TEST(test_addu64_overflow,
);


static int test_backoff_time(const struct test *t)
{
unsigned long long delta;

/* Check exponential increments */
get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
assert_return(delta == 1, EXIT_FAILURE);
get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
assert_return(delta == 2, EXIT_FAILURE);
get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
assert_return(delta == 4, EXIT_FAILURE);
get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
assert_return(delta == 8, EXIT_FAILURE);

{
unsigned long long t0, tend;

/* Check tail */
delta = 4;
tend = now_msec() + 3;
t0 = tend - 10;
get_backoff_delta_msec(t0, tend, &delta);
assert_return(delta == 2, EXIT_FAILURE);
tend = now_msec() + 1;
t0 = tend - 9;
get_backoff_delta_msec(t0, tend, &delta);
assert_return(delta == 1, EXIT_FAILURE);
tend = now_msec();
t0 = tend - 10;
get_backoff_delta_msec(t0, tend, &delta);
assert_return(delta == 0, EXIT_FAILURE);
}

return EXIT_SUCCESS;
}
DEFINE_TEST(test_backoff_time,
.description = "check implementation of get_backoff_delta_msec()",
.need_spawn = false,
);


TESTSUITE_MAIN();
14 changes: 1 addition & 13 deletions testsuite/testsuite.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ static const struct option options[] = {
};

#define OVERRIDE_LIBDIR ABS_TOP_BUILDDIR "/testsuite/.libs/"
#define TEST_TIMEOUT_USEC 2 * USEC_PER_SEC

struct _env_config {
const char *key;
Expand All @@ -62,19 +63,6 @@ struct _env_config {
[TC_DELETE_MODULE_RETCODES] = { S_TC_DELETE_MODULE_RETCODES, OVERRIDE_LIBDIR "delete_module.so" },
};

#define USEC_PER_SEC 1000000ULL
#define USEC_PER_MSEC 1000ULL
#define TEST_TIMEOUT_USEC 2 * USEC_PER_SEC
static unsigned long long now_usec(void)
{
struct timespec ts;

if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return 0;

return ts_usec(&ts);
}

static void help(void)
{
const struct option *itr;
Expand Down
228 changes: 159 additions & 69 deletions tools/depmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ struct cfg_external {
char path[];
};

struct cfg_exclude {
struct cfg_exclude *next;
char exclude_dir[];
};

struct cfg {
const char *kversion;
char dirname[PATH_MAX];
Expand All @@ -469,6 +474,7 @@ struct cfg {
struct cfg_override *overrides;
struct cfg_search *searches;
struct cfg_external *externals;
struct cfg_exclude *excludes;
};

static enum search_type cfg_define_search_type(const char *path)
Expand Down Expand Up @@ -580,6 +586,30 @@ static void cfg_external_free(struct cfg_external *ext)
free(ext);
}

static int cfg_exclude_add(struct cfg *cfg, const char *path)
{
struct cfg_exclude *exc;
size_t len = strlen(path);

exc = malloc(sizeof(struct cfg_exclude) + len + 1);
if (exc == NULL) {
ERR("exclude add: out of memory\n");
return -ENOMEM;
}
memcpy(exc->exclude_dir, path, len + 1);

DBG("exclude add: %s\n", path);

exc->next = cfg->excludes;
cfg->excludes = exc;
return 0;
}

static void cfg_exclude_free(struct cfg_exclude *exc)
{
free(exc);
}

static int cfg_kernel_matches(const struct cfg *cfg, const char *pattern)
{
regex_t re;
Expand Down Expand Up @@ -657,6 +687,11 @@ static int cfg_file_parse(struct cfg *cfg, const char *filename)
}

cfg_external_add(cfg, dir);
} else if (streq(cmd, "exclude")) {
const char *sp;
while ((sp = strtok_r(NULL, "\t ", &saveptr)) != NULL) {
cfg_exclude_add(cfg, sp);
}
} else if (streq(cmd, "include")
|| streq(cmd, "make_map_files")) {
INF("%s:%u: command %s not implemented yet\n",
Expand Down Expand Up @@ -857,6 +892,12 @@ static void cfg_free(struct cfg *cfg)
cfg->externals = cfg->externals->next;
cfg_external_free(tmp);
}

while (cfg->excludes) {
struct cfg_exclude *tmp = cfg->excludes;
cfg->excludes = cfg->excludes->next;
cfg_exclude_free(tmp);
}
}


Expand Down Expand Up @@ -1229,6 +1270,25 @@ static int depmod_modules_search_file(struct depmod *depmod, size_t baselen, siz
return 0;
}

static bool should_exclude_dir(const struct cfg *cfg, const char *name)
{
struct cfg_exclude *exc;

if (name[0] == '.' && (name[1] == '\0' ||
(name[1] == '.' && name[2] == '\0')))
return true;

if (streq(name, "build") || streq(name, "source"))
return true;

for (exc = cfg->excludes; exc != NULL; exc = exc->next) {
if (streq(name, exc->exclude_dir))
return true;
}

return false;
}

static int depmod_modules_search_dir(struct depmod *depmod, DIR *d, size_t baselen, struct scratchbuf *s_path)
{
struct dirent *de;
Expand All @@ -1240,11 +1300,9 @@ static int depmod_modules_search_dir(struct depmod *depmod, DIR *d, size_t basel
size_t namelen;
uint8_t is_dir;

if (name[0] == '.' && (name[1] == '\0' ||
(name[1] == '.' && name[2] == '\0')))
continue;
if (streq(name, "build") || streq(name, "source"))
if (should_exclude_dir(depmod->cfg, name))
continue;

namelen = strlen(name);
if (scratchbuf_alloc(s_path, baselen + namelen + 2) < 0) {
err = -ENOMEM;
Expand Down Expand Up @@ -2346,6 +2404,103 @@ static int output_builtin_bin(struct depmod *depmod, FILE *out)
return 0;
}

static int flush_stream(FILE *in, int endchar)
{
size_t i = 0;
int c;

for (c = fgetc(in);
c != EOF && c != endchar && c != '\0';
c = fgetc(in))
;

return c == endchar ? i : 0;
}

static int flush_stream_to(FILE *in, int endchar, char *dst, size_t dst_sz)
{
size_t i = 0;
int c;

for (c = fgetc(in);
c != EOF && c != endchar && c != '\0' && i < dst_sz;
c = fgetc(in))
dst[i++] = c;

if (i == dst_sz) {
WRN("Could not flush stream: %d. Partial content: %.*s\n",
ENOSPC, (int) dst_sz, dst);
i--;
}

return c == endchar ? i : 0;
}

static int output_builtin_alias_bin(struct depmod *depmod, FILE *out)
{
FILE *in;
struct index_node *idx;
int ret;

if (out == stdout)
return 0;

in = dfdopen(depmod->cfg->dirname, "modules.builtin.modinfo", O_RDONLY, "r");
if (in == NULL)
return 0;

idx = index_create();
if (idx == NULL) {
fclose(in);
return -ENOMEM;
}

/* format: modname.key=value\0 */
while (!feof(in) && !ferror(in)) {
char alias[PATH_MAX];
char modname[PATH_MAX];
char value[PATH_MAX];
size_t len;

len = flush_stream_to(in, '.', modname, sizeof(modname));
modname[len] = '\0';
if (!len)
continue;

len = flush_stream_to(in, '=', value, sizeof(value));
value[len] = '\0';
if (!streq(value, "alias")) {
flush_stream(in, '\0');
continue;
}

len = flush_stream_to(in, '\0', value, sizeof(value));
value[len] = '\0';
if (!len)
continue;

alias[0] = '\0';
if (alias_normalize(value, alias, NULL) < 0) {
WRN("Unmatched bracket in %s\n", value);
continue;
}

index_insert(idx, alias, modname, 0);
}

if (ferror(in)) {
ret = -EINVAL;
} else {
index_write(idx, out);
ret = 0;
}

index_destroy(idx);
fclose(in);

return ret;
}

static int output_devname(struct depmod *depmod, FILE *out)
{
size_t i;
Expand Down Expand Up @@ -2403,71 +2558,6 @@ static int output_devname(struct depmod *depmod, FILE *out)
return 0;
}

static int output_builtin_alias_bin(struct depmod *depmod, FILE *out)
{
int ret = 0, count = 0;
struct index_node *idx;
struct kmod_list *l, *builtin = NULL;

if (out == stdout)
return 0;

idx = index_create();
if (idx == NULL)
return -ENOMEM;

ret = kmod_module_get_builtin(depmod->ctx, &builtin);
if (ret < 0) {
if (ret == -ENOENT)
ret = 0;
goto out;
}

kmod_list_foreach(l, builtin) {
struct kmod_list *ll, *info_list = NULL;
struct kmod_module *mod = l->data;
const char *modname = kmod_module_get_name(mod);

ret = kmod_module_get_info(mod, &info_list);
if (ret < 0)
goto out;

kmod_list_foreach(ll, info_list) {
char alias[PATH_MAX];
const char *key = kmod_module_info_get_key(ll);
const char *value = kmod_module_info_get_value(ll);

if (!streq(key, "alias"))
continue;

alias[0] = '\0';
if (alias_normalize(value, alias, NULL) < 0) {
WRN("Unmatched bracket in %s\n", value);
continue;
}

index_insert(idx, alias, modname, 0);
}

kmod_module_info_free_list(info_list);

index_insert(idx, modname, modname, 0);
count++;
}

out:
/* do not bother writing the index if we are going to discard it */
if (ret > 0)
index_write(idx, out);

if (builtin)
kmod_module_unref_list(builtin);

index_destroy(idx);

return ret;
}

static int depmod_output(struct depmod *depmod, FILE *out)
{
static const struct depfile {
Expand Down
32 changes: 29 additions & 3 deletions tools/modinfo.c
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,24 @@ static int modinfo_path_do(struct kmod_ctx *ctx, const char *path)
return err;
}

static int modinfo_name_do(struct kmod_ctx *ctx, const char *name)
{
struct kmod_module *mod = NULL;
int err;

err = kmod_module_new_from_name_lookup(ctx, name, &mod);
if (err < 0 || mod == NULL) {
ERR("Module name %s not found.\n", name);
return err < 0 ? err : -ENOENT;
}

err = modinfo_do(mod);
kmod_module_unref(mod);

return err;
}


static int modinfo_alias_do(struct kmod_ctx *ctx, const char *alias)
{
struct kmod_list *l, *list = NULL;
Expand All @@ -318,14 +336,15 @@ static int modinfo_alias_do(struct kmod_ctx *ctx, const char *alias)
return err;
}

static const char cmdopts_s[] = "adlpn0F:k:b:Vh";
static const char cmdopts_s[] = "adlpn0mF:k:b:Vh";
static const struct option cmdopts[] = {
{"author", no_argument, 0, 'a'},
{"description", no_argument, 0, 'd'},
{"license", no_argument, 0, 'l'},
{"parameters", no_argument, 0, 'p'},
{"filename", no_argument, 0, 'n'},
{"null", no_argument, 0, '0'},
{"modname", no_argument, 0, 'm'},
{"field", required_argument, 0, 'F'},
{"set-version", required_argument, 0, 'k'},
{"basedir", required_argument, 0, 'b'},
Expand All @@ -337,14 +356,15 @@ static const struct option cmdopts[] = {
static void help(void)
{
printf("Usage:\n"
"\t%s [options] filename [args]\n"
"\t%s [options] <modulename|filename> [args]\n"
"Options:\n"
"\t-a, --author Print only 'author'\n"
"\t-d, --description Print only 'description'\n"
"\t-l, --license Print only 'license'\n"
"\t-p, --parameters Print only 'parm'\n"
"\t-n, --filename Print only 'filename'\n"
"\t-0, --null Use \\0 instead of \\n\n"
"\t-m, --modname Handle argument as module name instead of alias or filename\n"
"\t-F, --field=FIELD Print only provided FIELD\n"
"\t-k, --set-version=VERSION Use VERSION instead of `uname -r`\n"
"\t-b, --basedir=DIR Use DIR as filesystem root for /lib/modules\n"
Expand Down Expand Up @@ -372,6 +392,7 @@ static int do_modinfo(int argc, char *argv[])
const char *kversion = NULL;
const char *root = NULL;
const char *null_config = NULL;
bool arg_is_modname = false;
int i, err;

for (;;) {
Expand All @@ -398,6 +419,9 @@ static int do_modinfo(int argc, char *argv[])
case '0':
separator = '\0';
break;
case 'm':
arg_is_modname = true;
break;
case 'F':
field = optarg;
break;
Expand Down Expand Up @@ -454,7 +478,9 @@ static int do_modinfo(int argc, char *argv[])
const char *name = argv[i];
int r;

if (is_module_filename(name))
if (arg_is_modname)
r = modinfo_name_do(ctx, name);
else if (is_module_filename(name))
r = modinfo_path_do(ctx, name);
else
r = modinfo_alias_do(ctx, name);
Expand Down
144 changes: 104 additions & 40 deletions tools/modprobe.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <sys/wait.h>

#include <shared/array.h>
#include <shared/util.h>
#include <shared/macro.h>

#include <libkmod/libkmod.h>
Expand All @@ -54,14 +55,19 @@ 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 int remove_holders = 0;
static unsigned long long wait_msec = 0;
static int quiet_inuse = 0;

static const char cmdopts_s[] = "arRibfDcnC:d:S:sqvVh";
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'},
Expand Down Expand Up @@ -107,8 +113,11 @@ static void help(void)
"\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 Also remove modules depending on it\n"
"\t-R, --resolve-alias Only lookup and print alias and exit\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"
Expand All @@ -120,6 +129,7 @@ static void help(void)
"\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"
Expand Down Expand Up @@ -320,44 +330,67 @@ static int command_do(struct kmod_module *module, const char *type,
static int rmmod_do_remove_module(struct kmod_module *mod)
{
const char *modname = kmod_module_get_name(mod);
struct kmod_list *deps, *itr;
unsigned long long interval_msec = 0, t0_msec = 0,
tend_msec = 0;
int flags = 0, err;

SHOW("rmmod %s\n", kmod_module_get_name(mod));
SHOW("rmmod %s\n", modname);

if (dry_run)
return 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);
}
if (wait_msec)
flags |= KMOD_REMOVE_NOLOG;

deps = kmod_module_get_dependencies(mod);
if (deps != NULL) {
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);
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;
}
kmod_module_unref_list(deps);
}
} while (interval_msec);

if (err < 0 && wait_msec)
ERR("could not remove '%s': %s\n", modname, strerror(-err));

return err;
}

#define RMMOD_FLAG_DO_DEPENDENCIES 0x1
#define RMMOD_FLAG_REMOVE_HOLDERS 0x1
#define RMMOD_FLAG_IGNORE_BUILTIN 0x2
static int rmmod_do_module(struct kmod_module *mod, int flags);

static int rmmod_do_deps_list(struct kmod_list *list, bool stop_on_errors)
/* Remove modules in reverse order */
static int rmmod_do_modlist(struct kmod_list *list, bool stop_on_errors)
{
struct kmod_list *l;

Expand Down Expand Up @@ -391,7 +424,8 @@ static int rmmod_do_module(struct kmod_module *mod, int flags)
cmd = kmod_module_get_remove_commands(mod);
}

if (cmd == NULL && !ignore_loaded) {
/* 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) {
Expand All @@ -413,17 +447,20 @@ static int rmmod_do_module(struct kmod_module *mod, int flags)
}
}

rmmod_do_deps_list(post, false);
/* 1. @mod's post-softdeps in reverse order */
rmmod_do_modlist(post, false);

if ((flags & RMMOD_FLAG_DO_DEPENDENCIES) && remove_dependencies) {
struct kmod_list *deps = kmod_module_get_dependencies(mod);
/* 2. Other modules holding @mod */
if (flags & RMMOD_FLAG_REMOVE_HOLDERS) {
struct kmod_list *holders = kmod_module_get_holders(mod);

err = rmmod_do_deps_list(deps, true);
err = rmmod_do_modlist(holders, true);
if (err < 0)
goto error;
}

if (!ignore_loaded && !cmd) {
/* 3. @mod itself, but check for refcnt first */
if (!cmd && !ignore_loaded && !wait_msec) {
int usage = kmod_module_get_refcnt(mod);

if (usage > 0) {
Expand All @@ -435,15 +472,30 @@ static int rmmod_do_module(struct kmod_module *mod, int flags)
}
}

if (cmd == NULL)
if (!cmd)
err = rmmod_do_remove_module(mod);
else
err = command_do(mod, "remove", cmd, NULL);

if (err < 0)
goto error;

rmmod_do_deps_list(pre, false);
/* 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);
Expand All @@ -468,7 +520,9 @@ static int rmmod(struct kmod_ctx *ctx, const char *alias)

kmod_list_foreach(l, list) {
struct kmod_module *mod = kmod_module_get_module(l);
err = rmmod_do_module(mod, RMMOD_FLAG_DO_DEPENDENCIES);
int flags = remove_holders ? RMMOD_FLAG_REMOVE_HOLDERS : 0;

err = rmmod_do_module(mod, flags);
kmod_module_unref(mod);
if (err < 0)
break;
Expand Down Expand Up @@ -683,7 +737,7 @@ static int options_from_array(char **args, int nargs, char **output)
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;
char **new_argv, *str_end, *str, *s, *quote;
int i, argc = *p_argc;
size_t envlen, space_count = 0;

Expand All @@ -701,10 +755,10 @@ static char **prepend_options_from_env(int *p_argc, char **orig_argv)
return NULL;

new_argv[0] = orig_argv[0];
str_start = str = (char *) (new_argv + argc + space_count + 3);
str = (char *) (new_argv + argc + space_count + 3);
memcpy(str, env, envlen + 1);

str_end = str_start + envlen;
str_end = str + envlen;

quote = NULL;
for (i = 1, s = str; *s != '\0'; s++) {
Expand Down Expand Up @@ -743,7 +797,7 @@ static char **prepend_options_from_env(int *p_argc, char **orig_argv)
}

memcpy(new_argv + i, orig_argv + 1, sizeof(char *) * (argc - 1));
new_argv[i + argc] = NULL;
new_argv[i + argc - 1] = NULL;
*p_argc = i + argc - 1;

return new_argv;
Expand Down Expand Up @@ -786,11 +840,18 @@ static int do_modprobe(int argc, char **orig_argv)
do_remove = 1;
break;
case 5:
remove_dependencies = 1;
remove_holders = 1;
break;
case 'R':
lookup_only = 1;
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;
Expand All @@ -814,6 +875,9 @@ static int do_modprobe(int argc, char **orig_argv)
dry_run = 1;
do_show = 1;
break;
case 'R':
lookup_only = 1;
break;
case 'c':
do_show_config = 1;
break;
Expand Down