www.pudn.com > module-init-tools-3.0.rar > modprobe.c
/* modprobe.c: insert a module into the kernel, intelligently.
Copyright (C) 2001 Rusty Russell.
Copyright (C) 2002, 2003 Rusty Russell, IBM Corporation.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define _GNU_SOURCE /* asprintf */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define streq(a,b) (strcmp((a),(b)) == 0)
#include "zlibsupport.h"
#include "list.h"
#include "backwards_compat.c"
extern long init_module(void *, unsigned long, const char *);
extern long delete_module(const char *, unsigned int);
struct module {
struct list_head list;
char *modname;
char filename[0];
};
#ifndef MODULE_DIR
#define MODULE_DIR "/lib/modules"
#endif
typedef void (*errfn_t)(const char *fmt, ...);
/* Do we use syslog or stderr for messages? */
static int log;
static void message(const char *prefix, const char *fmt, va_list *arglist)
{
char *buf, *buf2;
vasprintf(&buf, fmt, *arglist);
asprintf(&buf2, "%s%s", prefix, buf);
if (log)
syslog(LOG_NOTICE, buf2);
else
fprintf(stderr, "%s", buf2);
free(buf2);
free(buf);
}
static void warn(const char *fmt, ...)
{
va_list arglist;
va_start(arglist, fmt);
message("WARNING: ", fmt, &arglist);
va_end(arglist);
}
static void fatal(const char *fmt, ...)
{
va_list arglist;
va_start(arglist, fmt);
message("FATAL: ", fmt, &arglist);
va_end(arglist);
exit(1);
}
static void grammar(const char *cmd, const char *filename, unsigned int line)
{
warn("%s line %u: ignoring bad line starting with '%s'\n",
filename, line, cmd);
}
static void *do_nofail(void *ptr, const char *file, int line, const char *expr)
{
if (!ptr) {
fatal("Memory allocation failure %s line %d: %s.\n",
file, line, expr);
}
return ptr;
}
#define NOFAIL(ptr) do_nofail((ptr), __FILE__, __LINE__, #ptr)
static void print_usage(const char *progname)
{
fprintf(stderr,
"Usage: %s [-v] [-V] [-C config-file] [-n] [-i] [-q] [-o ] [parameters...]\n"
"%s -r [-n] [-i] [-v] ...\n"
"%s -l -t [ -a ...]\n",
progname, progname, progname);
exit(1);
}
static int fgetc_wrapped(FILE *file, unsigned int *linenum)
{
for (;;) {
int ch = fgetc(file);
if (ch != '\\')
return ch;
ch = fgetc(file);
if (ch != '\n')
return ch;
if (linenum)
(*linenum)++;
}
}
static char *getline_wrapped(FILE *file, unsigned int *linenum)
{
int size = 1024;
int i = 0;
char *buf = NOFAIL(malloc(size));
for(;;) {
int ch = fgetc_wrapped(file, linenum);
if (i == size) {
size *= 2;
buf = NOFAIL(realloc(buf, size));
}
if (ch < 0 && i == 0) {
free(buf);
return NULL;
}
if (ch < 0 || ch == '\n') {
if (linenum)
(*linenum)++;
buf[i] = '\0';
return NOFAIL(realloc(buf, i+1));
}
buf[i++] = ch;
}
}
static struct module *find_module(const char *filename, struct list_head *list)
{
struct module *i;
list_for_each_entry(i, list, list) {
if (strcmp(i->filename, filename) == 0)
return i;
}
return NULL;
}
/* Convert filename to the module name. Works if filename == modname, too. */
static void filename2modname(char *modname, const char *filename)
{
const char *afterslash;
unsigned int i;
afterslash = strrchr(filename, '/');
if (!afterslash)
afterslash = filename;
else
afterslash++;
/* Convert to underscores, stop at first . */
for (i = 0; afterslash[i] && afterslash[i] != '.'; i++) {
if (afterslash[i] == '-')
modname[i] = '_';
else
modname[i] = afterslash[i];
}
modname[i] = '\0';
}
static void add_module(char *filename, int namelen, struct list_head *list)
{
struct module *mod;
/* If it's a duplicate: move it to the end, so it gets
inserted where it is *first* required. */
mod = find_module(filename, list);
if (mod)
list_del(&mod->list);
else {
/* No match. Create a new module. */
mod = NOFAIL(malloc(sizeof(struct module) + namelen + 1));
memcpy(mod->filename, filename, namelen);
mod->filename[namelen] = '\0';
mod->modname = NOFAIL(malloc(namelen + 1));
filename2modname(mod->modname, mod->filename);
}
list_add_tail(&mod->list, list);
}
/* Compare len chars of a to b, with _ and - equivalent. */
static int modname_equal(const char *a, const char *b, unsigned int len)
{
unsigned int i;
if (strlen(b) != len)
return 0;
for (i = 0; i < len; i++) {
if ((a[i] == '_' || a[i] == '-')
&& (b[i] == '_' || b[i] == '-'))
continue;
if (a[i] != b[i])
return 0;
}
return 1;
}
/* Fills in list of modules if this is the line we want. */
static int add_modules_dep_line(char *line,
const char *name,
struct list_head *list)
{
char *ptr;
int len;
char *modname;
/* Ignore lines without : or which start with a # */
ptr = index(line, ':');
if (ptr == NULL || line[strspn(line, "\t ")] == '#')
return 0;
/* Is this the module we are looking for? */
*ptr = '\0';
if (strrchr(line, '/'))
modname = strrchr(line, '/') + 1;
else
modname = line;
len = strlen(modname);
if (strchr(modname, '.'))
len = strchr(modname, '.') - modname;
if (!modname_equal(modname, name, len))
return 0;
/* Create the list. */
add_module(line, ptr - line, list);
ptr++;
for(;;) {
char *dep_start;
ptr += strspn(ptr, " \t");
if (*ptr == '\0')
break;
dep_start = ptr;
ptr += strcspn(ptr, " \t");
add_module(dep_start, ptr - dep_start, list);
}
return 1;
}
static void read_depends(const char *dirname,
const char *start_name,
struct list_head *list)
{
char *modules_dep_name;
char *line;
FILE *modules_dep;
int done = 0;
asprintf(&modules_dep_name, "%s/%s", dirname, "modules.dep");
modules_dep = fopen(modules_dep_name, "r");
if (!modules_dep)
fatal("Could not load %s: %s\n",
modules_dep_name, strerror(errno));
/* Stop at first line, as we can have duplicates (eg. symlinks
from boot/ */
while (!done && (line = getline_wrapped(modules_dep, NULL)) != NULL) {
done = add_modules_dep_line(line, start_name, list);
free(line);
}
fclose(modules_dep);
free(modules_dep_name);
}
/* We use error numbers in a loose translation... */
static const char *insert_moderror(int err)
{
switch (err) {
case ENOEXEC:
return "Invalid module format";
case ENOENT:
return "Unknown symbol in module, or unknown parameter (see dmesg)";
case ENOSYS:
return "Kernel does not have module support";
default:
return strerror(err);
}
}
static const char *remove_moderror(int err)
{
switch (err) {
case ENOENT:
return "No such module";
case ENOSYS:
return "Kernel does not have module unloading support";
default:
return strerror(err);
}
}
/* Is module in /proc/modules? If so, fill in usecount if not NULL.
0 means no, 1 means yes, -1 means unknown.
*/
static int module_in_kernel(const char *modname, int *usecount)
{
FILE *proc_modules;
char *line;
/* Might not be mounted yet. Don't fail. */
proc_modules = fopen("/proc/modules", "r");
if (!proc_modules)
return -1;
while ((line = getline_wrapped(proc_modules, NULL)) != NULL) {
char *entry = strtok(line, " \n");
if (entry && strcmp(entry, modname) == 0) {
/* If it exists, usecount is the third entry. */
if (usecount) {
entry = strtok(NULL, " \n");
if (entry
&& (entry = strtok(NULL, " \n")) != NULL)
*usecount = atoi(entry);
}
free(line);
fclose(proc_modules);
return 1;
}
free(line);
}
fclose(proc_modules);
return 0;
}
static void replace_modname(struct module *module,
void *mem, unsigned long len,
const char *oldname, const char *newname)
{
char *p;
/* 64 - sizeof(unsigned long) - 1 */
if (strlen(newname) > 55)
fatal("New name %s is too long\n", newname);
/* Find where it is in the module structure. Don't assume layout! */
for (p = mem; p < (char *)mem + len - strlen(oldname); p++) {
if (memcmp(p, oldname, strlen(oldname)) == 0) {
strcpy(p, newname);
return;
}
}
warn("Could not find old name in %s to replace!\n", module->filename);
}
static void *get_section32(void *file,
unsigned long size,
const char *name,
unsigned long *secsize)
{
Elf32_Ehdr *hdr = file;
Elf32_Shdr *sechdrs = file + hdr->e_shoff;
const char *secnames;
unsigned int i;
/* Too short? */
if (size < sizeof(*hdr))
return NULL;
if (size < hdr->e_shoff + hdr->e_shnum * sizeof(sechdrs[0]))
return NULL;
if (size < sechdrs[hdr->e_shstrndx].sh_offset)
return NULL;
secnames = file + sechdrs[hdr->e_shstrndx].sh_offset;
for (i = 1; i < hdr->e_shnum; i++)
if (strcmp(secnames + sechdrs[i].sh_name, name) == 0) {
*secsize = sechdrs[i].sh_size;
return file + sechdrs[i].sh_offset;
}
return NULL;
}
static void *get_section64(void *file,
unsigned long size,
const char *name,
unsigned long *secsize)
{
Elf64_Ehdr *hdr = file;
Elf64_Shdr *sechdrs = file + hdr->e_shoff;
const char *secnames;
unsigned int i;
/* Too short? */
if (size < sizeof(*hdr))
return NULL;
if (size < hdr->e_shoff + hdr->e_shnum * sizeof(sechdrs[0]))
return NULL;
if (size < sechdrs[hdr->e_shstrndx].sh_offset)
return NULL;
secnames = file + sechdrs[hdr->e_shstrndx].sh_offset;
for (i = 1; i < hdr->e_shnum; i++)
if (strcmp(secnames + sechdrs[i].sh_name, name) == 0) {
*secsize = sechdrs[i].sh_size;
return file + sechdrs[i].sh_offset;
}
return NULL;
}
static int elf_ident(void *mod, unsigned long size)
{
/* "\177ELF" where byte = 001 for 32-bit, 002 for 64 */
char *ident = mod;
if (size < EI_CLASS || memcmp(mod, ELFMAG, SELFMAG) != 0)
return ELFCLASSNONE;
return ident[EI_CLASS];
}
static void *get_section(void *file,
unsigned long size,
const char *name,
unsigned long *secsize)
{
switch (elf_ident(file, size)) {
case ELFCLASS32:
return get_section32(file, size, name, secsize);
case ELFCLASS64:
return get_section64(file, size, name, secsize);
default:
return NULL;
}
}
static void rename_module(struct module *module,
void *mod,
unsigned long len,
const char *newname)
{
void *modstruct;
unsigned long modstruct_len;
/* Old-style */
modstruct = get_section(mod, len, ".gnu.linkonce.this_module",
&modstruct_len);
/* New-style */
if (!modstruct)
modstruct = get_section(mod, len, "__module", &modstruct_len);
if (!modstruct)
warn("Could not find module name to change in %s\n",
module->filename);
else
replace_modname(module, modstruct, modstruct_len,
module->modname, newname);
}
/* Kernel told to ignore these sections if SHF_ALLOC not set. */
static void invalidate_section32(void *mod, const char *secname)
{
Elf32_Ehdr *hdr = mod;
Elf32_Shdr *sechdrs = mod + hdr->e_shoff;
const char *secnames = mod + sechdrs[hdr->e_shstrndx].sh_offset;
unsigned int i;
for (i = 1; i < hdr->e_shnum; i++)
if (strcmp(secnames+sechdrs[i].sh_name, secname) == 0)
sechdrs[i].sh_flags &= ~SHF_ALLOC;
}
static void invalidate_section64(void *mod, const char *secname)
{
Elf64_Ehdr *hdr = mod;
Elf64_Shdr *sechdrs = mod + hdr->e_shoff;
const char *secnames = mod + sechdrs[hdr->e_shstrndx].sh_offset;
unsigned int i;
for (i = 1; i < hdr->e_shnum; i++)
if (strcmp(secnames+sechdrs[i].sh_name, secname) == 0)
sechdrs[i].sh_flags &= ~(unsigned long long)SHF_ALLOC;
}
static void strip_section(struct module *module,
void *mod,
unsigned long len,
const char *secname)
{
switch (elf_ident(mod, len)) {
case ELFCLASS32:
invalidate_section32(mod, secname);
break;
case ELFCLASS64:
invalidate_section64(mod, secname);
break;
default:
warn("Unknown module format in %s: not forcing version\n",
module->filename);
}
}
static const char *next_string(const char *string, unsigned long *secsize)
{
/* Skip non-zero chars */
while (string[0]) {
string++;
if ((*secsize)-- <= 1)
return NULL;
}
/* Skip any zero padding. */
while (!string[0]) {
string++;
if ((*secsize)-- <= 1)
return NULL;
}
return string;
}
static void clear_magic(struct module *module, void *mod, unsigned long len)
{
const char *p;
unsigned long modlen;
/* Old-style: __vermagic section */
strip_section(module, mod, len, "__vermagic");
/* New-style: in .modinfo section */
for (p = get_section(mod, len, ".modinfo", &modlen);
p;
p = next_string(p, &modlen)) {
if (strncmp(p, "vermagic=", strlen("vermagic=")) == 0) {
memset((char *)p, 0, strlen(p));
return;
}
}
}
struct module_options
{
struct module_options *next;
char *modulename;
char *options;
};
struct module_command
{
struct module_command *next;
char *modulename;
char *command;
};
/* Link in a new option line from the config file. */
static struct module_options *
add_options(const char *modname,
const char *option,
struct module_options *options)
{
struct module_options *new;
char *tab;
new = NOFAIL(malloc(sizeof(*new)));
new->modulename = NOFAIL(strdup(modname));
new->options = NOFAIL(strdup(option));
/* We can handle tabs, kernel can't. */
for (tab = strchr(new->options, '\t'); tab; tab = strchr(tab, '\t'))
*tab = ' ';
new->next = options;
return new;
}
/* Link in a new install line from the config file. */
static struct module_command *
add_command(const char *modname,
const char *command,
struct module_command *commands)
{
struct module_command *new;
new = NOFAIL(malloc(sizeof(*new)));
new->modulename = NOFAIL(strdup(modname));
new->command = NOFAIL(strdup(command));
new->next = commands;
return new;
}
/* Find install commands if any. */
static const char *find_command(const char *modname,
const struct module_command *commands)
{
while (commands) {
if (fnmatch(commands->modulename, modname, 0) == 0)
return commands->command;
commands = commands->next;
}
return NULL;
}
static char *append_option(char *options, const char *newoption)
{
options = NOFAIL(realloc(options, strlen(options) + 1
+ strlen(newoption) + 1));
if (strlen(options)) strcat(options, " ");
strcat(options, newoption);
return options;
}
/* Add to options */
static char *add_extra_options(const char *modname,
char *optstring,
const struct module_options *options)
{
while (options) {
if (strcmp(options->modulename, modname) == 0)
optstring = append_option(optstring, options->options);
options = options->next;
}
return optstring;
}
/* If we don't flush, then child processes print before we do */
static void verbose_printf(int verbose, const char *fmt, ...)
{
va_list arglist;
if (verbose) {
va_start(arglist, fmt);
vprintf(fmt, arglist);
fflush(stdout);
va_end(arglist);
}
}
/* Do an install/remove command. */
static void do_command(const char *modname,
const char *command,
int verbose, int dry_run,
errfn_t error,
const char *type)
{
int ret;
verbose_printf(verbose, "%s %s\n", type, command);
if (dry_run)
return;
setenv("MODPROBE_MODULE", modname, 1);
ret = system(command);
if (ret == -1 || WEXITSTATUS(ret))
error("Error running %s command for %s\n", type, modname);
}
/* Actually do the insert. Frees second arg. */
static void insmod(struct list_head *list,
char *optstring,
const char *newname,
int first_time,
errfn_t error,
int dry_run,
int verbose,
const struct module_options *options,
const struct module_command *commands,
int ignore_commands,
int ignore_proc,
int strip_vermagic,
int strip_modversion)
{
int ret;
unsigned long len;
void *map;
const char *command;
struct module *mod = list_entry(list->next, struct module, list);
/* Take us off the list. */
list_del(&mod->list);
/* Do things we (or parent) depend on first, but don't die if
* they fail. */
if (!list_empty(list)) {
insmod(list, NOFAIL(strdup("")), NULL, 0, warn,
dry_run, verbose, options, commands, 0, ignore_proc,
strip_vermagic, strip_modversion);
}
/* Don't do ANYTHING if already in kernel. */
if (!ignore_proc
&& module_in_kernel(newname ?: mod->modname, NULL) == 1)
goto exists_error;
command = find_command(mod->modname, commands);
if (command && !ignore_commands) {
do_command(mod->modname, command, verbose, dry_run, error,
"install");
goto out_optstring;
}
map = grab_file(mod->filename, &len);
if (!map) {
error("Could not open '%s': %s\n",
mod->filename, strerror(errno));
goto out_optstring;
}
/* Rename it? */
if (newname)
rename_module(mod, map, len, newname);
if (strip_modversion)
strip_section(mod, map, len, "__versions");
if (strip_vermagic)
clear_magic(mod, map, len);
/* Config file might have given more options */
optstring = add_extra_options(mod->modname, optstring, options);
verbose_printf(verbose, "insmod %s %s\n", mod->filename, optstring);
if (dry_run)
goto out;
ret = init_module(map, len, optstring);
if (ret != 0) {
if (errno == EEXIST)
goto exists_error;
error("Error inserting %s (%s): %s\n",
mod->modname, mod->filename, insert_moderror(errno));
}
out:
release_file(map, len);
out_optstring:
free(optstring);
return;
exists_error:
if (first_time)
error("Module %s already in kernel.\n",
newname ?: mod->modname);
goto out_optstring;
}
/* Do recursive removal. */
static void rmmod(struct list_head *list,
int first_time,
errfn_t error,
int dry_run,
int verbose,
struct module_command *commands,
int ignore_commands,
int ignore_inuse)
{
const char *command;
unsigned int usecount = 0;
struct module *mod = list_entry(list->next, struct module, list);
/* Take first one off the list. */
list_del(&mod->list);
command = find_command(mod->modname, commands);
if (command && !ignore_commands) {
do_command(mod->modname, command, verbose, dry_run, error,
"remove");
goto remove_rest;
}
if (module_in_kernel(mod->modname, &usecount) == 0)
goto nonexistent_module;
if (usecount != 0) {
if (!ignore_inuse)
error("Module %s is in use.\n", mod->modname);
goto remove_rest;
}
verbose_printf(verbose, "rmmod %s\n", mod->filename);
if (dry_run)
goto remove_rest;
if (delete_module(mod->modname, O_EXCL) != 0) {
if (errno == ENOENT)
goto nonexistent_module;
error("Error removing %s (%s): %s\n",
mod->modname, mod->filename,
remove_moderror(errno));
}
remove_rest:
/* Now do things we depend. */
if (!list_empty(list))
rmmod(list, 0, warn, dry_run, verbose, commands,
0, 1);
return;
nonexistent_module:
if (first_time)
fatal("Module %s is not in kernel.\n", mod->modname);
goto remove_rest;
}
/* Does path contain directory(s) subpath? */
static int type_matches(const char *path, const char *subpath)
{
char *subpath_with_slashes;
int ret;
asprintf(&subpath_with_slashes, "/%s/", subpath);
NOFAIL(subpath_with_slashes);
ret = (strstr(path, subpath_with_slashes) != NULL);
free(subpath_with_slashes);
return ret;
}
static char *underscores(char *string)
{
if (string) {
unsigned int i;
for (i = 0; string[i]; i++)
if (string[i] == '-')
string[i] = '_';
}
return string;
}
static int do_wildcard(const char *dirname,
const char *type,
const char *wildcard)
{
char modules_dep_name[strlen(dirname) + sizeof("modules.dep") + 1];
char *line, *wcard;
FILE *modules_dep;
/* Canonicalize wildcard */
wcard = strdup(wildcard);
underscores(wcard);
sprintf(modules_dep_name, "%s/%s", dirname, "modules.dep");
modules_dep = fopen(modules_dep_name, "r");
if (!modules_dep)
fatal("Could not load %s: %s\n",
modules_dep_name, strerror(errno));
while ((line = getline_wrapped(modules_dep, NULL)) != NULL) {
char *ptr;
/* Ignore lines without : or which start with a # */
ptr = strchr(line, ':');
if (ptr == NULL || line[strspn(line, "\t ")] == '#')
goto next;
*ptr = '\0';
/* "type" must match complete directory component(s). */
if (!type || type_matches(line, type)) {
char modname[strlen(line)+1];
filename2modname(modname, line);
if (fnmatch(wcard, modname, 0) == 0)
printf("%s\n", line);
}
next:
free(line);
}
free(wcard);
return 0;
}
static char *strsep_skipspace(char **string, char *delim)
{
if (!*string)
return NULL;
*string += strspn(*string, delim);
return strsep(string, delim);
}
/* FIXME: Maybe should be extended to "alias a b [and|or c]...". --RR */
/* Simple format, ignore lines starting with #, one command per line.
Returns NULL or resolved alias. */
static char *read_config(const char *filename,
int mustload,
const char *name,
int dump_only,
int removing,
struct module_options **options,
struct module_command **commands)
{
FILE *cfile;
char *line;
char *result = NULL;
unsigned int linenum = 0;
cfile = fopen(filename, "r");
if (!cfile) {
if (mustload)
fatal("Failed to open config file %s: %s\n",
filename, strerror(errno));
return NULL;
}
while ((line = getline_wrapped(cfile, &linenum)) != NULL) {
char *ptr = line;
char *cmd, *modname;
if (dump_only)
printf("%s\n", line);
cmd = strsep_skipspace(&ptr, "\t ");
if (cmd == NULL || cmd[0] == '#' || cmd[0] == '\0')
continue;
if (strcmp(cmd, "alias") == 0) {
char *wildcard
= underscores(strsep_skipspace(&ptr, "\t "));
char *realname
= underscores(strsep_skipspace(&ptr, "\t "));
if (!wildcard || !realname)
grammar(cmd, filename, linenum);
else if (fnmatch(wildcard,name,0) == 0)
result = NOFAIL(strdup(realname));
} else if (strcmp(cmd, "include") == 0) {
char *newresult, *newfilename;
newfilename = strsep_skipspace(&ptr, "\t ");
if (!newfilename)
grammar(cmd, filename, linenum);
else {
newresult = read_config(newfilename, 1, name,
dump_only, removing,
options, commands);
/* Files included override aliases,
etc that was already set ... */
if (newresult)
result = newresult;
}
} else if (strcmp(cmd, "options") == 0) {
modname = strsep_skipspace(&ptr, "\t ");
if (!modname || !ptr)
grammar(cmd, filename, linenum);
else {
ptr += strspn(ptr, "\t ");
*options = add_options(underscores(modname),
ptr, *options);
}
} else if (strcmp(cmd, "install") == 0) {
modname = strsep_skipspace(&ptr, "\t ");
if (!modname || !ptr)
grammar(cmd, filename, linenum);
else if (!removing) {
ptr += strspn(ptr, "\t ");
*commands = add_command(underscores(modname),
ptr, *commands);
}
} else if (strcmp(cmd, "remove") == 0) {
modname = strsep_skipspace(&ptr, "\t ");
if (!modname || !ptr)
grammar(cmd, filename, linenum);
else if (removing) {
ptr += strspn(ptr, "\t ");
*commands = add_command(underscores(modname),
ptr, *commands);
}
} else
grammar(cmd, filename, linenum);
free(line);
}
fclose(cfile);
return result;
}
static void add_to_env_var(const char *option)
{
const char *oldenv;
if ((oldenv = getenv("MODPROBE_OPTIONS")) != NULL) {
char *newenv;
asprintf(&newenv, "%s %s", oldenv, option);
setenv("MODPROBE_OPTIONS", newenv, 1);
} else
setenv("MODPROBE_OPTIONS", option, 1);
}
/* Prepend options from environment. */
static char **merge_args(char *args, char *argv[], int *argc)
{
char *arg, *argstring;
char **newargs = NULL;
unsigned int i, num_env = 0;
if (!args)
return argv;
argstring = NOFAIL(strdup(args));
for (arg = strtok(argstring, " "); arg; arg = strtok(NULL, " ")) {
num_env++;
newargs = NOFAIL(realloc(newargs,
sizeof(newargs[0])
* (num_env + *argc + 1)));
newargs[num_env] = arg;
}
/* Append commandline args */
newargs[0] = argv[0];
for (i = 1; i <= *argc; i++)
newargs[num_env+i] = argv[i];
*argc += num_env;
return newargs;
}
static char *gather_options(char *argv[])
{
char *optstring = NOFAIL(strdup(""));
/* Rest is module options */
while (*argv) {
if (strchr(*argv, ' ')) {
/* Spaces handled by "" pairs, but no way of
escaping quotes */
char protected_option[strlen(optstring) + 3];
sprintf(protected_option, "\"%s\"", *argv);
optstring = append_option(optstring, protected_option);
} else
optstring = append_option(optstring, *argv);
argv++;
}
return optstring;
}
static struct option options[] = { { "verbose", 0, NULL, 'v' },
{ "version", 0, NULL, 'V' },
{ "config", 1, NULL, 'C' },
{ "name", 1, NULL, 'o' },
{ "remove", 0, NULL, 'r' },
{ "showconfig", 0, NULL, 'c' },
{ "autoclean", 0, NULL, 'k' },
{ "quiet", 0, NULL, 'q' },
{ "show", 0, NULL, 'n' },
{ "dry-run", 0, NULL, 'n' },
{ "syslog", 0, NULL, 's' },
{ "type", 1, NULL, 't' },
{ "list", 0, NULL, 'l' },
{ "all", 0, NULL, 'a' },
{ "ignore-install", 0, NULL, 'i' },
{ "ignore-remove", 0, NULL, 'i' },
{ "force", 0, NULL, 'f' },
{ "force-vermagic", 0, NULL, 1 },
{ "force-modversion", 0, NULL, 2 },
{ "set-version", 1, NULL, 'S' },
{ "show-depends", 0, NULL, 'D' },
{ "first-time", 0, NULL, 3 },
{ NULL, 0, NULL, 0 } };
#define DEFAULT_CONFIG "/etc/modprobe.conf"
#define MODPROBE_DEVFSD_CONF "/etc/modprobe.devfs"
/* This is a horrible hack to allow devfsd, which calls modprobe with
-C /etc/modules.conf or /etc/modules.devfs, to work. FIXME. */
/* Modern devfsd or variants should use -q explicitly in 2.6. */
static int is_devfs_call(char *argv[])
{
unsigned int i;
/* Look for "/dev" arg */
for (i = 1; argv[i]; i++) {
if (strncmp(argv[i], "/dev/", 5) == 0)
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
struct utsname buf;
struct stat statbuf;
int opt;
int dump_only = 0;
int dry_run = 0;
int remove = 0;
int verbose = 0;
int unknown_silent = 0;
int list_only = 0;
int all = 0;
int ignore_commands = 0;
int strip_vermagic = 0;
int strip_modversion = 0;
int ignore_proc = 0;
int first_time = 0;
unsigned int i, num_modules;
char *type = NULL;
const char *config = NULL, *command = NULL;
char *dirname, *optstring;
char *modname, *newname = NULL;
char *aliasfilename, *symfilename;
errfn_t error = fatal;
/* Prepend options from environment. */
argv = merge_args(getenv("MODPROBE_OPTIONS"), argv, &argc);
/* --set-version overrides version, and disables backwards compat. */
for (opt = 1; opt < argc; opt++)
if (strncmp(argv[opt],"--set-version",strlen("--set-version"))
== 0)
break;
if (opt == argc)
try_old_version("modprobe", argv);
uname(&buf);
while ((opt = getopt_long(argc, argv, "vVC:o:rknqQsclt:aif", options, NULL)) != -1){
switch (opt) {
case 'v':
add_to_env_var("-v");
verbose = 1;
break;
case 'V':
puts(PACKAGE " version " VERSION);
exit(0);
case 'S':
strncpy(buf.release, optarg, sizeof(buf.release));
buf.release[sizeof(buf.release)-1] = '\0';
break;
case 'C':
if (is_devfs_call(argv)) {
if (streq("/etc/modules.devfs", optarg)) {
config = MODPROBE_DEVFSD_CONF;
add_to_env_var("-C");
add_to_env_var(config);
/* Fall thru to -q */
} else if (streq("/etc/modules.conf", optarg))
/* Ignore config, fall thru to -q */
;
else {
/* False alarm. Treat as normal. */
config = optarg;
add_to_env_var("-C");
add_to_env_var(config);
break;
}
} else {
config = optarg;
add_to_env_var("-C");
add_to_env_var(config);
break;
}
case 'q':
unknown_silent = 1;
add_to_env_var("-q");
break;
case 'D':
dry_run = 1;
ignore_proc = 1;
verbose = 1;
add_to_env_var("-D");
break;
case 'o':
newname = optarg;
break;
case 'r':
remove = 1;
break;
case 'c':
dump_only = 1;
break;
case 't':
type = optarg;
break;
case 'l':
list_only = 1;
break;
case 'a':
all = 1;
error = warn;
break;
case 'k':
/* FIXME: This should actually do something */
break;
case 'n':
dry_run = 1;
break;
case 's':
add_to_env_var("-s");
log = 1;
break;
case 'i':
ignore_commands = 1;
break;
case 'f':
strip_vermagic = 1;
strip_modversion = 1;
break;
case 1:
strip_vermagic = 1;
break;
case 2:
strip_modversion = 1;
break;
case 3:
first_time = 1;
break;
default:
print_usage(argv[0]);
}
}
/* If stderr not open, go to syslog */
if (log || fstat(STDERR_FILENO, &statbuf) != 0) {
openlog("modprobe", LOG_CONS, LOG_DAEMON);
log = 1;
}
if (argc < optind + 1 && !dump_only && !list_only && !remove)
print_usage(argv[0]);
dirname = NOFAIL(malloc(strlen(buf.release) + sizeof(MODULE_DIR) + 1));
sprintf(dirname, "%s/%s", MODULE_DIR, buf.release);
aliasfilename = NOFAIL(malloc(strlen(dirname)
+ sizeof("/modules.alias")));
sprintf(aliasfilename, "%s/modules.alias", dirname);
symfilename = NOFAIL(malloc(strlen(dirname)
+ sizeof("/modules.symbols")));
sprintf(symfilename, "%s/modules.symbols", dirname);
/* Old-style -t xxx wildcard? Only with -l. */
if (list_only) {
if (optind+1 < argc)
fatal("Can't have multiple wildcards\n");
/* fprintf(stderr, "man find\n"); return 1; */
return do_wildcard(dirname, type, argv[optind]?:"*");
}
if (type)
fatal("-t only supported with -l");
if (dump_only) {
struct module_command *commands = NULL;
struct module_options *modoptions = NULL;
read_config(config ?: DEFAULT_CONFIG,
config ? 1 : 0, "", 1, 0, &modoptions, &commands);
read_config(aliasfilename, 0, "", 1, 0,&modoptions, &commands);
read_config(symfilename, 0, "", 1, 0, &modoptions, &commands);
exit(0);
}
if (remove || all) {
num_modules = argc - optind;
optstring = NOFAIL(strdup(""));
/* -r only allows certain restricted options */
if (newname)
fatal("Can't specify replacement name with -%c\n",
remove ? 'r' : 'a');
} else {
num_modules = 1;
optstring = gather_options(argv+optind+1);
}
/* num_modules is always 1 except for -r or -a. */
for (i = 0; i < num_modules; i++) {
struct module_command *commands = NULL;
struct module_options *modoptions = NULL;
LIST_HEAD(list);
char *modulearg = argv[optind + i];
/* Convert name we are looking for */
underscores(modulearg);
/* Returns the resolved alias, options */
modname = read_config(config ?: DEFAULT_CONFIG,
config ? 1 : 0,
modulearg, 0,
remove, &modoptions, &commands);
/* No luck? Try symbol names, if starts with symbol:. */
if (!modname
&& strncmp(modulearg, "symbol:", strlen("symbol:") == 0))
modname = read_config(symfilename, 0, modulearg, 0,
remove, &modoptions, &commands);
/* If we have an alias, gather any options associated with it
(needs to happen after parsing complete). */
if (modname) {
got_modname:
optstring = add_extra_options(modulearg, optstring,
modoptions);
read_depends(dirname, modname, &list);
} else {
read_depends(dirname, modulearg, &list);
/* We don't allow canned aliases to override real
modules, so we delay lookup until now. */
if (list_empty(&list)
&& (modname = read_config(aliasfilename, 0,
modulearg, 0,
remove, &modoptions,
&commands)))
goto got_modname;
modname = strdup(modulearg);
}
if (list_empty(&list)) {
/* The dependencies have to be real modules, but
handle case where the first is completely bogus. */
command = find_command(modname, commands);
if (command && !ignore_commands) {
do_command(modname, command, verbose, dry_run,
fatal, remove ? "remove":"install");
continue;
}
if (unknown_silent)
exit(1);
error("Module %s not found.\n", modname);
continue;
}
if (remove)
rmmod(&list, first_time, error, dry_run, verbose,
commands, ignore_commands, 0);
else
insmod(&list, NOFAIL(strdup(optstring)), newname,
first_time, error, dry_run, verbose, modoptions,
commands, ignore_commands, ignore_proc,
strip_vermagic, strip_modversion);
free(modname);
}
free(dirname);
free(aliasfilename);
free(symfilename);
free(optstring);
if (log)
closelog();
return 0;
}