bdf6319d3a
Attached patch prevents modprobe from trying to call 'insmod (null)' whenever nonexistent module is either passed to modprobe via command line or mentioned in modules.dep this replaces cryptic error sh: Syntax error: word unexpected (expecting ")") with modprobe: module some-module not found. egor.
655 lines
14 KiB
C
655 lines
14 KiB
C
/* vi: set sw=4 ts=4: */
|
|
/*
|
|
* Modprobe written from scratch for BusyBox
|
|
*
|
|
* Copyright (c) 2002 by Robert Griebl, griebl@gmx.de
|
|
* Copyright (c) 2003 by Andrew Dennison, andrew.dennison@motec.com.au
|
|
*
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
#include <sys/utsname.h>
|
|
#include <getopt.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <syslog.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include "busybox.h"
|
|
|
|
|
|
|
|
struct dep_t {
|
|
char * m_name;
|
|
char * m_path;
|
|
char * m_options;
|
|
|
|
int m_isalias : 1;
|
|
int m_reserved : 15;
|
|
|
|
int m_depcnt : 16;
|
|
char ** m_deparr;
|
|
|
|
struct dep_t * m_next;
|
|
};
|
|
|
|
struct mod_list_t {
|
|
char * m_name;
|
|
char * m_path;
|
|
char * m_options;
|
|
|
|
struct mod_list_t * m_prev;
|
|
struct mod_list_t * m_next;
|
|
};
|
|
|
|
|
|
static struct dep_t *depend;
|
|
static int autoclean, show_only, quiet, do_syslog, verbose;
|
|
static int k_version;
|
|
|
|
int parse_tag_value ( char *buffer, char **ptag, char **pvalue )
|
|
{
|
|
char *tag, *value;
|
|
|
|
while ( isspace ( *buffer ))
|
|
buffer++;
|
|
tag = value = buffer;
|
|
while ( !isspace ( *value ))
|
|
if (!*value) return 0;
|
|
else value++;
|
|
*value++ = 0;
|
|
while ( isspace ( *value ))
|
|
value++;
|
|
if (!*value) return 0;
|
|
|
|
*ptag = tag;
|
|
*pvalue = value;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Jump through hoops to simulate how fgets() grabs just one line at a
|
|
* time... Don't use any stdio since modprobe gets called from a kernel
|
|
* thread and stdio junk can overflow the limited stack...
|
|
*/
|
|
static char *reads ( int fd, char *buffer, size_t len )
|
|
{
|
|
int n = read ( fd, buffer, len );
|
|
|
|
if ( n > 0 ) {
|
|
char *p;
|
|
|
|
buffer [len-1] = 0;
|
|
p = strchr ( buffer, '\n' );
|
|
|
|
if ( p ) {
|
|
off_t offset;
|
|
|
|
offset = lseek ( fd, 0L, SEEK_CUR ); // Get the current file descriptor offset
|
|
lseek ( fd, offset-n + (p-buffer) + 1, SEEK_SET ); // Set the file descriptor offset to right after the \n
|
|
|
|
p[1] = 0;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static struct dep_t *build_dep ( void )
|
|
{
|
|
int fd;
|
|
struct utsname un;
|
|
struct dep_t *first = 0;
|
|
struct dep_t *current = 0;
|
|
char buffer[2048];
|
|
char *filename = buffer;
|
|
int continuation_line = 0;
|
|
|
|
k_version = 0;
|
|
if ( uname ( &un ))
|
|
return 0;
|
|
|
|
// check for buffer overflow in following code
|
|
if ( bb_strlen ( un.release ) > ( sizeof( buffer ) - 64 )) {
|
|
return 0;
|
|
}
|
|
if (un.release[0] == '2') {
|
|
k_version = un.release[2] - '0';
|
|
}
|
|
|
|
strcpy ( filename, "/lib/modules/" );
|
|
strcat ( filename, un.release );
|
|
strcat ( filename, "/modules.dep" );
|
|
|
|
if (( fd = open ( filename, O_RDONLY )) < 0 ) {
|
|
|
|
/* Ok, that didn't work. Fall back to looking in /lib/modules */
|
|
if (( fd = open ( "/lib/modules/modules.dep", O_RDONLY )) < 0 ) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
while ( reads ( fd, buffer, sizeof( buffer ))) {
|
|
int l = bb_strlen ( buffer );
|
|
char *p = 0;
|
|
|
|
while ( isspace ( buffer [l-1] )) {
|
|
buffer [l-1] = 0;
|
|
l--;
|
|
}
|
|
|
|
if ( l == 0 ) {
|
|
continuation_line = 0;
|
|
continue;
|
|
}
|
|
|
|
if ( !continuation_line ) {
|
|
char *col = strchr ( buffer, ':' );
|
|
char *dot = col;
|
|
|
|
if ( col ) {
|
|
char *mods;
|
|
char *modpath;
|
|
char *mod;
|
|
|
|
*col = 0;
|
|
mods = strrchr ( buffer, '/' );
|
|
|
|
if ( !mods )
|
|
mods = buffer;
|
|
else
|
|
mods++;
|
|
|
|
modpath = strchr ( buffer, '/' );
|
|
if ( !modpath )
|
|
modpath = buffer;
|
|
#if defined(CONFIG_FEATURE_2_6_MODULES)
|
|
if ((k_version > 4) && ( *(col-3) == '.' ) &&
|
|
( *(col-2) == 'k' ) && ( *(col-1) == 'o' ))
|
|
dot = col - 3;
|
|
else
|
|
#endif
|
|
if (( *(col-2) == '.' ) && ( *(col-1) == 'o' ))
|
|
dot = col - 2;
|
|
|
|
mod = bb_xstrndup ( mods, dot - mods );
|
|
|
|
if ( !current ) {
|
|
first = current = (struct dep_t *) xmalloc ( sizeof ( struct dep_t ));
|
|
}
|
|
else {
|
|
current-> m_next = (struct dep_t *) xmalloc ( sizeof ( struct dep_t ));
|
|
current = current-> m_next;
|
|
}
|
|
current-> m_name = mod;
|
|
current-> m_path = bb_xstrdup(modpath);
|
|
current-> m_options = 0;
|
|
current-> m_isalias = 0;
|
|
current-> m_depcnt = 0;
|
|
current-> m_deparr = 0;
|
|
current-> m_next = 0;
|
|
|
|
//printf ( "%s:\n", mod );
|
|
p = col + 1;
|
|
}
|
|
else
|
|
p = 0;
|
|
}
|
|
else
|
|
p = buffer;
|
|
|
|
while ( p && *p && isblank(*p))
|
|
p++;
|
|
|
|
if ( p && *p ) {
|
|
char *end = &buffer [l-1];
|
|
char *deps;
|
|
char *dep;
|
|
char *next;
|
|
int ext = 0;
|
|
|
|
while ( isblank ( *end ) || ( *end == '\\' ))
|
|
end--;
|
|
|
|
do
|
|
{
|
|
next = strchr (p, ' ' );
|
|
if (next)
|
|
{
|
|
*next = 0;
|
|
next--;
|
|
}
|
|
else
|
|
next = end;
|
|
|
|
deps = strrchr ( p, '/' );
|
|
|
|
if ( !deps || ( deps < p )) {
|
|
deps = p;
|
|
|
|
while ( isblank ( *deps ))
|
|
deps++;
|
|
}
|
|
else
|
|
deps++;
|
|
|
|
#if defined(CONFIG_FEATURE_2_6_MODULES)
|
|
if ((k_version > 4) && ( *(next-2) == '.' ) && *(next-1) == 'k' &&
|
|
( *next == 'o' ))
|
|
ext = 3;
|
|
else
|
|
#endif
|
|
if (( *(next-1) == '.' ) && ( *next == 'o' ))
|
|
ext = 2;
|
|
|
|
/* Cope with blank lines */
|
|
if ((next-deps-ext+1) <= 0)
|
|
continue;
|
|
dep = bb_xstrndup ( deps, next - deps - ext + 1 );
|
|
|
|
current-> m_depcnt++;
|
|
current-> m_deparr = (char **) xrealloc ( current-> m_deparr,
|
|
sizeof ( char *) * current-> m_depcnt );
|
|
current-> m_deparr [current-> m_depcnt - 1] = dep;
|
|
|
|
//printf ( " %d) %s\n", current-> m_depcnt, current-> m_deparr [current-> m_depcnt -1] );
|
|
p = next + 2;
|
|
} while (next < end);
|
|
}
|
|
|
|
if ( buffer [l-1] == '\\' )
|
|
continuation_line = 1;
|
|
else
|
|
continuation_line = 0;
|
|
}
|
|
close ( fd );
|
|
|
|
// alias parsing is not 100% correct (no correct handling of continuation lines within an alias) !
|
|
|
|
#if defined(CONFIG_FEATURE_2_6_MODULES)
|
|
if (( fd = open ( "/etc/modprobe.conf", O_RDONLY )) < 0 )
|
|
#endif
|
|
if (( fd = open ( "/etc/modules.conf", O_RDONLY )) < 0 )
|
|
if (( fd = open ( "/etc/conf.modules", O_RDONLY )) < 0 )
|
|
return first;
|
|
|
|
continuation_line = 0;
|
|
while ( reads ( fd, buffer, sizeof( buffer ))) {
|
|
int l;
|
|
char *p;
|
|
|
|
p = strchr ( buffer, '#' );
|
|
if ( p )
|
|
*p = 0;
|
|
|
|
l = bb_strlen ( buffer );
|
|
|
|
while ( l && isspace ( buffer [l-1] )) {
|
|
buffer [l-1] = 0;
|
|
l--;
|
|
}
|
|
|
|
if ( l == 0 ) {
|
|
continuation_line = 0;
|
|
continue;
|
|
}
|
|
|
|
if ( !continuation_line ) {
|
|
if (( strncmp ( buffer, "alias", 5 ) == 0 ) && isspace ( buffer [5] )) {
|
|
char *alias, *mod;
|
|
|
|
if ( parse_tag_value ( buffer + 6, &alias, &mod )) {
|
|
// fprintf ( stderr, "ALIAS: '%s' -> '%s'\n", alias, mod );
|
|
|
|
if ( !current ) {
|
|
first = current = (struct dep_t *) xcalloc ( 1, sizeof ( struct dep_t ));
|
|
}
|
|
else {
|
|
current-> m_next = (struct dep_t *) xcalloc ( 1, sizeof ( struct dep_t ));
|
|
current = current-> m_next;
|
|
}
|
|
current-> m_name = bb_xstrdup ( alias );
|
|
current-> m_isalias = 1;
|
|
|
|
if (( strcmp ( mod, "off" ) == 0 ) || ( strcmp ( mod, "null" ) == 0 )) {
|
|
current-> m_depcnt = 0;
|
|
current-> m_deparr = 0;
|
|
}
|
|
else {
|
|
current-> m_depcnt = 1;
|
|
current-> m_deparr = xmalloc ( 1 * sizeof( char * ));
|
|
current-> m_deparr[0] = bb_xstrdup ( mod );
|
|
}
|
|
current-> m_next = 0;
|
|
}
|
|
}
|
|
else if (( strncmp ( buffer, "options", 7 ) == 0 ) && isspace ( buffer [7] )) {
|
|
char *mod, *opt;
|
|
|
|
if ( parse_tag_value ( buffer + 8, &mod, &opt )) {
|
|
struct dep_t *dt;
|
|
|
|
for ( dt = first; dt; dt = dt-> m_next ) {
|
|
if ( strcmp ( dt-> m_name, mod ) == 0 )
|
|
break;
|
|
}
|
|
if ( dt ) {
|
|
dt-> m_options = xrealloc ( dt-> m_options, bb_strlen( opt ) + 1 );
|
|
strcpy ( dt-> m_options, opt );
|
|
|
|
// fprintf ( stderr, "OPTION: '%s' -> '%s'\n", dt-> m_name, dt-> m_options );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
close ( fd );
|
|
|
|
return first;
|
|
}
|
|
|
|
/* return 1 = loaded, 0 = not loaded, -1 = can't tell */
|
|
static int already_loaded (const char *name)
|
|
{
|
|
int fd;
|
|
char buffer[4096];
|
|
|
|
fd = open ("/proc/modules", O_RDONLY);
|
|
if (fd < 0)
|
|
return -1;
|
|
|
|
while ( reads ( fd, buffer, sizeof( buffer ))) {
|
|
char *p;
|
|
|
|
p = strchr (buffer, ' ');
|
|
if (p) {
|
|
*p = 0;
|
|
if (strcmp (name, buffer) == 0) {
|
|
close (fd);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
close (fd);
|
|
return 0;
|
|
}
|
|
|
|
static int mod_process ( struct mod_list_t *list, int do_insert )
|
|
{
|
|
char lcmd [4096];
|
|
int rc = 0;
|
|
|
|
while ( list ) {
|
|
*lcmd = '\0';
|
|
if ( do_insert ) {
|
|
if (already_loaded (list->m_name) != 1)
|
|
snprintf ( lcmd, sizeof( lcmd ) - 1, "insmod %s %s %s %s %s",
|
|
do_syslog ? "-s" : "", autoclean ? "-k" : "",
|
|
quiet ? "-q" : "", list-> m_path, list-> m_options ?
|
|
list-> m_options : "" );
|
|
} else {
|
|
/* modutils uses short name for removal */
|
|
if (already_loaded (list->m_name) != 0)
|
|
snprintf ( lcmd, sizeof( lcmd ) - 1, "rmmod %s %s",
|
|
do_syslog ? "-s" : "", list-> m_name );
|
|
}
|
|
|
|
if (*lcmd) {
|
|
if (verbose) {
|
|
printf("%s\n", lcmd);
|
|
}
|
|
if (!show_only) {
|
|
int rc2 = system(lcmd);
|
|
if (do_insert) {
|
|
rc = rc2; /* only last module matters */
|
|
}
|
|
else if (!rc2) {
|
|
rc = 0; /* success if remove any mod */
|
|
}
|
|
}
|
|
}
|
|
list = do_insert ? list-> m_prev : list-> m_next;
|
|
}
|
|
return (show_only) ? 0 : rc;
|
|
}
|
|
|
|
static void check_dep ( char *mod, struct mod_list_t **head, struct mod_list_t **tail )
|
|
{
|
|
struct mod_list_t *find;
|
|
struct dep_t *dt;
|
|
char *opt = 0;
|
|
char *path = 0;
|
|
|
|
// check dependencies
|
|
for ( dt = depend; dt; dt = dt-> m_next ) {
|
|
if ( strcmp ( dt-> m_name, mod ) == 0) {
|
|
mod = dt-> m_name;
|
|
path = dt-> m_path;
|
|
opt = dt-> m_options;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// resolve alias names
|
|
while ( dt && dt-> m_isalias ) {
|
|
if ( dt-> m_depcnt == 1 ) {
|
|
struct dep_t *adt;
|
|
|
|
for ( adt = depend; adt; adt = adt-> m_next ) {
|
|
if ( strcmp ( adt-> m_name, dt-> m_deparr [0] ) == 0 )
|
|
break;
|
|
}
|
|
if ( adt ) {
|
|
dt = adt;
|
|
mod = dt-> m_name;
|
|
path = dt-> m_path;
|
|
if ( !opt )
|
|
opt = dt-> m_options;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
|
|
if ( !path ) {
|
|
bb_error_msg ("module %s not found.", mod);
|
|
return;
|
|
}
|
|
|
|
// search for duplicates
|
|
for ( find = *head; find; find = find-> m_next ) {
|
|
if ( !strcmp ( mod, find-> m_name )) {
|
|
// found -> dequeue it
|
|
|
|
if ( find-> m_prev )
|
|
find-> m_prev-> m_next = find-> m_next;
|
|
else
|
|
*head = find-> m_next;
|
|
|
|
if ( find-> m_next )
|
|
find-> m_next-> m_prev = find-> m_prev;
|
|
else
|
|
*tail = find-> m_prev;
|
|
|
|
break; // there can be only one duplicate
|
|
}
|
|
}
|
|
|
|
if ( !find ) { // did not find a duplicate
|
|
find = (struct mod_list_t *) xmalloc ( sizeof(struct mod_list_t));
|
|
find-> m_name = mod;
|
|
find-> m_path = path;
|
|
find-> m_options = opt;
|
|
}
|
|
|
|
// enqueue at tail
|
|
if ( *tail )
|
|
(*tail)-> m_next = find;
|
|
find-> m_prev = *tail;
|
|
find-> m_next = 0;
|
|
|
|
if ( !*head )
|
|
*head = find;
|
|
*tail = find;
|
|
|
|
if ( dt ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < dt-> m_depcnt; i++ )
|
|
check_dep ( dt-> m_deparr [i], head, tail );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static int mod_insert ( char *mod, int argc, char **argv )
|
|
{
|
|
struct mod_list_t *tail = 0;
|
|
struct mod_list_t *head = 0;
|
|
int rc;
|
|
|
|
// get dep list for module mod
|
|
check_dep ( mod, &head, &tail );
|
|
|
|
if ( head && tail ) {
|
|
#if defined(CONFIG_FEATURE_2_6_MODULES)
|
|
if ( argc ) {
|
|
int i;
|
|
int l = 0;
|
|
|
|
// append module args
|
|
for ( i = 0; i < argc; i++ )
|
|
l += ( bb_strlen ( argv [i] ) + 1 );
|
|
|
|
head-> m_options = xrealloc ( head-> m_options, l + 1 );
|
|
head-> m_options [0] = 0;
|
|
|
|
for ( i = 0; i < argc; i++ ) {
|
|
strcat ( head-> m_options, argv [i] );
|
|
strcat ( head-> m_options, " " );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// process tail ---> head
|
|
rc = mod_process ( tail, 1 );
|
|
}
|
|
else
|
|
rc = 1;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mod_remove ( char *mod )
|
|
{
|
|
int rc;
|
|
static struct mod_list_t rm_a_dummy = { "-a", 0, 0 };
|
|
|
|
struct mod_list_t *head = 0;
|
|
struct mod_list_t *tail = 0;
|
|
|
|
if ( mod )
|
|
check_dep ( mod, &head, &tail );
|
|
else // autoclean
|
|
head = tail = &rm_a_dummy;
|
|
|
|
if ( head && tail )
|
|
rc = mod_process ( head, 0 ); // process head ---> tail
|
|
else
|
|
rc = 1;
|
|
return rc;
|
|
|
|
}
|
|
|
|
extern int modprobe_main(int argc, char** argv)
|
|
{
|
|
int opt;
|
|
int remove_opt = 0;
|
|
|
|
autoclean = show_only = quiet = do_syslog = verbose = 0;
|
|
|
|
while ((opt = getopt(argc, argv, "acdklnqrst:vVC:")) != -1) {
|
|
switch(opt) {
|
|
case 'c': // no config used
|
|
case 'l': // no pattern matching
|
|
return EXIT_SUCCESS;
|
|
break;
|
|
case 'C': // no config used
|
|
case 't': // no pattern matching
|
|
bb_error_msg_and_die("-t and -C not supported");
|
|
|
|
case 'a': // ignore
|
|
case 'd': // ignore
|
|
break;
|
|
case 'k':
|
|
autoclean++;
|
|
break;
|
|
case 'n':
|
|
show_only++;
|
|
break;
|
|
case 'q':
|
|
quiet++;
|
|
break;
|
|
case 'r':
|
|
remove_opt++;
|
|
break;
|
|
case 's':
|
|
do_syslog++;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
case 'V':
|
|
default:
|
|
bb_show_usage();
|
|
break;
|
|
}
|
|
}
|
|
|
|
depend = build_dep ( );
|
|
|
|
if ( !depend )
|
|
bb_error_msg_and_die ( "could not parse modules.dep\n" );
|
|
|
|
if (remove_opt) {
|
|
int rc = EXIT_SUCCESS;
|
|
do {
|
|
if (mod_remove ( optind < argc ?
|
|
bb_xstrdup (argv [optind]) : NULL )) {
|
|
bb_error_msg ("failed to remove module %s",
|
|
argv [optind] );
|
|
rc = EXIT_FAILURE;
|
|
}
|
|
} while ( ++optind < argc );
|
|
|
|
return rc;
|
|
}
|
|
|
|
if (optind >= argc)
|
|
bb_error_msg_and_die ( "No module or pattern provided\n" );
|
|
|
|
if ( mod_insert ( bb_xstrdup ( argv [optind] ), argc - optind - 1, argv + optind + 1 ))
|
|
bb_error_msg_and_die ( "failed to load module %s", argv [optind] );
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|