mirror of
https://github.com/AuxXxilium/kmod.git
synced 2024-12-28 06:15:20 +07:00
ece09aac7f
This field can be used to iterate the modules, controlling whether we are revisiting a certain module. A function to clear the values in all modules is needed since when we are iterating, we don't know if the module is created anew or if it's picked from the pool. Therefore we can't know if the field is true because of a previous iteration or if the module was indeed already visited.
2592 lines
60 KiB
C
2592 lines
60 KiB
C
/*
|
|
* libkmod - interface to kernel module operations
|
|
*
|
|
* Copyright (C) 2011-2012 ProFUSION embedded systems
|
|
*
|
|
* This library 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; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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 library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/wait.h>
|
|
#include <string.h>
|
|
#include <fnmatch.h>
|
|
|
|
#include "libkmod.h"
|
|
#include "libkmod-private.h"
|
|
|
|
/**
|
|
* SECTION:libkmod-module
|
|
* @short_description: operate on kernel modules
|
|
*/
|
|
|
|
/**
|
|
* kmod_module:
|
|
*
|
|
* Opaque object representing a module.
|
|
*/
|
|
struct kmod_module {
|
|
struct kmod_ctx *ctx;
|
|
char *hashkey;
|
|
char *name;
|
|
char *path;
|
|
struct kmod_list *dep;
|
|
char *options;
|
|
const char *install_commands; /* owned by kmod_config */
|
|
const char *remove_commands; /* owned by kmod_config */
|
|
char *alias; /* only set if this module was created from an alias */
|
|
int n_dep;
|
|
int refcount;
|
|
struct {
|
|
bool dep : 1;
|
|
bool options : 1;
|
|
bool install_commands : 1;
|
|
bool remove_commands : 1;
|
|
} init;
|
|
bool visited : 1;
|
|
};
|
|
|
|
static inline const char *path_join(const char *path, size_t prefixlen,
|
|
char buf[PATH_MAX])
|
|
{
|
|
size_t pathlen;
|
|
|
|
if (path[0] == '/')
|
|
return path;
|
|
|
|
pathlen = strlen(path);
|
|
if (prefixlen + pathlen + 1 >= PATH_MAX)
|
|
return NULL;
|
|
|
|
memcpy(buf + prefixlen, path, pathlen + 1);
|
|
return buf;
|
|
}
|
|
|
|
int kmod_module_parse_depline(struct kmod_module *mod, char *line)
|
|
{
|
|
struct kmod_ctx *ctx = mod->ctx;
|
|
struct kmod_list *list = NULL;
|
|
const char *dirname;
|
|
char buf[PATH_MAX];
|
|
char *p, *saveptr;
|
|
int err = 0, n = 0;
|
|
size_t dirnamelen;
|
|
|
|
if (mod->init.dep)
|
|
return mod->n_dep;
|
|
assert(mod->dep == NULL);
|
|
mod->init.dep = true;
|
|
|
|
p = strchr(line, ':');
|
|
if (p == NULL)
|
|
return 0;
|
|
|
|
*p = '\0';
|
|
dirname = kmod_get_dirname(mod->ctx);
|
|
dirnamelen = strlen(dirname);
|
|
if (dirnamelen + 2 >= PATH_MAX)
|
|
return 0;
|
|
|
|
memcpy(buf, dirname, dirnamelen);
|
|
buf[dirnamelen] = '/';
|
|
dirnamelen++;
|
|
buf[dirnamelen] = '\0';
|
|
|
|
if (mod->path == NULL) {
|
|
const char *str = path_join(line, dirnamelen, buf);
|
|
if (str == NULL)
|
|
return 0;
|
|
mod->path = strdup(str);
|
|
if (mod->path == NULL)
|
|
return 0;
|
|
}
|
|
|
|
p++;
|
|
for (p = strtok_r(p, " \t", &saveptr); p != NULL;
|
|
p = strtok_r(NULL, " \t", &saveptr)) {
|
|
struct kmod_module *depmod;
|
|
const char *path;
|
|
|
|
path = path_join(p, dirnamelen, buf);
|
|
if (path == NULL) {
|
|
ERR(ctx, "could not join path '%s' and '%s'.\n",
|
|
dirname, p);
|
|
goto fail;
|
|
}
|
|
|
|
err = kmod_module_new_from_path(ctx, path, &depmod);
|
|
if (err < 0) {
|
|
ERR(ctx, "ctx=%p path=%s error=%s\n",
|
|
ctx, path, strerror(-err));
|
|
goto fail;
|
|
}
|
|
|
|
DBG(ctx, "add dep: %s\n", path);
|
|
|
|
list = kmod_list_prepend(list, depmod);
|
|
n++;
|
|
}
|
|
|
|
DBG(ctx, "%d dependencies for %s\n", n, mod->name);
|
|
|
|
mod->dep = list;
|
|
mod->n_dep = n;
|
|
return n;
|
|
|
|
fail:
|
|
kmod_module_unref_list(list);
|
|
mod->init.dep = false;
|
|
return err;
|
|
}
|
|
|
|
void kmod_module_set_visited(struct kmod_module *mod, bool visited)
|
|
{
|
|
mod->visited = visited;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_new_from_name:
|
|
* @ctx: kmod library context
|
|
* @name: name of the module
|
|
* @mod: where to save the created struct kmod_module
|
|
*
|
|
* Create a new struct kmod_module using the module name. @name can not be an
|
|
* alias, file name or anything else; it must be a module name. There's no
|
|
* check if the module does exists in the system.
|
|
*
|
|
* This function is also used internally by many others that return a new
|
|
* struct kmod_module or a new list of modules.
|
|
*
|
|
* 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
|
|
* kmod_module are done.
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise. It fails if name is not a valid
|
|
* module name or if memory allocation failed.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_new_from_name(struct kmod_ctx *ctx,
|
|
const char *name,
|
|
struct kmod_module **mod)
|
|
{
|
|
struct kmod_module *m;
|
|
size_t namelen;
|
|
char name_norm[PATH_MAX];
|
|
char *namesep;
|
|
|
|
if (ctx == NULL || name == NULL || mod == NULL)
|
|
return -ENOENT;
|
|
|
|
if (alias_normalize(name, name_norm, &namelen) < 0) {
|
|
DBG(ctx, "invalid alias: %s\n", name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
m = kmod_pool_get_module(ctx, name_norm);
|
|
if (m != NULL) {
|
|
*mod = kmod_module_ref(m);
|
|
return 0;
|
|
}
|
|
|
|
namesep = strchr(name_norm, '/');
|
|
m = malloc(sizeof(*m) + (namesep == NULL ? 1 : 2) * namelen + 2);
|
|
if (m == NULL) {
|
|
free(m);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memset(m, 0, sizeof(*m));
|
|
|
|
m->ctx = kmod_ref(ctx);
|
|
m->name = (char *)m + sizeof(*m);
|
|
memcpy(m->name, name_norm, namelen + 1);
|
|
|
|
if (namesep) {
|
|
size_t len = namesep - name_norm;
|
|
|
|
m->name[len] = '\0';
|
|
m->alias = m->name + len + 1;
|
|
m->hashkey = m->name + namelen + 1;
|
|
memcpy(m->hashkey, name_norm, namelen + 1);
|
|
} else {
|
|
m->hashkey = m->name;
|
|
}
|
|
|
|
m->refcount = 1;
|
|
kmod_pool_add_module(ctx, m, m->hashkey);
|
|
*mod = m;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kmod_module_new_from_alias(struct kmod_ctx *ctx, const char *alias,
|
|
const char *name, struct kmod_module **mod)
|
|
{
|
|
int err;
|
|
char key[PATH_MAX];
|
|
size_t namelen = strlen(name);
|
|
size_t aliaslen = strlen(alias);
|
|
|
|
if (namelen + aliaslen + 2 > PATH_MAX)
|
|
return -ENAMETOOLONG;
|
|
|
|
memcpy(key, name, namelen);
|
|
memcpy(key + namelen + 1, alias, aliaslen + 1);
|
|
key[namelen] = '/';
|
|
|
|
err = kmod_module_new_from_name(ctx, key, mod);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_new_from_path:
|
|
* @ctx: kmod library context
|
|
* @path: path where to find the given module
|
|
* @mod: where to save the created struct kmod_module
|
|
*
|
|
* Create a new struct kmod_module using the module path. @path must be an
|
|
* existent file with in the filesystem and must be accessible to libkmod.
|
|
*
|
|
* 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
|
|
* kmod_module are done.
|
|
*
|
|
* If @path is relative, it's treated as relative to the current working
|
|
* directory. Otherwise, give an absolute path.
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise. It fails if file does not exist, if
|
|
* it's not a valid file for a kmod_module or if memory allocation failed.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_new_from_path(struct kmod_ctx *ctx,
|
|
const char *path,
|
|
struct kmod_module **mod)
|
|
{
|
|
struct kmod_module *m;
|
|
int err;
|
|
struct stat st;
|
|
char name[PATH_MAX];
|
|
char *abspath;
|
|
size_t namelen;
|
|
|
|
if (ctx == NULL || path == NULL || mod == NULL)
|
|
return -ENOENT;
|
|
|
|
abspath = path_make_absolute_cwd(path);
|
|
if (abspath == NULL) {
|
|
DBG(ctx, "no absolute path for %s\n", path);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
err = stat(abspath, &st);
|
|
if (err < 0) {
|
|
err = -errno;
|
|
DBG(ctx, "stat %s: %s\n", path, strerror(errno));
|
|
free(abspath);
|
|
return err;
|
|
}
|
|
|
|
if (path_to_modname(path, name, &namelen) == NULL) {
|
|
DBG(ctx, "could not get modname from path %s\n", path);
|
|
free(abspath);
|
|
return -ENOENT;
|
|
}
|
|
|
|
m = kmod_pool_get_module(ctx, name);
|
|
if (m != NULL) {
|
|
if (m->path == NULL)
|
|
m->path = abspath;
|
|
else if (streq(m->path, abspath))
|
|
free(abspath);
|
|
else {
|
|
ERR(ctx, "kmod_module '%s' already exists with different path: new-path='%s' old-path='%s'\n",
|
|
name, abspath, m->path);
|
|
free(abspath);
|
|
return -EEXIST;
|
|
}
|
|
|
|
*mod = kmod_module_ref(m);
|
|
return 0;
|
|
}
|
|
|
|
m = malloc(sizeof(*m) + namelen + 1);
|
|
if (m == NULL)
|
|
return -errno;
|
|
|
|
memset(m, 0, sizeof(*m));
|
|
|
|
m->ctx = kmod_ref(ctx);
|
|
m->name = (char *)m + sizeof(*m);
|
|
memcpy(m->name, name, namelen + 1);
|
|
m->path = abspath;
|
|
m->hashkey = m->name;
|
|
m->refcount = 1;
|
|
|
|
kmod_pool_add_module(ctx, m, m->hashkey);
|
|
|
|
*mod = m;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_unref:
|
|
* @mod: kmod module
|
|
*
|
|
* Drop a reference of the kmod module. If the refcount reaches zero, its
|
|
* resources are released.
|
|
*
|
|
* Returns: NULL if @mod is NULL or if the module was released. Otherwise it
|
|
* returns the passed @mod with its refcount decremented.
|
|
*/
|
|
KMOD_EXPORT struct kmod_module *kmod_module_unref(struct kmod_module *mod)
|
|
{
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
if (--mod->refcount > 0)
|
|
return mod;
|
|
|
|
DBG(mod->ctx, "kmod_module %p released\n", mod);
|
|
|
|
kmod_pool_del_module(mod->ctx, mod, mod->hashkey);
|
|
kmod_module_unref_list(mod->dep);
|
|
kmod_unref(mod->ctx);
|
|
free(mod->options);
|
|
free(mod->path);
|
|
free(mod);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_ref:
|
|
* @mod: kmod module
|
|
*
|
|
* Take a reference of the kmod module, incrementing its refcount.
|
|
*
|
|
* Returns: the passed @module with its refcount incremented.
|
|
*/
|
|
KMOD_EXPORT struct kmod_module *kmod_module_ref(struct kmod_module *mod)
|
|
{
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
mod->refcount++;
|
|
|
|
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)
|
|
|
|
/**
|
|
* kmod_module_new_from_lookup:
|
|
* @ctx: kmod library context
|
|
* @given_alias: alias to look for
|
|
* @list: an empty list where to save the list of modules matching
|
|
* @given_alias
|
|
*
|
|
* Create a new list of kmod modules using an alias or module name and lookup
|
|
* libkmod's configuration files and indexes in order to find the module.
|
|
* Once it's found in one of the places, it stops searching and create the
|
|
* list of modules that is saved in @list.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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
|
|
* calling kmod_module_unref_list(). 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 fail. If module
|
|
* is not found, it still returns 0, but @list is an empty list.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_new_from_lookup(struct kmod_ctx *ctx,
|
|
const char *given_alias,
|
|
struct kmod_list **list)
|
|
{
|
|
int err;
|
|
char alias[PATH_MAX];
|
|
|
|
if (ctx == NULL || given_alias == NULL)
|
|
return -ENOENT;
|
|
|
|
if (list == NULL || *list != NULL) {
|
|
ERR(ctx, "An empty list is needed to create lookup\n");
|
|
return -ENOSYS;
|
|
}
|
|
|
|
if (alias_normalize(given_alias, alias, NULL) < 0) {
|
|
DBG(ctx, "invalid alias: %s\n", given_alias);
|
|
return -EINVAL;
|
|
}
|
|
|
|
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);
|
|
|
|
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 modules.symbols %s\n", alias);
|
|
err = kmod_lookup_alias_from_symbols_file(ctx, alias, list);
|
|
CHECK_ERR_AND_FINISH(err, fail, list, finish);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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:
|
|
* @list: list of kmod modules
|
|
*
|
|
* Drop a reference of each kmod module in @list and releases the resources
|
|
* taken by the list itself.
|
|
*
|
|
* Returns: NULL if @mod is NULL or if the module was released. Otherwise it
|
|
* returns the passed @mod with its refcount decremented.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_unref_list(struct kmod_list *list)
|
|
{
|
|
for (; list != NULL; list = kmod_list_remove(list))
|
|
kmod_module_unref(list->data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_filtered_blacklist:
|
|
* @ctx: kmod library context
|
|
* @input: list of kmod_module to be filtered with blacklist
|
|
* @output: where to save the new list
|
|
*
|
|
* Given a list @input, this function filter it out with config's blacklist
|
|
* ans save it in @output.
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise. @output is saved with the updated
|
|
* list.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx,
|
|
const struct kmod_list *input,
|
|
struct kmod_list **output)
|
|
{
|
|
const struct kmod_list *li;
|
|
const struct kmod_list *blacklist;
|
|
|
|
if (ctx == NULL || output == NULL)
|
|
return -ENOENT;
|
|
|
|
*output = NULL;
|
|
if (input == NULL)
|
|
return 0;
|
|
|
|
blacklist = kmod_get_blacklists(ctx);
|
|
kmod_list_foreach(li, input) {
|
|
struct kmod_module *mod = li->data;
|
|
const struct kmod_list *lb;
|
|
struct kmod_list *node;
|
|
bool filtered = false;
|
|
|
|
kmod_list_foreach(lb, blacklist) {
|
|
const char *name = lb->data;
|
|
|
|
if (streq(name, mod->name)) {
|
|
filtered = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (filtered)
|
|
continue;
|
|
|
|
node = kmod_list_append(*output, mod);
|
|
if (node == NULL)
|
|
goto fail;
|
|
|
|
*output = node;
|
|
kmod_module_ref(mod);
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
kmod_module_unref_list(*output);
|
|
*output = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static const struct kmod_list *module_get_dependencies_noref(const struct kmod_module *mod)
|
|
{
|
|
if (!mod->init.dep) {
|
|
/* lazy init */
|
|
char *line = kmod_search_moddep(mod->ctx, mod->name);
|
|
|
|
if (line == NULL)
|
|
return NULL;
|
|
|
|
kmod_module_parse_depline((struct kmod_module *)mod, line);
|
|
free(line);
|
|
|
|
if (!mod->init.dep)
|
|
return NULL;
|
|
}
|
|
|
|
return mod->dep;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_dependencies:
|
|
* @mod: kmod module
|
|
*
|
|
* Search the modules.dep index to find the dependencies of the given @mod.
|
|
* The result is cached in @mod, so subsequent calls to this function will
|
|
* return the already searched list of modules.
|
|
*
|
|
* Returns: NULL on failure or if there are any dependencies. Otherwise it
|
|
* returns a list of kmod modules that can be released by calling
|
|
* kmod_module_unref_list().
|
|
*/
|
|
KMOD_EXPORT struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod)
|
|
{
|
|
struct kmod_list *l, *l_new, *list_new = NULL;
|
|
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
module_get_dependencies_noref(mod);
|
|
|
|
kmod_list_foreach(l, mod->dep) {
|
|
l_new = kmod_list_append(list_new, kmod_module_ref(l->data));
|
|
if (l_new == NULL) {
|
|
kmod_module_unref(l->data);
|
|
goto fail;
|
|
}
|
|
|
|
list_new = l_new;
|
|
}
|
|
|
|
return list_new;
|
|
|
|
fail:
|
|
ERR(mod->ctx, "out of memory\n");
|
|
kmod_module_unref_list(list_new);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_module:
|
|
* @entry: an entry in a list of kmod modules.
|
|
*
|
|
* Get the kmod module of this @entry in the list, increasing its refcount.
|
|
* After it's used, unref it. Since the refcount is incremented upon return,
|
|
* you still have to call kmod_module_unref_list() to release the list of kmod
|
|
* modules.
|
|
*
|
|
* Returns: NULL on failure or the kmod_module contained in this list entry
|
|
* with its refcount incremented.
|
|
*/
|
|
KMOD_EXPORT struct kmod_module *kmod_module_get_module(const struct kmod_list *entry)
|
|
{
|
|
if (entry == NULL)
|
|
return NULL;
|
|
|
|
return kmod_module_ref(entry->data);
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_name:
|
|
* @mod: kmod module
|
|
*
|
|
* Get the name of this kmod module. Name is always available, independently
|
|
* if it was created by kmod_module_new_from_name() or another function and
|
|
* it's always normalized (dashes are replaced with underscores).
|
|
*
|
|
* Returns: the name of this kmod module.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_get_name(const struct kmod_module *mod)
|
|
{
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
return mod->name;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_path:
|
|
* @mod: kmod module
|
|
*
|
|
* Get the path of this kmod module. If this kmod module was not created by
|
|
* path, it can search the modules.dep index in order to find out the module
|
|
* under context's dirname.
|
|
*
|
|
* Returns: the path of this kmod module or NULL if such information is not
|
|
* available.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_get_path(const struct kmod_module *mod)
|
|
{
|
|
char *line;
|
|
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
DBG(mod->ctx, "name='%s' path='%s'\n", mod->name, mod->path);
|
|
|
|
if (mod->path != NULL)
|
|
return mod->path;
|
|
if (mod->init.dep)
|
|
return NULL;
|
|
|
|
/* lazy init */
|
|
line = kmod_search_moddep(mod->ctx, mod->name);
|
|
if (line == NULL)
|
|
return NULL;
|
|
|
|
kmod_module_parse_depline((struct kmod_module *) mod, line);
|
|
free(line);
|
|
|
|
return mod->path;
|
|
}
|
|
|
|
|
|
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
|
|
*
|
|
* Remove a module from Linux kernel.
|
|
*
|
|
* Returns: 0 on success or < 0 on failure.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod,
|
|
unsigned int flags)
|
|
{
|
|
int err;
|
|
|
|
if (mod == NULL)
|
|
return -ENOENT;
|
|
|
|
/* Filter out other flags */
|
|
flags &= (KMOD_REMOVE_FORCE | KMOD_REMOVE_NOWAIT);
|
|
|
|
err = delete_module(mod->name, flags);
|
|
if (err != 0) {
|
|
err = -errno;
|
|
ERR(mod->ctx, "could not remove '%s': %m\n", mod->name);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
extern long init_module(const void *mem, unsigned long len, const char *args);
|
|
|
|
/**
|
|
* kmod_module_insert_module:
|
|
* @mod: kmod module
|
|
* @flags: flags are not passed to Linux Kernel, but instead they dictate the
|
|
* behavior of this function.
|
|
* @options: module's options to pass to Linux Kernel.
|
|
*
|
|
* Insert a module in Linux kernel. It opens the file pointed by @mod,
|
|
* mmap'ing it and passing to kernel.
|
|
*
|
|
* Returns: 0 on success or < 0 on failure. If module is already loaded it
|
|
* returns -EEXIST.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
|
|
unsigned int flags,
|
|
const char *options)
|
|
{
|
|
int err;
|
|
const void *mem;
|
|
off_t size;
|
|
struct kmod_file *file;
|
|
struct kmod_elf *elf = NULL;
|
|
const char *path;
|
|
const char *args = options ? options : "";
|
|
|
|
if (mod == NULL)
|
|
return -ENOENT;
|
|
|
|
path = kmod_module_get_path(mod);
|
|
if (path == NULL) {
|
|
ERR(mod->ctx, "could not find module by name='%s'\n", mod->name);
|
|
return -ENOSYS;
|
|
}
|
|
|
|
file = kmod_file_open(mod->ctx, path);
|
|
if (file == NULL) {
|
|
err = -errno;
|
|
return err;
|
|
}
|
|
|
|
size = kmod_file_get_size(file);
|
|
mem = kmod_file_get_contents(file);
|
|
|
|
if (flags & (KMOD_INSERT_FORCE_VERMAGIC | KMOD_INSERT_FORCE_MODVERSION)) {
|
|
elf = kmod_elf_new(mem, size);
|
|
if (elf == NULL) {
|
|
err = -errno;
|
|
goto elf_failed;
|
|
}
|
|
|
|
if (flags & KMOD_INSERT_FORCE_MODVERSION) {
|
|
err = kmod_elf_strip_section(elf, "__versions");
|
|
if (err < 0)
|
|
INFO(mod->ctx, "Failed to strip modversion: %s\n", strerror(-err));
|
|
}
|
|
|
|
if (flags & KMOD_INSERT_FORCE_VERMAGIC) {
|
|
err = kmod_elf_strip_vermagic(elf);
|
|
if (err < 0)
|
|
INFO(mod->ctx, "Failed to strip vermagic: %s\n", strerror(-err));
|
|
}
|
|
|
|
mem = kmod_elf_get_memory(elf);
|
|
}
|
|
|
|
err = init_module(mem, size, args);
|
|
if (err < 0) {
|
|
err = -errno;
|
|
INFO(mod->ctx, "Failed to insert module '%s': %m\n", path);
|
|
}
|
|
|
|
if (elf != NULL)
|
|
kmod_elf_unref(elf);
|
|
elf_failed:
|
|
kmod_file_unref(file);
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool module_is_blacklisted(struct kmod_module *mod)
|
|
{
|
|
struct kmod_ctx *ctx = mod->ctx;
|
|
const struct kmod_list *bl = kmod_get_blacklists(ctx);
|
|
const struct kmod_list *l;
|
|
|
|
kmod_list_foreach(l, bl) {
|
|
const char *modname = kmod_blacklist_get_modname(l);
|
|
|
|
if (streq(modname, mod->name))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#define RECURSION_CHECK_STEP 10
|
|
#define RET_CHECK_NOLOOP_OR_FAIL(_ret, _flags, _label) \
|
|
do { \
|
|
if (_ret < 0) { \
|
|
if (_ret == -ELOOP || _ret == -ENOMEM \
|
|
|| (_flags & KMOD_PROBE_STOP_ON_FAILURE)) \
|
|
goto _label; \
|
|
} \
|
|
} while (0)
|
|
|
|
struct probe_insert_cb {
|
|
int (*run_install)(struct kmod_module *m, const char *cmd, void *data);
|
|
void *data;
|
|
};
|
|
|
|
int module_probe_insert_module(struct kmod_module *mod,
|
|
unsigned int flags, const char *extra_options,
|
|
struct probe_insert_cb *cb,
|
|
struct kmod_list *rec, unsigned int reccount);
|
|
|
|
static int command_do(struct kmod_module *mod, const char *type,
|
|
const char *cmd)
|
|
{
|
|
const char *modname = kmod_module_get_name(mod);
|
|
int err;
|
|
|
|
DBG(mod->ctx, "%s %s\n", type, cmd);
|
|
|
|
setenv("MODPROBE_MODULE", modname, 1);
|
|
err = system(cmd);
|
|
unsetenv("MODPROBE_MODULE");
|
|
|
|
if (err == -1 || WEXITSTATUS(err)) {
|
|
ERR(mod->ctx, "Error running %s command for %s\n",
|
|
type, modname);
|
|
if (err != -1)
|
|
err = -WEXITSTATUS(err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int module_do_install_commands(struct kmod_module *mod,
|
|
const char *options,
|
|
struct probe_insert_cb *cb)
|
|
{
|
|
const char *command = kmod_module_get_install_commands(mod);
|
|
char *p, *cmd;
|
|
int err;
|
|
size_t cmdlen, options_len, varlen;
|
|
|
|
assert(command);
|
|
|
|
if (options == NULL)
|
|
options = "";
|
|
|
|
options_len = strlen(options);
|
|
cmdlen = strlen(command);
|
|
varlen = sizeof("$CMDLINE_OPTS") - 1;
|
|
|
|
cmd = memdup(command, cmdlen + 1);
|
|
if (cmd == NULL)
|
|
return -ENOMEM;
|
|
|
|
while ((p = strstr(cmd, "$CMDLINE_OPTS")) != NULL) {
|
|
size_t prefixlen = p - cmd;
|
|
size_t suffixlen = cmdlen - prefixlen - varlen;
|
|
size_t slen = cmdlen - varlen + options_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, options, options_len);
|
|
memcpy(s + prefixlen + options_len, suffix, suffixlen);
|
|
s[slen] = '\0';
|
|
|
|
free(cmd);
|
|
cmd = s;
|
|
cmdlen = slen;
|
|
}
|
|
|
|
if (cb->run_install != NULL)
|
|
err = cb->run_install(mod, cmd, cb->data);
|
|
else
|
|
err = command_do(mod, "install", cmd);
|
|
|
|
free(cmd);
|
|
|
|
return err;
|
|
}
|
|
|
|
static bool module_dep_has_loop(const struct kmod_list *deps,
|
|
struct kmod_list *rec,
|
|
unsigned int reccount)
|
|
{
|
|
struct kmod_list *l;
|
|
struct kmod_module *mod;
|
|
|
|
if (reccount < RECURSION_CHECK_STEP || deps == NULL)
|
|
return false;
|
|
|
|
mod = deps->data;
|
|
reccount = 0;
|
|
kmod_list_foreach(l, rec) {
|
|
struct kmod_list *loop;
|
|
|
|
if (l->data != mod)
|
|
continue;
|
|
|
|
ERR(mod->ctx, "Dependency loop detected while inserting '%s'. Operation aborted\n",
|
|
mod->name);
|
|
|
|
for (loop = l; loop != NULL;
|
|
loop = kmod_list_next(rec, loop)) {
|
|
struct kmod_module *m = loop->data;
|
|
ERR(mod->ctx, "%s\n", m->name);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int module_do_insmod_dep(const struct kmod_list *deps,
|
|
unsigned int flags, struct probe_insert_cb *cb,
|
|
struct kmod_list *rec, unsigned int reccount)
|
|
{
|
|
const struct kmod_list *d;
|
|
int err = 0;
|
|
|
|
if (module_dep_has_loop(deps, rec, reccount))
|
|
return -ELOOP;
|
|
|
|
kmod_list_foreach(d, deps) {
|
|
struct kmod_module *dm = d->data;
|
|
struct kmod_list *tmp;
|
|
|
|
tmp = kmod_list_append(rec, dm);
|
|
if (tmp == NULL)
|
|
return -ENOMEM;
|
|
rec = tmp;
|
|
|
|
err = module_probe_insert_module(dm, flags, NULL, cb,
|
|
rec, reccount + 1);
|
|
|
|
rec = kmod_list_remove_n_latest(rec, 1);
|
|
RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish);
|
|
}
|
|
|
|
finish:
|
|
return err;
|
|
}
|
|
|
|
static char *module_options_concat(const char *opt, const char *xopt)
|
|
{
|
|
// TODO: we might need to check if xopt overrides options on opt
|
|
size_t optlen = opt == NULL ? 0 : strlen(opt);
|
|
size_t xoptlen = xopt == NULL ? 0 : strlen(xopt);
|
|
char *r;
|
|
|
|
if (optlen == 0 && xoptlen == 0)
|
|
return NULL;
|
|
|
|
r = malloc(optlen + xoptlen + 2);
|
|
|
|
if (opt != NULL) {
|
|
memcpy(r, opt, optlen);
|
|
r[optlen] = ' ';
|
|
optlen++;
|
|
}
|
|
|
|
if (xopt != NULL)
|
|
memcpy(r + optlen, xopt, xoptlen);
|
|
|
|
r[optlen + xoptlen] = '\0';
|
|
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* Do the probe_insert work recursively. We traverse the dependencies in
|
|
* depth-first order, checking the following conditions:
|
|
*
|
|
* - Is blacklisted?
|
|
* - Is install command?
|
|
* - Is already loaded?
|
|
*
|
|
* Then we insert the modules (calling module_do_insmod_dep(), which will
|
|
* re-enter this function) needed to load @mod in the following order:
|
|
*
|
|
* 1) pre-softdep
|
|
* 2) dependency
|
|
* 3) @mod
|
|
* 4) post-softdep
|
|
*/
|
|
int module_probe_insert_module(struct kmod_module *mod,
|
|
unsigned int flags, const char *extra_options,
|
|
struct probe_insert_cb *cb,
|
|
struct kmod_list *rec, unsigned int reccount)
|
|
{
|
|
int err;
|
|
const char *install_cmds;
|
|
const struct kmod_list *dep;
|
|
struct kmod_list *pre = NULL, *post = NULL;
|
|
char *options;
|
|
|
|
if ((flags & KMOD_PROBE_STOP_ON_BLACKLIST)
|
|
&& module_is_blacklisted(mod)) {
|
|
DBG(mod->ctx, "Stopping on '%s': blacklisted\n", mod->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
install_cmds = kmod_module_get_install_commands(mod);
|
|
if (install_cmds != NULL) {
|
|
if (flags & KMOD_PROBE_STOP_ON_COMMAND) {
|
|
DBG(mod->ctx, "Stopping on '%s': install command\n",
|
|
mod->name);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
int state = kmod_module_get_initstate(mod);
|
|
|
|
if (state == KMOD_MODULE_LIVE ||
|
|
state == KMOD_MODULE_COMING ||
|
|
state == KMOD_MODULE_BUILTIN)
|
|
return 0;
|
|
}
|
|
|
|
err = kmod_module_get_softdeps(mod, &pre, &post);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = module_do_insmod_dep(pre, flags, cb, rec, reccount);
|
|
RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish);
|
|
|
|
dep = module_get_dependencies_noref(mod);
|
|
err = module_do_insmod_dep(dep, flags, cb, rec, reccount);
|
|
RET_CHECK_NOLOOP_OR_FAIL(err, flags, finish);
|
|
|
|
options = module_options_concat(kmod_module_get_options(mod),
|
|
extra_options);
|
|
|
|
if (install_cmds != NULL)
|
|
err = module_do_install_commands(mod, options, cb);
|
|
else
|
|
err = kmod_module_insert_module(mod, flags, options);
|
|
|
|
free(options);
|
|
|
|
/*
|
|
* Ignore "already loaded" error. We need to check here because of
|
|
* race conditions. We checked first if module was already loaded but
|
|
* it may have been loaded between the check and the moment we try to
|
|
* insert it.
|
|
*/
|
|
if (err < 0 && err != -EEXIST && (flags & KMOD_PROBE_STOP_ON_FAILURE))
|
|
goto finish;
|
|
|
|
err = module_do_insmod_dep(post, flags, cb, rec, reccount);
|
|
|
|
finish:
|
|
kmod_module_unref_list(pre);
|
|
kmod_module_unref_list(post);
|
|
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_probe_insert_module:
|
|
* @mod: kmod module
|
|
* @flags: flags are not passed to Linux Kernel, but instead they dictate the
|
|
* behavior of this function.
|
|
* @extra_options: module's options to pass to Linux Kernel.
|
|
* @run_install: function to run when @mod is backed by a install command.
|
|
* @data: data to give back to @run_install callback
|
|
*
|
|
* Insert a module in Linux kernel resolving dependencies, soft dependencies
|
|
* install commands and applying blacklist.
|
|
*
|
|
* If @run_install is NULL, and the flag KMOD_PROBE_STOP_ON_COMMANDS is not
|
|
* given, this function will fork and exec by calling system(3). If you need
|
|
* control over the execution of an install command, give a callback function
|
|
* in @run_install.
|
|
*
|
|
* Returns: 0 on success or < 0 on failure.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_probe_insert_module(struct kmod_module *mod,
|
|
unsigned int flags, const char *extra_options,
|
|
int (*run_install)(struct kmod_module *m,
|
|
const char *cmd, void *data),
|
|
const void *data)
|
|
{
|
|
struct probe_insert_cb cb;
|
|
|
|
cb.run_install = run_install;
|
|
cb.data = (void *) data;
|
|
|
|
return module_probe_insert_module(mod, flags, extra_options, &cb,
|
|
NULL, 0);
|
|
}
|
|
|
|
#undef RECURSION_CHECK_STEP
|
|
#undef RET_CHECK_NOLOOP_OR_FAIL
|
|
|
|
|
|
/**
|
|
* kmod_module_get_options:
|
|
* @mod: kmod module
|
|
*
|
|
* Get options of this kmod module. Options come from the configuration file
|
|
* and are cached in @mod. The first call to this function will search for
|
|
* this module in configuration and subsequent calls return the cached string.
|
|
*
|
|
* Returns: a string with all the options separated by spaces. This string is
|
|
* owned by @mod, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_get_options(const struct kmod_module *mod)
|
|
{
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
if (!mod->init.options) {
|
|
/* lazy init */
|
|
struct kmod_module *m = (struct kmod_module *)mod;
|
|
const struct kmod_list *l, *ctx_options;
|
|
char *opts = NULL;
|
|
size_t optslen = 0;
|
|
|
|
ctx_options = kmod_get_options(mod->ctx);
|
|
|
|
kmod_list_foreach(l, ctx_options) {
|
|
const char *modname = kmod_option_get_modname(l);
|
|
const char *str;
|
|
size_t len;
|
|
void *tmp;
|
|
|
|
DBG(mod->ctx, "modname=%s mod->name=%s mod->alias=%s\n", modname, mod->name, mod->alias);
|
|
if (!(streq(modname, mod->name) || (mod->alias != NULL &&
|
|
streq(modname, mod->alias))))
|
|
continue;
|
|
|
|
DBG(mod->ctx, "passed = modname=%s mod->name=%s mod->alias=%s\n", modname, mod->name, mod->alias);
|
|
str = kmod_option_get_options(l);
|
|
len = strlen(str);
|
|
if (len < 1)
|
|
continue;
|
|
|
|
tmp = realloc(opts, optslen + len + 2);
|
|
if (tmp == NULL) {
|
|
free(opts);
|
|
goto failed;
|
|
}
|
|
|
|
opts = tmp;
|
|
|
|
if (optslen > 0) {
|
|
opts[optslen] = ' ';
|
|
optslen++;
|
|
}
|
|
|
|
memcpy(opts + optslen, str, len);
|
|
optslen += len;
|
|
opts[optslen] = '\0';
|
|
}
|
|
|
|
m->init.options = true;
|
|
m->options = opts;
|
|
}
|
|
|
|
return mod->options;
|
|
|
|
failed:
|
|
ERR(mod->ctx, "out of memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_install_commands:
|
|
* @mod: kmod module
|
|
*
|
|
* Get install commands for this kmod module. Install commands come from the
|
|
* configuration file and are cached in @mod. The first call to this function
|
|
* will search for this module in configuration and subsequent calls return
|
|
* the cached string. The install commands are returned as they were in the
|
|
* configuration, concatenated by ';'. No other processing is made in this
|
|
* string.
|
|
*
|
|
* Returns: a string with all install commands separated by semicolons. This
|
|
* string is owned by @mod, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_get_install_commands(const struct kmod_module *mod)
|
|
{
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
if (!mod->init.install_commands) {
|
|
/* lazy init */
|
|
struct kmod_module *m = (struct kmod_module *)mod;
|
|
const struct kmod_list *l, *ctx_install_commands;
|
|
|
|
ctx_install_commands = kmod_get_install_commands(mod->ctx);
|
|
|
|
kmod_list_foreach(l, ctx_install_commands) {
|
|
const char *modname = kmod_command_get_modname(l);
|
|
|
|
if (fnmatch(modname, mod->name, 0) != 0)
|
|
continue;
|
|
|
|
m->install_commands = kmod_command_get_command(l);
|
|
|
|
/*
|
|
* find only the first command, as modprobe from
|
|
* module-init-tools does
|
|
*/
|
|
break;
|
|
}
|
|
|
|
m->init.install_commands = true;
|
|
}
|
|
|
|
return mod->install_commands;
|
|
}
|
|
|
|
void kmod_module_set_install_commands(struct kmod_module *mod, const char *cmd)
|
|
{
|
|
mod->init.install_commands = true;
|
|
mod->install_commands = cmd;
|
|
}
|
|
|
|
static struct kmod_list *lookup_softdep(struct kmod_ctx *ctx, const char * const * array, unsigned int count)
|
|
{
|
|
struct kmod_list *ret = NULL;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
const char *depname = array[i];
|
|
struct kmod_list *lst = NULL;
|
|
int err;
|
|
|
|
err = kmod_module_new_from_lookup(ctx, depname, &lst);
|
|
if (err < 0) {
|
|
ERR(ctx, "failed to lookup soft dependency '%s', continuing anyway.\n", depname);
|
|
continue;
|
|
} else if (lst != NULL)
|
|
ret = kmod_list_append_list(ret, lst);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_softdeps:
|
|
* @mod: kmod module
|
|
* @pre: where to save the list of preceding soft dependencies.
|
|
* @post: where to save the list of post soft dependencies.
|
|
*
|
|
* Get soft dependencies for this kmod module. Soft dependencies come
|
|
* from configuration file and are not cached in @mod because it may include
|
|
* dependency cycles that would make we leak kmod_module. Any call
|
|
* to this function will search for this module in configuration, allocate a
|
|
* list and return the result.
|
|
*
|
|
* Both @pre and @post are newly created list of kmod_module and
|
|
* should be unreferenced with kmod_module_unref_list().
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_softdeps(const struct kmod_module *mod,
|
|
struct kmod_list **pre,
|
|
struct kmod_list **post)
|
|
{
|
|
const struct kmod_list *l, *ctx_softdeps;
|
|
|
|
if (mod == NULL || pre == NULL || post == NULL)
|
|
return -ENOENT;
|
|
|
|
assert(*pre == NULL);
|
|
assert(*post == NULL);
|
|
|
|
ctx_softdeps = kmod_get_softdeps(mod->ctx);
|
|
|
|
kmod_list_foreach(l, ctx_softdeps) {
|
|
const char *modname = kmod_softdep_get_name(l);
|
|
const char * const *array;
|
|
unsigned count;
|
|
|
|
if (fnmatch(modname, mod->name, 0) != 0)
|
|
continue;
|
|
|
|
array = kmod_softdep_get_pre(l, &count);
|
|
*pre = lookup_softdep(mod->ctx, array, count);
|
|
array = kmod_softdep_get_post(l, &count);
|
|
*post = lookup_softdep(mod->ctx, array, count);
|
|
|
|
/*
|
|
* find only the first command, as modprobe from
|
|
* module-init-tools does
|
|
*/
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_remove_commands:
|
|
* @mod: kmod module
|
|
*
|
|
* Get remove commands for this kmod module. Remove commands come from the
|
|
* configuration file and are cached in @mod. The first call to this function
|
|
* will search for this module in configuration and subsequent calls return
|
|
* the cached string. The remove commands are returned as they were in the
|
|
* configuration, concatenated by ';'. No other processing is made in this
|
|
* string.
|
|
*
|
|
* Returns: a string with all remove commands separated by semicolons. This
|
|
* string is owned by @mod, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_get_remove_commands(const struct kmod_module *mod)
|
|
{
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
if (!mod->init.remove_commands) {
|
|
/* lazy init */
|
|
struct kmod_module *m = (struct kmod_module *)mod;
|
|
const struct kmod_list *l, *ctx_remove_commands;
|
|
|
|
ctx_remove_commands = kmod_get_remove_commands(mod->ctx);
|
|
|
|
kmod_list_foreach(l, ctx_remove_commands) {
|
|
const char *modname = kmod_command_get_modname(l);
|
|
|
|
if (fnmatch(modname, mod->name, 0) != 0)
|
|
continue;
|
|
|
|
m->remove_commands = kmod_command_get_command(l);
|
|
|
|
/*
|
|
* find only the first command, as modprobe from
|
|
* module-init-tools does
|
|
*/
|
|
break;
|
|
}
|
|
|
|
m->init.remove_commands = true;
|
|
}
|
|
|
|
return mod->remove_commands;
|
|
}
|
|
|
|
void kmod_module_set_remove_commands(struct kmod_module *mod, const char *cmd)
|
|
{
|
|
mod->init.remove_commands = true;
|
|
mod->remove_commands = cmd;
|
|
}
|
|
|
|
/**
|
|
* SECTION:libkmod-loaded
|
|
* @short_description: currently loaded modules
|
|
*
|
|
* Information about currently loaded modules, as reported by Linux kernel.
|
|
* These information are not cached by libkmod and are always read from /sys
|
|
* and /proc/modules.
|
|
*/
|
|
|
|
/**
|
|
* kmod_module_new_from_loaded:
|
|
* @ctx: kmod library context
|
|
* @list: where to save the list of loaded modules
|
|
*
|
|
* Create a new list of kmod modules with all modules currently loaded in
|
|
* kernel. It uses /proc/modules to get the names of loaded modules and to
|
|
* create kmod modules by calling kmod_module_new_from_name() in each of them.
|
|
* They are put are put in @list in no particular order.
|
|
*
|
|
* 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
|
|
* calling kmod_module_unref_list(). 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 on error.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_new_from_loaded(struct kmod_ctx *ctx,
|
|
struct kmod_list **list)
|
|
{
|
|
struct kmod_list *l = NULL;
|
|
FILE *fp;
|
|
char line[4096];
|
|
|
|
if (ctx == NULL || list == NULL)
|
|
return -ENOENT;
|
|
|
|
fp = fopen("/proc/modules", "re");
|
|
if (fp == NULL) {
|
|
int err = -errno;
|
|
ERR(ctx, "could not open /proc/modules: %s\n", strerror(errno));
|
|
return err;
|
|
}
|
|
|
|
while (fgets(line, sizeof(line), fp)) {
|
|
struct kmod_module *m;
|
|
struct kmod_list *node;
|
|
int err;
|
|
char *saveptr, *name = strtok_r(line, " \t", &saveptr);
|
|
|
|
err = kmod_module_new_from_name(ctx, name, &m);
|
|
if (err < 0) {
|
|
ERR(ctx, "could not get module from name '%s': %s\n",
|
|
name, strerror(-err));
|
|
continue;
|
|
}
|
|
|
|
node = kmod_list_append(l, m);
|
|
if (node)
|
|
l = node;
|
|
else {
|
|
ERR(ctx, "out of memory\n");
|
|
kmod_module_unref(m);
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
*list = l;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_initstate_str:
|
|
* @state: the state as returned by kmod_module_get_initstate()
|
|
*
|
|
* Translate a initstate to a string.
|
|
*
|
|
* Returns: the string associated to the @state. This string is statically
|
|
* allocated, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_initstate_str(enum kmod_module_initstate state)
|
|
{
|
|
switch (state) {
|
|
case KMOD_MODULE_BUILTIN:
|
|
return "builtin";
|
|
case KMOD_MODULE_LIVE:
|
|
return "live";
|
|
case KMOD_MODULE_COMING:
|
|
return "coming";
|
|
case KMOD_MODULE_GOING:
|
|
return "going";
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_initstate:
|
|
* @mod: kmod module
|
|
*
|
|
* Get the initstate of this @mod, as returned by Linux Kernel, by reading
|
|
* /sys filesystem.
|
|
*
|
|
* Returns: < 0 on error or enum kmod_initstate if module is found in kernel.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_initstate(const struct kmod_module *mod)
|
|
{
|
|
char path[PATH_MAX], buf[32];
|
|
int fd, err, pathlen;
|
|
|
|
if (mod == NULL)
|
|
return -ENOENT;
|
|
|
|
pathlen = snprintf(path, sizeof(path),
|
|
"/sys/module/%s/initstate", mod->name);
|
|
fd = open(path, O_RDONLY|O_CLOEXEC);
|
|
if (fd < 0) {
|
|
err = -errno;
|
|
|
|
DBG(mod->ctx, "could not open '%s': %s\n",
|
|
path, strerror(-err));
|
|
|
|
if (pathlen > (int)sizeof("/initstate") - 1) {
|
|
struct stat st;
|
|
path[pathlen - (sizeof("/initstate") - 1)] = '\0';
|
|
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
|
|
return KMOD_MODULE_BUILTIN;
|
|
}
|
|
|
|
DBG(mod->ctx, "could not open '%s': %s\n",
|
|
path, strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
err = read_str_safe(fd, buf, sizeof(buf));
|
|
close(fd);
|
|
if (err < 0) {
|
|
ERR(mod->ctx, "could not read from '%s': %s\n",
|
|
path, strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
if (streq(buf, "live\n"))
|
|
return KMOD_MODULE_LIVE;
|
|
else if (streq(buf, "coming\n"))
|
|
return KMOD_MODULE_COMING;
|
|
else if (streq(buf, "going\n"))
|
|
return KMOD_MODULE_GOING;
|
|
|
|
ERR(mod->ctx, "unknown %s: '%s'\n", path, buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_size:
|
|
* @mod: kmod module
|
|
*
|
|
* Get the size of this kmod module as returned by Linux kernel. It reads the
|
|
* file /proc/modules to search for this module and get its size.
|
|
*
|
|
* Returns: the size of this kmod module.
|
|
*/
|
|
KMOD_EXPORT long kmod_module_get_size(const struct kmod_module *mod)
|
|
{
|
|
// FIXME TODO: this should be available from /sys/module/foo
|
|
FILE *fp;
|
|
char line[4096];
|
|
int lineno = 0;
|
|
long size = -ENOENT;
|
|
|
|
if (mod == NULL)
|
|
return -ENOENT;
|
|
|
|
fp = fopen("/proc/modules", "re");
|
|
if (fp == NULL) {
|
|
int err = -errno;
|
|
ERR(mod->ctx,
|
|
"could not open /proc/modules: %s\n", strerror(errno));
|
|
return err;
|
|
}
|
|
|
|
while (fgets(line, sizeof(line), fp)) {
|
|
char *saveptr, *endptr, *tok = strtok_r(line, " \t", &saveptr);
|
|
long value;
|
|
|
|
lineno++;
|
|
if (tok == NULL || !streq(tok, mod->name))
|
|
continue;
|
|
|
|
tok = strtok_r(NULL, " \t", &saveptr);
|
|
if (tok == NULL) {
|
|
ERR(mod->ctx,
|
|
"invalid line format at /proc/modules:%d\n", lineno);
|
|
break;
|
|
}
|
|
|
|
value = strtol(tok, &endptr, 10);
|
|
if (endptr == tok || *endptr != '\0') {
|
|
ERR(mod->ctx,
|
|
"invalid line format at /proc/modules:%d\n", lineno);
|
|
break;
|
|
}
|
|
|
|
size = value;
|
|
break;
|
|
}
|
|
fclose(fp);
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_refcnt:
|
|
* @mod: kmod module
|
|
*
|
|
* Get the ref count of this @mod, as returned by Linux Kernel, by reading
|
|
* /sys filesystem.
|
|
*
|
|
* Returns: 0 on success or < 0 on failure.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_refcnt(const struct kmod_module *mod)
|
|
{
|
|
char path[PATH_MAX];
|
|
long refcnt;
|
|
int fd, err;
|
|
|
|
if (mod == NULL)
|
|
return -ENOENT;
|
|
|
|
snprintf(path, sizeof(path), "/sys/module/%s/refcnt", mod->name);
|
|
fd = open(path, O_RDONLY|O_CLOEXEC);
|
|
if (fd < 0) {
|
|
err = -errno;
|
|
ERR(mod->ctx, "could not open '%s': %s\n",
|
|
path, strerror(errno));
|
|
return err;
|
|
}
|
|
|
|
err = read_str_long(fd, &refcnt, 10);
|
|
close(fd);
|
|
if (err < 0) {
|
|
ERR(mod->ctx, "could not read integer from '%s': '%s'\n",
|
|
path, strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
return (int)refcnt;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_holders:
|
|
* @mod: kmod module
|
|
*
|
|
* Get a list of kmod modules that are holding this @mod, as returned by Linux
|
|
* Kernel. After use, free the @list by calling kmod_module_unref_list().
|
|
*
|
|
* Returns: a new list of kmod modules on success or NULL on failure.
|
|
*/
|
|
KMOD_EXPORT struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod)
|
|
{
|
|
char dname[PATH_MAX];
|
|
struct kmod_list *list = NULL;
|
|
DIR *d;
|
|
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
snprintf(dname, sizeof(dname), "/sys/module/%s/holders", mod->name);
|
|
|
|
d = opendir(dname);
|
|
if (d == NULL) {
|
|
ERR(mod->ctx, "could not open '%s': %s\n",
|
|
dname, strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
for (;;) {
|
|
struct dirent de, *entp;
|
|
struct kmod_module *holder;
|
|
struct kmod_list *l;
|
|
int err;
|
|
|
|
err = readdir_r(d, &de, &entp);
|
|
if (err != 0) {
|
|
ERR(mod->ctx, "could not iterate for module '%s': %s\n",
|
|
mod->name, strerror(-err));
|
|
goto fail;
|
|
}
|
|
|
|
if (entp == NULL)
|
|
break;
|
|
|
|
if (de.d_name[0] == '.') {
|
|
if (de.d_name[1] == '\0' ||
|
|
(de.d_name[1] == '.' && de.d_name[2] == '\0'))
|
|
continue;
|
|
}
|
|
|
|
err = kmod_module_new_from_name(mod->ctx, de.d_name, &holder);
|
|
if (err < 0) {
|
|
ERR(mod->ctx, "could not create module for '%s': %s\n",
|
|
de.d_name, strerror(-err));
|
|
goto fail;
|
|
}
|
|
|
|
l = kmod_list_append(list, holder);
|
|
if (l != NULL) {
|
|
list = l;
|
|
} else {
|
|
ERR(mod->ctx, "out of memory\n");
|
|
kmod_module_unref(holder);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
closedir(d);
|
|
return list;
|
|
|
|
fail:
|
|
closedir(d);
|
|
kmod_module_unref_list(list);
|
|
return NULL;
|
|
}
|
|
|
|
struct kmod_module_section {
|
|
unsigned long address;
|
|
char name[];
|
|
};
|
|
|
|
static void kmod_module_section_free(struct kmod_module_section *section)
|
|
{
|
|
free(section);
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_sections:
|
|
* @mod: kmod module
|
|
*
|
|
* Get a list of kmod sections of this @mod, as returned by Linux Kernel. The
|
|
* structure contained in this list is internal to libkmod and their fields
|
|
* can be obtained by calling kmod_module_section_get_name() and
|
|
* kmod_module_section_get_address().
|
|
*
|
|
* After use, free the @list by calling kmod_module_section_free_list().
|
|
*
|
|
* Returns: a new list of kmod module sections on success or NULL on failure.
|
|
*/
|
|
KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod)
|
|
{
|
|
char dname[PATH_MAX];
|
|
struct kmod_list *list = NULL;
|
|
DIR *d;
|
|
int dfd;
|
|
|
|
if (mod == NULL)
|
|
return NULL;
|
|
|
|
snprintf(dname, sizeof(dname), "/sys/module/%s/sections", mod->name);
|
|
|
|
d = opendir(dname);
|
|
if (d == NULL) {
|
|
ERR(mod->ctx, "could not open '%s': %s\n",
|
|
dname, strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
dfd = dirfd(d);
|
|
|
|
for (;;) {
|
|
struct dirent de, *entp;
|
|
struct kmod_module_section *section;
|
|
struct kmod_list *l;
|
|
unsigned long address;
|
|
size_t namesz;
|
|
int fd, err;
|
|
|
|
err = readdir_r(d, &de, &entp);
|
|
if (err != 0) {
|
|
ERR(mod->ctx, "could not iterate for module '%s': %s\n",
|
|
mod->name, strerror(-err));
|
|
goto fail;
|
|
}
|
|
|
|
if (de.d_name[0] == '.') {
|
|
if (de.d_name[1] == '\0' ||
|
|
(de.d_name[1] == '.' && de.d_name[2] == '\0'))
|
|
continue;
|
|
}
|
|
|
|
fd = openat(dfd, de.d_name, O_RDONLY|O_CLOEXEC);
|
|
if (fd < 0) {
|
|
ERR(mod->ctx, "could not open '%s/%s': %m\n",
|
|
dname, de.d_name);
|
|
goto fail;
|
|
}
|
|
|
|
err = read_str_ulong(fd, &address, 16);
|
|
close(fd);
|
|
if (err < 0) {
|
|
ERR(mod->ctx, "could not read long from '%s/%s': %m\n",
|
|
dname, de.d_name);
|
|
goto fail;
|
|
}
|
|
|
|
namesz = strlen(de.d_name) + 1;
|
|
section = malloc(sizeof(*section) + namesz);
|
|
|
|
if (section == NULL) {
|
|
ERR(mod->ctx, "out of memory\n");
|
|
goto fail;
|
|
}
|
|
|
|
section->address = address;
|
|
memcpy(section->name, de.d_name, namesz);
|
|
|
|
l = kmod_list_append(list, section);
|
|
if (l != NULL) {
|
|
list = l;
|
|
} else {
|
|
ERR(mod->ctx, "out of memory\n");
|
|
free(section);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
closedir(d);
|
|
return list;
|
|
|
|
fail:
|
|
closedir(d);
|
|
kmod_module_unref_list(list);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_section_get_module_name:
|
|
* @entry: a list entry representing a kmod module section
|
|
*
|
|
* Get the name of a kmod module section.
|
|
*
|
|
* After use, free the @list by calling kmod_module_section_free_list().
|
|
*
|
|
* Returns: the name of this kmod module section on success or NULL on
|
|
* failure. The string is owned by the section, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_section_get_name(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_section *section;
|
|
|
|
if (entry == NULL)
|
|
return NULL;
|
|
|
|
section = entry->data;
|
|
return section->name;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_section_get_address:
|
|
* @entry: a list entry representing a kmod module section
|
|
*
|
|
* Get the address of a kmod module section.
|
|
*
|
|
* After use, free the @list by calling kmod_module_section_free_list().
|
|
*
|
|
* Returns: the address of this kmod module section on success or ULONG_MAX
|
|
* on failure.
|
|
*/
|
|
KMOD_EXPORT unsigned long kmod_module_section_get_address(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_section *section;
|
|
|
|
if (entry == NULL)
|
|
return (unsigned long)-1;
|
|
|
|
section = entry->data;
|
|
return section->address;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_section_free_list:
|
|
* @list: kmod module section list
|
|
*
|
|
* Release the resources taken by @list
|
|
*/
|
|
KMOD_EXPORT void kmod_module_section_free_list(struct kmod_list *list)
|
|
{
|
|
while (list) {
|
|
kmod_module_section_free(list->data);
|
|
list = kmod_list_remove(list);
|
|
}
|
|
}
|
|
|
|
struct kmod_module_info {
|
|
char *key;
|
|
char value[];
|
|
};
|
|
|
|
static struct kmod_module_info *kmod_module_info_new(const char *key, size_t keylen, const char *value, size_t valuelen)
|
|
{
|
|
struct kmod_module_info *info;
|
|
|
|
info = malloc(sizeof(struct kmod_module_info) + keylen + valuelen + 2);
|
|
if (info == NULL)
|
|
return NULL;
|
|
|
|
info->key = (char *)info + sizeof(struct kmod_module_info)
|
|
+ valuelen + 1;
|
|
memcpy(info->key, key, keylen);
|
|
info->key[keylen] = '\0';
|
|
memcpy(info->value, value, valuelen);
|
|
info->value[valuelen] = '\0';
|
|
return info;
|
|
}
|
|
|
|
static void kmod_module_info_free(struct kmod_module_info *info)
|
|
{
|
|
free(info);
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_info:
|
|
* @mod: kmod module
|
|
* @list: where to return list of module information. Use
|
|
* kmod_module_info_get_key() and
|
|
* kmod_module_info_get_value(). Release this list with
|
|
* kmod_module_info_free_list()
|
|
*
|
|
* Get a list of entries in ELF section ".modinfo", these contain
|
|
* alias, license, depends, vermagic and other keys with respective
|
|
* values.
|
|
*
|
|
* After use, free the @list by calling kmod_module_info_free_list().
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list)
|
|
{
|
|
struct kmod_file *file;
|
|
struct kmod_elf *elf;
|
|
const char *path;
|
|
const void *mem;
|
|
char **strings;
|
|
size_t size;
|
|
int i, count, ret = 0;
|
|
|
|
if (mod == NULL || list == NULL)
|
|
return -ENOENT;
|
|
|
|
assert(*list == NULL);
|
|
|
|
path = kmod_module_get_path(mod);
|
|
if (path == NULL)
|
|
return -ENOENT;
|
|
|
|
file = kmod_file_open(mod->ctx, path);
|
|
if (file == NULL)
|
|
return -errno;
|
|
|
|
size = kmod_file_get_size(file);
|
|
mem = kmod_file_get_contents(file);
|
|
|
|
elf = kmod_elf_new(mem, size);
|
|
if (elf == NULL) {
|
|
ret = -errno;
|
|
goto elf_open_error;
|
|
}
|
|
|
|
count = kmod_elf_get_strings(elf, ".modinfo", &strings);
|
|
if (count < 0) {
|
|
ret = count;
|
|
goto get_strings_error;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct kmod_module_info *info;
|
|
struct kmod_list *n;
|
|
const char *key, *value;
|
|
size_t keylen, valuelen;
|
|
|
|
key = strings[i];
|
|
value = strchr(key, '=');
|
|
if (value == NULL) {
|
|
keylen = strlen(key);
|
|
valuelen = 0;
|
|
} else {
|
|
keylen = value - key;
|
|
value++;
|
|
valuelen = strlen(value);
|
|
}
|
|
|
|
info = kmod_module_info_new(key, keylen, value, valuelen);
|
|
if (info == NULL) {
|
|
ret = -errno;
|
|
kmod_module_info_free_list(*list);
|
|
*list = NULL;
|
|
goto list_error;
|
|
}
|
|
|
|
n = kmod_list_append(*list, info);
|
|
if (n != NULL)
|
|
*list = n;
|
|
else {
|
|
kmod_module_info_free(info);
|
|
kmod_module_info_free_list(*list);
|
|
*list = NULL;
|
|
ret = -ENOMEM;
|
|
goto list_error;
|
|
}
|
|
}
|
|
ret = count;
|
|
|
|
list_error:
|
|
free(strings);
|
|
get_strings_error:
|
|
kmod_elf_unref(elf);
|
|
elf_open_error:
|
|
kmod_file_unref(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_info_get_key:
|
|
* @entry: a list entry representing a kmod module info
|
|
*
|
|
* Get the key of a kmod module info.
|
|
*
|
|
* Returns: the key of this kmod module info on success or NULL on
|
|
* failure. The string is owned by the info, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_info_get_key(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_info *info;
|
|
|
|
if (entry == NULL)
|
|
return NULL;
|
|
|
|
info = entry->data;
|
|
return info->key;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_info_get_value:
|
|
* @entry: a list entry representing a kmod module info
|
|
*
|
|
* Get the value of a kmod module info.
|
|
*
|
|
* Returns: the value of this kmod module info on success or NULL on
|
|
* failure. The string is owned by the info, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_info_get_value(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_info *info;
|
|
|
|
if (entry == NULL)
|
|
return NULL;
|
|
|
|
info = entry->data;
|
|
return info->value;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_info_free_list:
|
|
* @list: kmod module info list
|
|
*
|
|
* Release the resources taken by @list
|
|
*/
|
|
KMOD_EXPORT void kmod_module_info_free_list(struct kmod_list *list)
|
|
{
|
|
while (list) {
|
|
kmod_module_info_free(list->data);
|
|
list = kmod_list_remove(list);
|
|
}
|
|
}
|
|
|
|
struct kmod_module_version {
|
|
uint64_t crc;
|
|
char symbol[];
|
|
};
|
|
|
|
static struct kmod_module_version *kmod_module_versions_new(uint64_t crc, const char *symbol)
|
|
{
|
|
struct kmod_module_version *mv;
|
|
size_t symbollen = strlen(symbol) + 1;
|
|
|
|
mv = malloc(sizeof(struct kmod_module_version) + symbollen);
|
|
if (mv == NULL)
|
|
return NULL;
|
|
|
|
mv->crc = crc;
|
|
memcpy(mv->symbol, symbol, symbollen);
|
|
return mv;
|
|
}
|
|
|
|
static void kmod_module_version_free(struct kmod_module_version *version)
|
|
{
|
|
free(version);
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_versions:
|
|
* @mod: kmod module
|
|
* @list: where to return list of module versions. Use
|
|
* kmod_module_version_get_symbol() and
|
|
* kmod_module_version_get_crc(). Release this list with
|
|
* kmod_module_versions_free_list()
|
|
*
|
|
* Get a list of entries in ELF section "__versions".
|
|
*
|
|
* After use, free the @list by calling kmod_module_versions_free_list().
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_versions(const struct kmod_module *mod, struct kmod_list **list)
|
|
{
|
|
struct kmod_file *file;
|
|
struct kmod_elf *elf;
|
|
const char *path;
|
|
const void *mem;
|
|
struct kmod_modversion *versions;
|
|
size_t size;
|
|
int i, count, ret = 0;
|
|
|
|
if (mod == NULL || list == NULL)
|
|
return -ENOENT;
|
|
|
|
assert(*list == NULL);
|
|
|
|
path = kmod_module_get_path(mod);
|
|
if (path == NULL)
|
|
return -ENOENT;
|
|
|
|
file = kmod_file_open(mod->ctx, path);
|
|
if (file == NULL)
|
|
return -errno;
|
|
|
|
size = kmod_file_get_size(file);
|
|
mem = kmod_file_get_contents(file);
|
|
|
|
elf = kmod_elf_new(mem, size);
|
|
if (elf == NULL) {
|
|
ret = -errno;
|
|
goto elf_open_error;
|
|
}
|
|
|
|
count = kmod_elf_get_modversions(elf, &versions);
|
|
if (count < 0) {
|
|
ret = count;
|
|
goto get_strings_error;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct kmod_module_version *mv;
|
|
struct kmod_list *n;
|
|
|
|
mv = kmod_module_versions_new(versions[i].crc, versions[i].symbol);
|
|
if (mv == NULL) {
|
|
ret = -errno;
|
|
kmod_module_versions_free_list(*list);
|
|
*list = NULL;
|
|
goto list_error;
|
|
}
|
|
|
|
n = kmod_list_append(*list, mv);
|
|
if (n != NULL)
|
|
*list = n;
|
|
else {
|
|
kmod_module_version_free(mv);
|
|
kmod_module_versions_free_list(*list);
|
|
*list = NULL;
|
|
ret = -ENOMEM;
|
|
goto list_error;
|
|
}
|
|
}
|
|
ret = count;
|
|
|
|
list_error:
|
|
free(versions);
|
|
get_strings_error:
|
|
kmod_elf_unref(elf);
|
|
elf_open_error:
|
|
kmod_file_unref(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_versions_get_symbol:
|
|
* @entry: a list entry representing a kmod module versions
|
|
*
|
|
* Get the symbol of a kmod module versions.
|
|
*
|
|
* Returns: the symbol of this kmod module versions on success or NULL
|
|
* on failure. The string is owned by the versions, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_version_get_symbol(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_version *version;
|
|
|
|
if (entry == NULL)
|
|
return NULL;
|
|
|
|
version = entry->data;
|
|
return version->symbol;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_version_get_crc:
|
|
* @entry: a list entry representing a kmod module version
|
|
*
|
|
* Get the crc of a kmod module version.
|
|
*
|
|
* Returns: the crc of this kmod module version on success or NULL on
|
|
* failure. The string is owned by the version, do not free it.
|
|
*/
|
|
KMOD_EXPORT uint64_t kmod_module_version_get_crc(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_version *version;
|
|
|
|
if (entry == NULL)
|
|
return 0;
|
|
|
|
version = entry->data;
|
|
return version->crc;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_versions_free_list:
|
|
* @list: kmod module versions list
|
|
*
|
|
* Release the resources taken by @list
|
|
*/
|
|
KMOD_EXPORT void kmod_module_versions_free_list(struct kmod_list *list)
|
|
{
|
|
while (list) {
|
|
kmod_module_version_free(list->data);
|
|
list = kmod_list_remove(list);
|
|
}
|
|
}
|
|
|
|
struct kmod_module_symbol {
|
|
uint64_t crc;
|
|
char symbol[];
|
|
};
|
|
|
|
static struct kmod_module_symbol *kmod_module_symbols_new(uint64_t crc, const char *symbol)
|
|
{
|
|
struct kmod_module_symbol *mv;
|
|
size_t symbollen = strlen(symbol) + 1;
|
|
|
|
mv = malloc(sizeof(struct kmod_module_symbol) + symbollen);
|
|
if (mv == NULL)
|
|
return NULL;
|
|
|
|
mv->crc = crc;
|
|
memcpy(mv->symbol, symbol, symbollen);
|
|
return mv;
|
|
}
|
|
|
|
static void kmod_module_symbol_free(struct kmod_module_symbol *symbol)
|
|
{
|
|
free(symbol);
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_symbols:
|
|
* @mod: kmod module
|
|
* @list: where to return list of module symbols. Use
|
|
* kmod_module_symbol_get_symbol() and
|
|
* kmod_module_symbol_get_crc(). Release this list with
|
|
* kmod_module_symbols_free_list()
|
|
*
|
|
* Get a list of entries in ELF section ".symtab" or "__ksymtab_strings".
|
|
*
|
|
* After use, free the @list by calling kmod_module_symbols_free_list().
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list)
|
|
{
|
|
struct kmod_file *file;
|
|
struct kmod_elf *elf;
|
|
const char *path;
|
|
const void *mem;
|
|
struct kmod_modversion *symbols;
|
|
size_t size;
|
|
int i, count, ret = 0;
|
|
|
|
if (mod == NULL || list == NULL)
|
|
return -ENOENT;
|
|
|
|
assert(*list == NULL);
|
|
|
|
path = kmod_module_get_path(mod);
|
|
if (path == NULL)
|
|
return -ENOENT;
|
|
|
|
file = kmod_file_open(mod->ctx, path);
|
|
if (file == NULL)
|
|
return -errno;
|
|
|
|
size = kmod_file_get_size(file);
|
|
mem = kmod_file_get_contents(file);
|
|
|
|
elf = kmod_elf_new(mem, size);
|
|
if (elf == NULL) {
|
|
ret = -errno;
|
|
goto elf_open_error;
|
|
}
|
|
|
|
count = kmod_elf_get_symbols(elf, &symbols);
|
|
if (count < 0) {
|
|
ret = count;
|
|
goto get_strings_error;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct kmod_module_symbol *mv;
|
|
struct kmod_list *n;
|
|
|
|
mv = kmod_module_symbols_new(symbols[i].crc, symbols[i].symbol);
|
|
if (mv == NULL) {
|
|
ret = -errno;
|
|
kmod_module_symbols_free_list(*list);
|
|
*list = NULL;
|
|
goto list_error;
|
|
}
|
|
|
|
n = kmod_list_append(*list, mv);
|
|
if (n != NULL)
|
|
*list = n;
|
|
else {
|
|
kmod_module_symbol_free(mv);
|
|
kmod_module_symbols_free_list(*list);
|
|
*list = NULL;
|
|
ret = -ENOMEM;
|
|
goto list_error;
|
|
}
|
|
}
|
|
ret = count;
|
|
|
|
list_error:
|
|
free(symbols);
|
|
get_strings_error:
|
|
kmod_elf_unref(elf);
|
|
elf_open_error:
|
|
kmod_file_unref(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_symbol_get_symbol:
|
|
* @entry: a list entry representing a kmod module symbols
|
|
*
|
|
* Get the symbol of a kmod module symbols.
|
|
*
|
|
* Returns: the symbol of this kmod module symbols on success or NULL
|
|
* on failure. The string is owned by the symbols, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_symbol *symbol;
|
|
|
|
if (entry == NULL)
|
|
return NULL;
|
|
|
|
symbol = entry->data;
|
|
return symbol->symbol;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_symbol_get_crc:
|
|
* @entry: a list entry representing a kmod module symbol
|
|
*
|
|
* Get the crc of a kmod module symbol.
|
|
*
|
|
* Returns: the crc of this kmod module symbol on success or NULL on
|
|
* failure. The string is owned by the symbol, do not free it.
|
|
*/
|
|
KMOD_EXPORT uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_symbol *symbol;
|
|
|
|
if (entry == NULL)
|
|
return 0;
|
|
|
|
symbol = entry->data;
|
|
return symbol->crc;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_symbols_free_list:
|
|
* @list: kmod module symbols list
|
|
*
|
|
* Release the resources taken by @list
|
|
*/
|
|
KMOD_EXPORT void kmod_module_symbols_free_list(struct kmod_list *list)
|
|
{
|
|
while (list) {
|
|
kmod_module_symbol_free(list->data);
|
|
list = kmod_list_remove(list);
|
|
}
|
|
}
|
|
|
|
struct kmod_module_dependency_symbol {
|
|
uint64_t crc;
|
|
uint8_t bind;
|
|
char symbol[];
|
|
};
|
|
|
|
static struct kmod_module_dependency_symbol *kmod_module_dependency_symbols_new(uint64_t crc, uint8_t bind, const char *symbol)
|
|
{
|
|
struct kmod_module_dependency_symbol *mv;
|
|
size_t symbollen = strlen(symbol) + 1;
|
|
|
|
mv = malloc(sizeof(struct kmod_module_dependency_symbol) + symbollen);
|
|
if (mv == NULL)
|
|
return NULL;
|
|
|
|
mv->crc = crc;
|
|
mv->bind = bind;
|
|
memcpy(mv->symbol, symbol, symbollen);
|
|
return mv;
|
|
}
|
|
|
|
static void kmod_module_dependency_symbol_free(struct kmod_module_dependency_symbol *dependency_symbol)
|
|
{
|
|
free(dependency_symbol);
|
|
}
|
|
|
|
/**
|
|
* kmod_module_get_dependency_symbols:
|
|
* @mod: kmod module
|
|
* @list: where to return list of module dependency_symbols. Use
|
|
* kmod_module_dependency_symbol_get_symbol() and
|
|
* kmod_module_dependency_symbol_get_crc(). Release this list with
|
|
* kmod_module_dependency_symbols_free_list()
|
|
*
|
|
* Get a list of entries in ELF section ".symtab" or "__ksymtab_strings".
|
|
*
|
|
* After use, free the @list by calling
|
|
* kmod_module_dependency_symbols_free_list().
|
|
*
|
|
* Returns: 0 on success or < 0 otherwise.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_get_dependency_symbols(const struct kmod_module *mod, struct kmod_list **list)
|
|
{
|
|
struct kmod_file *file;
|
|
struct kmod_elf *elf;
|
|
const char *path;
|
|
const void *mem;
|
|
struct kmod_modversion *symbols;
|
|
size_t size;
|
|
int i, count, ret = 0;
|
|
|
|
if (mod == NULL || list == NULL)
|
|
return -ENOENT;
|
|
|
|
assert(*list == NULL);
|
|
|
|
path = kmod_module_get_path(mod);
|
|
if (path == NULL)
|
|
return -ENOENT;
|
|
|
|
file = kmod_file_open(mod->ctx, path);
|
|
if (file == NULL)
|
|
return -errno;
|
|
|
|
size = kmod_file_get_size(file);
|
|
mem = kmod_file_get_contents(file);
|
|
|
|
elf = kmod_elf_new(mem, size);
|
|
if (elf == NULL) {
|
|
ret = -errno;
|
|
goto elf_open_error;
|
|
}
|
|
|
|
count = kmod_elf_get_dependency_symbols(elf, &symbols);
|
|
if (count < 0) {
|
|
ret = count;
|
|
goto get_strings_error;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct kmod_module_dependency_symbol *mv;
|
|
struct kmod_list *n;
|
|
|
|
mv = kmod_module_dependency_symbols_new(symbols[i].crc,
|
|
symbols[i].bind,
|
|
symbols[i].symbol);
|
|
if (mv == NULL) {
|
|
ret = -errno;
|
|
kmod_module_dependency_symbols_free_list(*list);
|
|
*list = NULL;
|
|
goto list_error;
|
|
}
|
|
|
|
n = kmod_list_append(*list, mv);
|
|
if (n != NULL)
|
|
*list = n;
|
|
else {
|
|
kmod_module_dependency_symbol_free(mv);
|
|
kmod_module_dependency_symbols_free_list(*list);
|
|
*list = NULL;
|
|
ret = -ENOMEM;
|
|
goto list_error;
|
|
}
|
|
}
|
|
ret = count;
|
|
|
|
list_error:
|
|
free(symbols);
|
|
get_strings_error:
|
|
kmod_elf_unref(elf);
|
|
elf_open_error:
|
|
kmod_file_unref(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_dependency_symbol_get_symbol:
|
|
* @entry: a list entry representing a kmod module dependency_symbols
|
|
*
|
|
* Get the dependency symbol of a kmod module
|
|
*
|
|
* Returns: the symbol of this kmod module dependency_symbols on success or NULL
|
|
* on failure. The string is owned by the dependency_symbols, do not free it.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_module_dependency_symbol_get_symbol(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_dependency_symbol *dependency_symbol;
|
|
|
|
if (entry == NULL)
|
|
return NULL;
|
|
|
|
dependency_symbol = entry->data;
|
|
return dependency_symbol->symbol;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_dependency_symbol_get_crc:
|
|
* @entry: a list entry representing a kmod module dependency_symbol
|
|
*
|
|
* Get the crc of a kmod module dependency_symbol.
|
|
*
|
|
* Returns: the crc of this kmod module dependency_symbol on success or NULL on
|
|
* failure. The string is owned by the dependency_symbol, do not free it.
|
|
*/
|
|
KMOD_EXPORT uint64_t kmod_module_dependency_symbol_get_crc(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_dependency_symbol *dependency_symbol;
|
|
|
|
if (entry == NULL)
|
|
return 0;
|
|
|
|
dependency_symbol = entry->data;
|
|
return dependency_symbol->crc;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_dependency_symbol_get_bind:
|
|
* @entry: a list entry representing a kmod module dependency_symbol
|
|
*
|
|
* Get the bind type of a kmod module dependency_symbol.
|
|
*
|
|
* Returns: the bind of this kmod module dependency_symbol on success
|
|
* or < 0 on failure.
|
|
*/
|
|
KMOD_EXPORT int kmod_module_dependency_symbol_get_bind(const struct kmod_list *entry)
|
|
{
|
|
struct kmod_module_dependency_symbol *dependency_symbol;
|
|
|
|
if (entry == NULL)
|
|
return 0;
|
|
|
|
dependency_symbol = entry->data;
|
|
return dependency_symbol->bind;
|
|
}
|
|
|
|
/**
|
|
* kmod_module_dependency_symbols_free_list:
|
|
* @list: kmod module dependency_symbols list
|
|
*
|
|
* Release the resources taken by @list
|
|
*/
|
|
KMOD_EXPORT void kmod_module_dependency_symbols_free_list(struct kmod_list *list)
|
|
{
|
|
while (list) {
|
|
kmod_module_dependency_symbol_free(list->data);
|
|
list = kmod_list_remove(list);
|
|
}
|
|
}
|