mirror of
https://github.com/AuxXxilium/kmod.git
synced 2025-02-24 03:11:31 +07:00

It has changed in the past, and these days, anyone can get a copy of the LGPL via the web rather than by post. Like 657a122 (Remove FSF mailing address) in libabc by Josh Tripplet, but let the FSF website in which the license can be found.
1168 lines
27 KiB
C
1168 lines
27 KiB
C
/*
|
|
* libkmod - interface to kernel module operations
|
|
*
|
|
* Copyright (C) 2011-2013 ProFUSION embedded systems
|
|
* Copyright (C) 2013 Intel Corporation. All rights reserved.
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <shared/util.h>
|
|
|
|
#include "libkmod.h"
|
|
#include "libkmod-internal.h"
|
|
|
|
struct kmod_alias {
|
|
char *name;
|
|
char modname[];
|
|
};
|
|
|
|
struct kmod_options {
|
|
char *options;
|
|
char modname[];
|
|
};
|
|
|
|
struct kmod_command {
|
|
char *command;
|
|
char modname[];
|
|
};
|
|
|
|
struct kmod_softdep {
|
|
char *name;
|
|
const char **pre;
|
|
const char **post;
|
|
unsigned int n_pre;
|
|
unsigned int n_post;
|
|
};
|
|
|
|
const char *kmod_blacklist_get_modname(const struct kmod_list *l)
|
|
{
|
|
return l->data;
|
|
}
|
|
|
|
const char *kmod_alias_get_name(const struct kmod_list *l) {
|
|
const struct kmod_alias *alias = l->data;
|
|
return alias->name;
|
|
}
|
|
|
|
const char *kmod_alias_get_modname(const struct kmod_list *l) {
|
|
const struct kmod_alias *alias = l->data;
|
|
return alias->modname;
|
|
}
|
|
|
|
const char *kmod_option_get_options(const struct kmod_list *l) {
|
|
const struct kmod_options *alias = l->data;
|
|
return alias->options;
|
|
}
|
|
|
|
const char *kmod_option_get_modname(const struct kmod_list *l) {
|
|
const struct kmod_options *alias = l->data;
|
|
return alias->modname;
|
|
}
|
|
|
|
const char *kmod_command_get_command(const struct kmod_list *l) {
|
|
const struct kmod_command *alias = l->data;
|
|
return alias->command;
|
|
}
|
|
|
|
const char *kmod_command_get_modname(const struct kmod_list *l) {
|
|
const struct kmod_command *alias = l->data;
|
|
return alias->modname;
|
|
}
|
|
|
|
const char *kmod_softdep_get_name(const struct kmod_list *l) {
|
|
const struct kmod_softdep *dep = l->data;
|
|
return dep->name;
|
|
}
|
|
|
|
const char * const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count) {
|
|
const struct kmod_softdep *dep = l->data;
|
|
*count = dep->n_pre;
|
|
return dep->pre;
|
|
}
|
|
|
|
const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count) {
|
|
const struct kmod_softdep *dep = l->data;
|
|
*count = dep->n_post;
|
|
return dep->post;
|
|
}
|
|
|
|
static int kmod_config_add_command(struct kmod_config *config,
|
|
const char *modname,
|
|
const char *command,
|
|
const char *command_name,
|
|
struct kmod_list **list)
|
|
{
|
|
_cleanup_free_ struct kmod_command *cmd;
|
|
struct kmod_list *l;
|
|
size_t modnamelen = strlen(modname) + 1;
|
|
size_t commandlen = strlen(command) + 1;
|
|
|
|
DBG(config->ctx, "modname='%s' cmd='%s %s'\n", modname, command_name,
|
|
command);
|
|
|
|
cmd = malloc(sizeof(*cmd) + modnamelen + commandlen);
|
|
if (!cmd)
|
|
return -ENOMEM;
|
|
|
|
cmd->command = sizeof(*cmd) + modnamelen + (char *)cmd;
|
|
memcpy(cmd->modname, modname, modnamelen);
|
|
memcpy(cmd->command, command, commandlen);
|
|
|
|
l = kmod_list_append(*list, cmd);
|
|
if (!l)
|
|
return -ENOMEM;
|
|
|
|
*list = l;
|
|
cmd = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static void kmod_config_free_command(struct kmod_config *config,
|
|
struct kmod_list *l,
|
|
struct kmod_list **list)
|
|
{
|
|
struct kmod_command *cmd = l->data;
|
|
|
|
free(cmd);
|
|
*list = kmod_list_remove(l);
|
|
}
|
|
|
|
static int kmod_config_add_options(struct kmod_config *config,
|
|
const char *modname, const char *options)
|
|
{
|
|
_cleanup_free_ struct kmod_options *opt;
|
|
struct kmod_list *list;
|
|
size_t modnamelen = strlen(modname) + 1;
|
|
size_t optionslen = strlen(options) + 1;
|
|
|
|
DBG(config->ctx, "modname='%s' options='%s'\n", modname, options);
|
|
|
|
opt = malloc(sizeof(*opt) + modnamelen + optionslen);
|
|
if (!opt)
|
|
return -ENOMEM;
|
|
|
|
opt->options = sizeof(*opt) + modnamelen + (char *)opt;
|
|
|
|
memcpy(opt->modname, modname, modnamelen);
|
|
memcpy(opt->options, options, optionslen);
|
|
strchr_replace(opt->options, '\t', ' ');
|
|
|
|
list = kmod_list_append(config->options, opt);
|
|
if (!list)
|
|
return -ENOMEM;
|
|
|
|
opt = NULL;
|
|
config->options = list;
|
|
return 0;
|
|
}
|
|
|
|
static void kmod_config_free_options(struct kmod_config *config,
|
|
struct kmod_list *l)
|
|
{
|
|
struct kmod_options *opt = l->data;
|
|
|
|
free(opt);
|
|
|
|
config->options = kmod_list_remove(l);
|
|
}
|
|
|
|
static int kmod_config_add_alias(struct kmod_config *config,
|
|
const char *name, const char *modname)
|
|
{
|
|
_cleanup_free_ struct kmod_alias *alias;
|
|
struct kmod_list *list;
|
|
size_t namelen = strlen(name) + 1, modnamelen = strlen(modname) + 1;
|
|
|
|
DBG(config->ctx, "name=%s modname=%s\n", name, modname);
|
|
|
|
alias = malloc(sizeof(*alias) + namelen + modnamelen);
|
|
if (!alias)
|
|
return -ENOMEM;
|
|
|
|
alias->name = sizeof(*alias) + modnamelen + (char *)alias;
|
|
|
|
memcpy(alias->modname, modname, modnamelen);
|
|
memcpy(alias->name, name, namelen);
|
|
|
|
list = kmod_list_append(config->aliases, alias);
|
|
if (!list)
|
|
return -ENOMEM;
|
|
|
|
alias = NULL;
|
|
config->aliases = list;
|
|
return 0;
|
|
}
|
|
|
|
static void kmod_config_free_alias(struct kmod_config *config,
|
|
struct kmod_list *l)
|
|
{
|
|
struct kmod_alias *alias = l->data;
|
|
|
|
free(alias);
|
|
|
|
config->aliases = kmod_list_remove(l);
|
|
}
|
|
|
|
static int kmod_config_add_blacklist(struct kmod_config *config,
|
|
const char *modname)
|
|
{
|
|
_cleanup_free_ char *p;
|
|
struct kmod_list *list;
|
|
|
|
DBG(config->ctx, "modname=%s\n", modname);
|
|
|
|
p = strdup(modname);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
list = kmod_list_append(config->blacklists, p);
|
|
if (!list)
|
|
return -ENOMEM;
|
|
|
|
p = NULL;
|
|
config->blacklists = list;
|
|
return 0;
|
|
}
|
|
|
|
static void kmod_config_free_blacklist(struct kmod_config *config,
|
|
struct kmod_list *l)
|
|
{
|
|
free(l->data);
|
|
config->blacklists = kmod_list_remove(l);
|
|
}
|
|
|
|
static int kmod_config_add_softdep(struct kmod_config *config,
|
|
const char *modname,
|
|
const char *line)
|
|
{
|
|
struct kmod_list *list;
|
|
struct kmod_softdep *dep;
|
|
const char *s, *p;
|
|
char *itr;
|
|
unsigned int n_pre = 0, n_post = 0;
|
|
size_t modnamelen = strlen(modname) + 1;
|
|
size_t buflen = 0;
|
|
bool was_space = false;
|
|
enum { S_NONE, S_PRE, S_POST } mode = S_NONE;
|
|
|
|
DBG(config->ctx, "modname=%s\n", modname);
|
|
|
|
/* analyze and count */
|
|
for (p = s = line; ; s++) {
|
|
size_t plen;
|
|
|
|
if (*s != '\0') {
|
|
if (!isspace(*s)) {
|
|
was_space = false;
|
|
continue;
|
|
}
|
|
|
|
if (was_space) {
|
|
p = s + 1;
|
|
continue;
|
|
}
|
|
was_space = true;
|
|
|
|
if (p >= s)
|
|
continue;
|
|
}
|
|
plen = s - p;
|
|
|
|
if (plen == sizeof("pre:") - 1 &&
|
|
memcmp(p, "pre:", sizeof("pre:") - 1) == 0)
|
|
mode = S_PRE;
|
|
else if (plen == sizeof("post:") - 1 &&
|
|
memcmp(p, "post:", sizeof("post:") - 1) == 0)
|
|
mode = S_POST;
|
|
else if (*s != '\0' || (*s == '\0' && !was_space)) {
|
|
if (mode == S_PRE) {
|
|
buflen += plen + 1;
|
|
n_pre++;
|
|
} else if (mode == S_POST) {
|
|
buflen += plen + 1;
|
|
n_post++;
|
|
}
|
|
}
|
|
p = s + 1;
|
|
if (*s == '\0')
|
|
break;
|
|
}
|
|
|
|
DBG(config->ctx, "%u pre, %u post\n", n_pre, n_post);
|
|
|
|
dep = malloc(sizeof(struct kmod_softdep) + modnamelen +
|
|
n_pre * sizeof(const char *) +
|
|
n_post * sizeof(const char *) +
|
|
buflen);
|
|
if (dep == NULL) {
|
|
ERR(config->ctx, "out-of-memory modname=%s\n", modname);
|
|
return -ENOMEM;
|
|
}
|
|
dep->n_pre = n_pre;
|
|
dep->n_post = n_post;
|
|
dep->pre = (const char **)((char *)dep + sizeof(struct kmod_softdep));
|
|
dep->post = dep->pre + n_pre;
|
|
dep->name = (char *)(dep->post + n_post);
|
|
|
|
memcpy(dep->name, modname, modnamelen);
|
|
|
|
/* copy strings */
|
|
itr = dep->name + modnamelen;
|
|
n_pre = 0;
|
|
n_post = 0;
|
|
mode = S_NONE;
|
|
for (p = s = line; ; s++) {
|
|
size_t plen;
|
|
|
|
if (*s != '\0') {
|
|
if (!isspace(*s)) {
|
|
was_space = false;
|
|
continue;
|
|
}
|
|
|
|
if (was_space) {
|
|
p = s + 1;
|
|
continue;
|
|
}
|
|
was_space = true;
|
|
|
|
if (p >= s)
|
|
continue;
|
|
}
|
|
plen = s - p;
|
|
|
|
if (plen == sizeof("pre:") - 1 &&
|
|
memcmp(p, "pre:", sizeof("pre:") - 1) == 0)
|
|
mode = S_PRE;
|
|
else if (plen == sizeof("post:") - 1 &&
|
|
memcmp(p, "post:", sizeof("post:") - 1) == 0)
|
|
mode = S_POST;
|
|
else if (*s != '\0' || (*s == '\0' && !was_space)) {
|
|
if (mode == S_PRE) {
|
|
dep->pre[n_pre] = itr;
|
|
memcpy(itr, p, plen);
|
|
itr[plen] = '\0';
|
|
itr += plen + 1;
|
|
n_pre++;
|
|
} else if (mode == S_POST) {
|
|
dep->post[n_post] = itr;
|
|
memcpy(itr, p, plen);
|
|
itr[plen] = '\0';
|
|
itr += plen + 1;
|
|
n_post++;
|
|
}
|
|
}
|
|
p = s + 1;
|
|
if (*s == '\0')
|
|
break;
|
|
}
|
|
|
|
list = kmod_list_append(config->softdeps, dep);
|
|
if (list == NULL) {
|
|
free(dep);
|
|
return -ENOMEM;
|
|
}
|
|
config->softdeps = list;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *softdep_to_char(struct kmod_softdep *dep) {
|
|
const size_t sz_preprefix = sizeof("pre: ") - 1;
|
|
const size_t sz_postprefix = sizeof("post: ") - 1;
|
|
size_t sz = 1; /* at least '\0' */
|
|
size_t sz_pre, sz_post;
|
|
const char *start, *end;
|
|
char *s, *itr;
|
|
|
|
/*
|
|
* Rely on the fact that dep->pre[] and dep->post[] are strv's that
|
|
* point to a contiguous buffer
|
|
*/
|
|
if (dep->n_pre > 0) {
|
|
start = dep->pre[0];
|
|
end = dep->pre[dep->n_pre - 1]
|
|
+ strlen(dep->pre[dep->n_pre - 1]);
|
|
sz_pre = end - start;
|
|
sz += sz_pre + sz_preprefix;
|
|
} else
|
|
sz_pre = 0;
|
|
|
|
if (dep->n_post > 0) {
|
|
start = dep->post[0];
|
|
end = dep->post[dep->n_post - 1]
|
|
+ strlen(dep->post[dep->n_post - 1]);
|
|
sz_post = end - start;
|
|
sz += sz_post + sz_postprefix;
|
|
} else
|
|
sz_post = 0;
|
|
|
|
itr = s = malloc(sz);
|
|
if (s == NULL)
|
|
return NULL;
|
|
|
|
if (sz_pre) {
|
|
char *p;
|
|
|
|
memcpy(itr, "pre: ", sz_preprefix);
|
|
itr += sz_preprefix;
|
|
|
|
/* include last '\0' */
|
|
memcpy(itr, dep->pre[0], sz_pre + 1);
|
|
for (p = itr; p < itr + sz_pre; p++) {
|
|
if (*p == '\0')
|
|
*p = ' ';
|
|
}
|
|
itr = p;
|
|
}
|
|
|
|
if (sz_post) {
|
|
char *p;
|
|
|
|
memcpy(itr, "post: ", sz_postprefix);
|
|
itr += sz_postprefix;
|
|
|
|
/* include last '\0' */
|
|
memcpy(itr, dep->post[0], sz_post + 1);
|
|
for (p = itr; p < itr + sz_post; p++) {
|
|
if (*p == '\0')
|
|
*p = ' ';
|
|
}
|
|
itr = p;
|
|
}
|
|
|
|
*itr = '\0';
|
|
|
|
return s;
|
|
}
|
|
|
|
static void kmod_config_free_softdep(struct kmod_config *config,
|
|
struct kmod_list *l)
|
|
{
|
|
free(l->data);
|
|
config->softdeps = kmod_list_remove(l);
|
|
}
|
|
|
|
static void kcmdline_parse_result(struct kmod_config *config, char *modname,
|
|
char *param, char *value)
|
|
{
|
|
if (modname == NULL || param == NULL)
|
|
return;
|
|
|
|
DBG(config->ctx, "%s %s\n", modname, param);
|
|
|
|
if (streq(modname, "modprobe") && !strncmp(param, "blacklist=", 10)) {
|
|
for (;;) {
|
|
char *t = strsep(&value, ",");
|
|
if (t == NULL)
|
|
break;
|
|
|
|
kmod_config_add_blacklist(config, t);
|
|
}
|
|
} else {
|
|
if (underscores(modname) < 0) {
|
|
ERR(config->ctx, "Ignoring bad option on kernel command line while parsing module name: '%s'\n",
|
|
modname);
|
|
}
|
|
kmod_config_add_options(config, modname, param);
|
|
}
|
|
}
|
|
|
|
static int kmod_config_parse_kcmdline(struct kmod_config *config)
|
|
{
|
|
char buf[KCMD_LINE_SIZE];
|
|
int fd, err;
|
|
char *p, *modname, *param = NULL, *value = NULL, is_module = 1;
|
|
|
|
fd = open("/proc/cmdline", O_RDONLY|O_CLOEXEC);
|
|
if (fd < 0) {
|
|
err = -errno;
|
|
DBG(config->ctx, "could not open '/proc/cmdline' for reading: %m\n");
|
|
return err;
|
|
}
|
|
|
|
err = read_str_safe(fd, buf, sizeof(buf));
|
|
close(fd);
|
|
if (err < 0) {
|
|
ERR(config->ctx, "could not read from '/proc/cmdline': %s\n",
|
|
strerror(-err));
|
|
return err;
|
|
}
|
|
|
|
for (p = buf, modname = buf; *p != '\0' && *p != '\n'; p++) {
|
|
switch (*p) {
|
|
case ' ':
|
|
*p = '\0';
|
|
if (is_module)
|
|
kcmdline_parse_result(config, modname, param, value);
|
|
param = value = NULL;
|
|
modname = p + 1;
|
|
is_module = 1;
|
|
break;
|
|
case '.':
|
|
if (param == NULL) {
|
|
*p = '\0';
|
|
param = p + 1;
|
|
}
|
|
break;
|
|
case '=':
|
|
if (param != NULL)
|
|
value = p + 1;
|
|
else
|
|
is_module = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*p = '\0';
|
|
if (is_module)
|
|
kcmdline_parse_result(config, modname, param, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Take an fd and own it. It will be closed on return. filename is used only
|
|
* for debug messages
|
|
*/
|
|
static int kmod_config_parse(struct kmod_config *config, int fd,
|
|
const char *filename)
|
|
{
|
|
struct kmod_ctx *ctx = config->ctx;
|
|
char *line;
|
|
FILE *fp;
|
|
unsigned int linenum = 0;
|
|
int err;
|
|
|
|
fp = fdopen(fd, "r");
|
|
if (fp == NULL) {
|
|
err = -errno;
|
|
ERR(config->ctx, "fd %d: %m\n", fd);
|
|
close(fd);
|
|
return err;
|
|
}
|
|
|
|
while ((line = freadline_wrapped(fp, &linenum)) != NULL) {
|
|
char *cmd, *saveptr;
|
|
|
|
if (line[0] == '\0' || line[0] == '#')
|
|
goto done_next;
|
|
|
|
cmd = strtok_r(line, "\t ", &saveptr);
|
|
if (cmd == NULL)
|
|
goto done_next;
|
|
|
|
if (streq(cmd, "alias")) {
|
|
char *alias = strtok_r(NULL, "\t ", &saveptr);
|
|
char *modname = strtok_r(NULL, "\t ", &saveptr);
|
|
|
|
if (underscores(alias) < 0 || underscores(modname) < 0)
|
|
goto syntax_error;
|
|
|
|
kmod_config_add_alias(config, alias, modname);
|
|
} else if (streq(cmd, "blacklist")) {
|
|
char *modname = strtok_r(NULL, "\t ", &saveptr);
|
|
|
|
if (underscores(modname) < 0)
|
|
goto syntax_error;
|
|
|
|
kmod_config_add_blacklist(config, modname);
|
|
} else if (streq(cmd, "options")) {
|
|
char *modname = strtok_r(NULL, "\t ", &saveptr);
|
|
char *options = strtok_r(NULL, "\0", &saveptr);
|
|
|
|
if (underscores(modname) < 0 || options == NULL)
|
|
goto syntax_error;
|
|
|
|
kmod_config_add_options(config, modname, options);
|
|
} else if (streq(cmd, "install")) {
|
|
char *modname = strtok_r(NULL, "\t ", &saveptr);
|
|
char *installcmd = strtok_r(NULL, "\0", &saveptr);
|
|
|
|
if (underscores(modname) < 0 || installcmd == NULL)
|
|
goto syntax_error;
|
|
|
|
kmod_config_add_command(config, modname, installcmd,
|
|
cmd, &config->install_commands);
|
|
} else if (streq(cmd, "remove")) {
|
|
char *modname = strtok_r(NULL, "\t ", &saveptr);
|
|
char *removecmd = strtok_r(NULL, "\0", &saveptr);
|
|
|
|
if (underscores(modname) < 0 || removecmd == NULL)
|
|
goto syntax_error;
|
|
|
|
kmod_config_add_command(config, modname, removecmd,
|
|
cmd, &config->remove_commands);
|
|
} else if (streq(cmd, "softdep")) {
|
|
char *modname = strtok_r(NULL, "\t ", &saveptr);
|
|
char *softdeps = strtok_r(NULL, "\0", &saveptr);
|
|
|
|
if (underscores(modname) < 0 || softdeps == NULL)
|
|
goto syntax_error;
|
|
|
|
kmod_config_add_softdep(config, modname, softdeps);
|
|
} else if (streq(cmd, "include")
|
|
|| streq(cmd, "config")) {
|
|
ERR(ctx, "%s: command %s is deprecated and not parsed anymore\n",
|
|
filename, cmd);
|
|
} else {
|
|
syntax_error:
|
|
ERR(ctx, "%s line %u: ignoring bad line starting with '%s'\n",
|
|
filename, linenum, cmd);
|
|
}
|
|
|
|
done_next:
|
|
free(line);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void kmod_config_free(struct kmod_config *config)
|
|
{
|
|
while (config->aliases)
|
|
kmod_config_free_alias(config, config->aliases);
|
|
|
|
while (config->blacklists)
|
|
kmod_config_free_blacklist(config, config->blacklists);
|
|
|
|
while (config->options)
|
|
kmod_config_free_options(config, config->options);
|
|
|
|
while (config->install_commands) {
|
|
kmod_config_free_command(config, config->install_commands,
|
|
&config->install_commands);
|
|
}
|
|
|
|
while (config->remove_commands) {
|
|
kmod_config_free_command(config, config->remove_commands,
|
|
&config->remove_commands);
|
|
}
|
|
|
|
while (config->softdeps)
|
|
kmod_config_free_softdep(config, config->softdeps);
|
|
|
|
for (; config->paths != NULL;
|
|
config->paths = kmod_list_remove(config->paths))
|
|
free(config->paths->data);
|
|
|
|
free(config);
|
|
}
|
|
|
|
static bool conf_files_filter_out(struct kmod_ctx *ctx, DIR *d,
|
|
const char *path, const char *fn)
|
|
{
|
|
size_t len = strlen(fn);
|
|
struct stat st;
|
|
|
|
if (fn[0] == '.')
|
|
return true;
|
|
|
|
if (len < 6 || (!streq(&fn[len - 5], ".conf")
|
|
&& !streq(&fn[len - 6], ".alias")))
|
|
return true;
|
|
|
|
fstatat(dirfd(d), fn, &st, 0);
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
ERR(ctx, "Directories inside directories are not supported: "
|
|
"%s/%s\n", path, fn);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
struct conf_file {
|
|
const char *path;
|
|
bool is_single;
|
|
char name[];
|
|
};
|
|
|
|
static int conf_files_insert_sorted(struct kmod_ctx *ctx,
|
|
struct kmod_list **list,
|
|
const char *path, const char *name)
|
|
{
|
|
struct kmod_list *lpos, *tmp;
|
|
struct conf_file *cf;
|
|
size_t namelen;
|
|
int cmp = -1;
|
|
bool is_single = false;
|
|
|
|
if (name == NULL) {
|
|
name = basename(path);
|
|
is_single = true;
|
|
}
|
|
|
|
kmod_list_foreach(lpos, *list) {
|
|
cf = lpos->data;
|
|
|
|
if ((cmp = strcmp(name, cf->name)) <= 0)
|
|
break;
|
|
}
|
|
|
|
if (cmp == 0) {
|
|
DBG(ctx, "Ignoring duplicate config file: %s/%s\n", path,
|
|
name);
|
|
return -EEXIST;
|
|
}
|
|
|
|
namelen = strlen(name);
|
|
cf = malloc(sizeof(*cf) + namelen + 1);
|
|
if (cf == NULL)
|
|
return -ENOMEM;
|
|
|
|
memcpy(cf->name, name, namelen + 1);
|
|
cf->path = path;
|
|
cf->is_single = is_single;
|
|
|
|
if (lpos == NULL)
|
|
tmp = kmod_list_append(*list, cf);
|
|
else if (lpos == *list)
|
|
tmp = kmod_list_prepend(*list, cf);
|
|
else
|
|
tmp = kmod_list_insert_before(lpos, cf);
|
|
|
|
if (tmp == NULL) {
|
|
free(cf);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (lpos == NULL || lpos == *list)
|
|
*list = tmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Insert configuration files in @list, ignoring duplicates
|
|
*/
|
|
static int conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list,
|
|
const char *path,
|
|
unsigned long long *path_stamp)
|
|
{
|
|
DIR *d;
|
|
int err;
|
|
struct stat st;
|
|
struct dirent *dent;
|
|
|
|
if (stat(path, &st) != 0) {
|
|
err = -errno;
|
|
DBG(ctx, "could not stat '%s': %m\n", path);
|
|
return err;
|
|
}
|
|
|
|
*path_stamp = stat_mstamp(&st);
|
|
|
|
if (!S_ISDIR(st.st_mode)) {
|
|
conf_files_insert_sorted(ctx, list, path, NULL);
|
|
return 0;
|
|
}
|
|
|
|
d = opendir(path);
|
|
if (d == NULL) {
|
|
ERR(ctx, "opendir(%s): %m\n", path);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (dent = readdir(d); dent != NULL; dent = readdir(d)) {
|
|
if (conf_files_filter_out(ctx, d, path, dent->d_name))
|
|
continue;
|
|
|
|
conf_files_insert_sorted(ctx, list, path, dent->d_name);
|
|
}
|
|
|
|
closedir(d);
|
|
return 0;
|
|
}
|
|
|
|
int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **p_config,
|
|
const char * const *config_paths)
|
|
{
|
|
struct kmod_config *config;
|
|
struct kmod_list *list = NULL;
|
|
struct kmod_list *path_list = NULL;
|
|
size_t i;
|
|
|
|
conf_files_insert_sorted(ctx, &list, kmod_get_dirname(ctx), "modules.softdep");
|
|
|
|
for (i = 0; config_paths[i] != NULL; i++) {
|
|
const char *path = config_paths[i];
|
|
unsigned long long path_stamp = 0;
|
|
size_t pathlen;
|
|
struct kmod_list *tmp;
|
|
struct kmod_config_path *cf;
|
|
|
|
if (conf_files_list(ctx, &list, path, &path_stamp) < 0)
|
|
continue;
|
|
|
|
pathlen = strlen(path) + 1;
|
|
cf = malloc(sizeof(*cf) + pathlen);
|
|
if (cf == NULL)
|
|
goto oom;
|
|
|
|
cf->stamp = path_stamp;
|
|
memcpy(cf->path, path, pathlen);
|
|
|
|
tmp = kmod_list_append(path_list, cf);
|
|
if (tmp == NULL)
|
|
goto oom;
|
|
path_list = tmp;
|
|
}
|
|
|
|
*p_config = config = calloc(1, sizeof(struct kmod_config));
|
|
if (config == NULL)
|
|
goto oom;
|
|
|
|
config->paths = path_list;
|
|
config->ctx = ctx;
|
|
|
|
for (; list != NULL; list = kmod_list_remove(list)) {
|
|
char fn[PATH_MAX];
|
|
struct conf_file *cf = list->data;
|
|
int fd;
|
|
|
|
if (cf->is_single)
|
|
strcpy(fn, cf->path);
|
|
else
|
|
snprintf(fn, sizeof(fn),"%s/%s", cf->path,
|
|
cf->name);
|
|
|
|
fd = open(fn, O_RDONLY|O_CLOEXEC);
|
|
DBG(ctx, "parsing file '%s' fd=%d\n", fn, fd);
|
|
|
|
if (fd >= 0)
|
|
kmod_config_parse(config, fd, fn);
|
|
|
|
free(cf);
|
|
}
|
|
|
|
kmod_config_parse_kcmdline(config);
|
|
|
|
return 0;
|
|
|
|
oom:
|
|
for (; list != NULL; list = kmod_list_remove(list))
|
|
free(list->data);
|
|
|
|
for (; path_list != NULL; path_list = kmod_list_remove(path_list))
|
|
free(path_list->data);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* struct kmod_config_iter functions
|
|
**********************************************************************/
|
|
|
|
enum config_type {
|
|
CONFIG_TYPE_BLACKLIST = 0,
|
|
CONFIG_TYPE_INSTALL,
|
|
CONFIG_TYPE_REMOVE,
|
|
CONFIG_TYPE_ALIAS,
|
|
CONFIG_TYPE_OPTION,
|
|
CONFIG_TYPE_SOFTDEP,
|
|
};
|
|
|
|
struct kmod_config_iter {
|
|
enum config_type type;
|
|
bool intermediate;
|
|
const struct kmod_list *list;
|
|
const struct kmod_list *curr;
|
|
void *data;
|
|
const char *(*get_key)(const struct kmod_list *l);
|
|
const char *(*get_value)(const struct kmod_list *l);
|
|
};
|
|
|
|
static const char *softdep_get_plain_softdep(const struct kmod_list *l)
|
|
{
|
|
char *s = softdep_to_char(l->data);
|
|
return s;
|
|
}
|
|
|
|
static struct kmod_config_iter *kmod_config_iter_new(const struct kmod_ctx* ctx,
|
|
enum config_type type)
|
|
{
|
|
struct kmod_config_iter *iter = calloc(1, sizeof(*iter));
|
|
const struct kmod_config *config = kmod_get_config(ctx);
|
|
|
|
if (iter == NULL)
|
|
return NULL;
|
|
|
|
iter->type = type;
|
|
|
|
switch (type) {
|
|
case CONFIG_TYPE_BLACKLIST:
|
|
iter->list = config->blacklists;
|
|
iter->get_key = kmod_blacklist_get_modname;
|
|
break;
|
|
case CONFIG_TYPE_INSTALL:
|
|
iter->list = config->install_commands;
|
|
iter->get_key = kmod_command_get_modname;
|
|
iter->get_value = kmod_command_get_command;
|
|
break;
|
|
case CONFIG_TYPE_REMOVE:
|
|
iter->list = config->remove_commands;
|
|
iter->get_key = kmod_command_get_modname;
|
|
iter->get_value = kmod_command_get_command;
|
|
break;
|
|
case CONFIG_TYPE_ALIAS:
|
|
iter->list = config->aliases;
|
|
iter->get_key = kmod_alias_get_name;
|
|
iter->get_value = kmod_alias_get_modname;
|
|
break;
|
|
case CONFIG_TYPE_OPTION:
|
|
iter->list = config->options;
|
|
iter->get_key = kmod_option_get_modname;
|
|
iter->get_value = kmod_option_get_options;
|
|
break;
|
|
case CONFIG_TYPE_SOFTDEP:
|
|
iter->list = config->softdeps;
|
|
iter->get_key = kmod_softdep_get_name;
|
|
iter->get_value = softdep_get_plain_softdep;
|
|
iter->intermediate = true;
|
|
break;
|
|
}
|
|
|
|
return iter;
|
|
}
|
|
|
|
/**
|
|
* SECTION:libkmod-config
|
|
* @short_description: retrieve current libkmod configuration
|
|
*/
|
|
|
|
/**
|
|
* kmod_config_get_blacklists:
|
|
* @ctx: kmod library context
|
|
*
|
|
* Retrieve an iterator to deal with the blacklist maintained inside the
|
|
* library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and
|
|
* kmod_config_iter_next(). At least one call to kmod_config_iter_next() must
|
|
* be made to initialize the iterator and check if it's valid.
|
|
*
|
|
* Returns: a new iterator over the blacklists or NULL on failure. Free it
|
|
* with kmod_config_iter_free_iter().
|
|
*/
|
|
KMOD_EXPORT struct kmod_config_iter *kmod_config_get_blacklists(const struct kmod_ctx *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
return NULL;;
|
|
|
|
return kmod_config_iter_new(ctx, CONFIG_TYPE_BLACKLIST);
|
|
}
|
|
|
|
/**
|
|
* kmod_config_get_install_commands:
|
|
* @ctx: kmod library context
|
|
*
|
|
* Retrieve an iterator to deal with the install commands maintained inside the
|
|
* library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and
|
|
* kmod_config_iter_next(). At least one call to kmod_config_iter_next() must
|
|
* be made to initialize the iterator and check if it's valid.
|
|
*
|
|
* Returns: a new iterator over the install commands or NULL on failure. Free
|
|
* it with kmod_config_iter_free_iter().
|
|
*/
|
|
KMOD_EXPORT struct kmod_config_iter *kmod_config_get_install_commands(const struct kmod_ctx *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
return NULL;;
|
|
|
|
return kmod_config_iter_new(ctx, CONFIG_TYPE_INSTALL);
|
|
}
|
|
|
|
/**
|
|
* kmod_config_get_remove_commands:
|
|
* @ctx: kmod library context
|
|
*
|
|
* Retrieve an iterator to deal with the remove commands maintained inside the
|
|
* library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and
|
|
* kmod_config_iter_next(). At least one call to kmod_config_iter_next() must
|
|
* be made to initialize the iterator and check if it's valid.
|
|
*
|
|
* Returns: a new iterator over the remove commands or NULL on failure. Free
|
|
* it with kmod_config_iter_free_iter().
|
|
*/
|
|
KMOD_EXPORT struct kmod_config_iter *kmod_config_get_remove_commands(const struct kmod_ctx *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
return NULL;;
|
|
|
|
return kmod_config_iter_new(ctx, CONFIG_TYPE_REMOVE);
|
|
}
|
|
|
|
/**
|
|
* kmod_config_get_aliases:
|
|
* @ctx: kmod library context
|
|
*
|
|
* Retrieve an iterator to deal with the aliases maintained inside the
|
|
* library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and
|
|
* kmod_config_iter_next(). At least one call to kmod_config_iter_next() must
|
|
* be made to initialize the iterator and check if it's valid.
|
|
*
|
|
* Returns: a new iterator over the aliases or NULL on failure. Free it with
|
|
* kmod_config_iter_free_iter().
|
|
*/
|
|
KMOD_EXPORT struct kmod_config_iter *kmod_config_get_aliases(const struct kmod_ctx *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
return NULL;;
|
|
|
|
return kmod_config_iter_new(ctx, CONFIG_TYPE_ALIAS);
|
|
}
|
|
|
|
/**
|
|
* kmod_config_get_options:
|
|
* @ctx: kmod library context
|
|
*
|
|
* Retrieve an iterator to deal with the options maintained inside the
|
|
* library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and
|
|
* kmod_config_iter_next(). At least one call to kmod_config_iter_next() must
|
|
* be made to initialize the iterator and check if it's valid.
|
|
*
|
|
* Returns: a new iterator over the options or NULL on failure. Free it with
|
|
* kmod_config_iter_free_iter().
|
|
*/
|
|
KMOD_EXPORT struct kmod_config_iter *kmod_config_get_options(const struct kmod_ctx *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
return NULL;;
|
|
|
|
return kmod_config_iter_new(ctx, CONFIG_TYPE_OPTION);
|
|
}
|
|
|
|
/**
|
|
* kmod_config_get_softdeps:
|
|
* @ctx: kmod library context
|
|
*
|
|
* Retrieve an iterator to deal with the softdeps maintained inside the
|
|
* library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and
|
|
* kmod_config_iter_next(). At least one call to kmod_config_iter_next() must
|
|
* be made to initialize the iterator and check if it's valid.
|
|
*
|
|
* Returns: a new iterator over the softdeps or NULL on failure. Free it with
|
|
* kmod_config_iter_free_iter().
|
|
*/
|
|
KMOD_EXPORT struct kmod_config_iter *kmod_config_get_softdeps(const struct kmod_ctx *ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
return NULL;;
|
|
|
|
return kmod_config_iter_new(ctx, CONFIG_TYPE_SOFTDEP);
|
|
}
|
|
|
|
/**
|
|
* kmod_config_iter_get_key:
|
|
* @iter: iterator over a certain configuration
|
|
*
|
|
* When using a new allocated iterator, user must perform a call to
|
|
* kmod_config_iter_next() to initialize iterator's position and check if it's
|
|
* valid.
|
|
*
|
|
* Returns: the key of the current configuration pointed by @iter.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_config_iter_get_key(const struct kmod_config_iter *iter)
|
|
{
|
|
if (iter == NULL || iter->curr == NULL)
|
|
return NULL;
|
|
|
|
return iter->get_key(iter->curr);
|
|
}
|
|
|
|
/**
|
|
* kmod_config_iter_get_value:
|
|
* @iter: iterator over a certain configuration
|
|
*
|
|
* When using a new allocated iterator, user must perform a call to
|
|
* kmod_config_iter_next() to initialize iterator's position and check if it's
|
|
* valid.
|
|
*
|
|
* Returns: the value of the current configuration pointed by @iter.
|
|
*/
|
|
KMOD_EXPORT const char *kmod_config_iter_get_value(const struct kmod_config_iter *iter)
|
|
{
|
|
const char *s;
|
|
|
|
if (iter == NULL || iter->curr == NULL)
|
|
return NULL;
|
|
|
|
if (iter->get_value == NULL)
|
|
return NULL;
|
|
|
|
if (iter->intermediate) {
|
|
struct kmod_config_iter *i = (struct kmod_config_iter *)iter;
|
|
|
|
free(i->data);
|
|
s = i->data = (void *) iter->get_value(iter->curr);
|
|
} else
|
|
s = iter->get_value(iter->curr);
|
|
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* kmod_config_iter_next:
|
|
* @iter: iterator over a certain configuration
|
|
*
|
|
* Make @iter point to the next item of a certain configuration. It's an
|
|
* automatically recycling iterator. When it reaches the end, false is
|
|
* returned; then if user wants to iterate again, it's sufficient to call this
|
|
* function once more.
|
|
*
|
|
* Returns: true if next position of @iter is valid or false if its end is
|
|
* reached.
|
|
*/
|
|
KMOD_EXPORT bool kmod_config_iter_next(struct kmod_config_iter *iter)
|
|
{
|
|
if (iter == NULL)
|
|
return false;
|
|
|
|
if (iter->curr == NULL) {
|
|
iter->curr = iter->list;
|
|
return iter->curr != NULL;
|
|
}
|
|
|
|
iter->curr = kmod_list_next(iter->list, iter->curr);
|
|
|
|
return iter->curr != NULL;
|
|
}
|
|
|
|
/**
|
|
* kmod_config_iter_free_iter:
|
|
* @iter: iterator over a certain configuration
|
|
*
|
|
* Free resources used by the iterator.
|
|
*/
|
|
KMOD_EXPORT void kmod_config_iter_free_iter(struct kmod_config_iter *iter)
|
|
{
|
|
free(iter->data);
|
|
free(iter);
|
|
}
|