/* modprobe.c - modprobe utility. * * Copyright 2012 Madhur Verma * Copyright 2013 Kyungwan Han * * No Standard. USE_MODPROBE(NEWTOY(modprobe, "alrqvsDbd*", TOYFLAG_SBIN)) config MODPROBE bool "modprobe" default n help usage: modprobe [-alrqvsDb] [-d DIR] MODULE [symbol=value][...] modprobe utility - inserts modules and dependencies. -a Load multiple MODULEs -d Load modules from DIR, option may be used multiple times -l List (MODULE is a pattern) -r Remove MODULE (stacks) or do autoclean -q Quiet -v Verbose -s Log to syslog -D Show dependencies -b Apply blacklist to module names too */ #define FOR_modprobe #include "toys.h" #include GLOBALS( struct arg_list *dirs; struct arg_list *probes, *dbase[256]; char *cmdopts; int nudeps, symreq; ) /* Note: if "#define DBASE_SIZE" modified, * Please update GLOBALS dbase[256] accordingly. */ #define DBASE_SIZE 256 #define MODNAME_LEN 256 // Modules flag definations #define MOD_ALOADED 0x0001 #define MOD_BLACKLIST 0x0002 #define MOD_FNDDEPMOD 0x0004 #define MOD_NDDEPS 0x0008 // Current probing modules info struct module_s { uint32_t flags; char *cmdname, *name, *depent, *opts; struct arg_list *rnames, *dep; }; // Converts path name FILE to module name. static char *path2mod(char *file, char *mod) { int i; char *from; if (!file) return NULL; if (!mod) mod = xmalloc(MODNAME_LEN); from = getbasename(file); for (i = 0; i < (MODNAME_LEN-1) && from[i] && from[i] != '.'; i++) mod[i] = (from[i] == '-') ? '_' : from[i]; mod[i] = '\0'; return mod; } // Add options in opts from toadd. static char *add_opts(char *opts, char *toadd) { if (toadd) { int optlen = 0; if (opts) optlen = strlen(opts); opts = xrealloc(opts, optlen + strlen(toadd) + 2); sprintf(opts + optlen, " %s", toadd); } return opts; } // Remove first element from the list and return it. static void *llist_popme(struct arg_list **head) { char *data = NULL; struct arg_list *temp = *head; if (temp) { data = temp->arg; *head = temp->next; free(temp); } return data; } // Add new node at the beginning of the list. static void llist_add(struct arg_list **old, void *data) { struct arg_list *new = xmalloc(sizeof(struct arg_list)); new->arg = (char*)data; new->next = *old; *old = new; } // Add new node at tail of list. static void llist_add_tail(struct arg_list **head, void *data) { while (*head) head = &(*head)->next; *head = xzalloc(sizeof(struct arg_list)); (*head)->arg = (char*)data; } // Reverse list order. static struct arg_list *llist_rev(struct arg_list *list) { struct arg_list *rev = NULL; while (list) { struct arg_list *next = list->next; list->next = rev; rev = list; list = next; } return rev; } /* * Returns struct module_s from the data base if found, NULL otherwise. * if add - create module entry, add it to data base and return the same mod. */ static struct module_s *get_mod(char *mod, uint8_t add) { char name[MODNAME_LEN]; struct module_s *modentry; struct arg_list *temp; unsigned i, hash = 0; path2mod(mod, name); for (i = 0; name[i]; i++) hash = ((hash*31) + hash) + name[i]; hash %= DBASE_SIZE; for (temp = TT.dbase[hash]; temp; temp = temp->next) { modentry = (struct module_s *) temp->arg; if (!strcmp(modentry->name, name)) return modentry; } if (!add) return NULL; modentry = xzalloc(sizeof(*modentry)); modentry->name = xstrdup(name); llist_add(&TT.dbase[hash], modentry); return modentry; } /* * Read a line from file with \ continuation and escape commented line. * Return the line in allocated string (*li) */ static int read_line(FILE *fl, char **li) { char *nxtline = NULL, *line; ssize_t len, nxtlen; size_t linelen, nxtlinelen; for (;;) { line = NULL; linelen = nxtlinelen = 0; len = getline(&line, &linelen, fl); if (len <= 0) { free(line); return len; } // checking for commented lines. if (line[0] != '#') break; free(line); } for (;;) { if (line[len - 1] == '\n') len--; if (!len) { free(line); return len; } else if (line[len - 1] != '\\') break; len--; nxtlen = getline(&nxtline, &nxtlinelen, fl); if (nxtlen <= 0) break; if (linelen < len + nxtlen + 1) { linelen = len + nxtlen + 1; line = xrealloc(line, linelen); } memcpy(&line[len], nxtline, nxtlen); len += nxtlen; } line[len] = '\0'; *li = xstrdup(line); free(line); if (nxtline) free(nxtline); return len; } /* * Action to be taken on all config files in default directories * checks for aliases, options, install, remove and blacklist */ static int config_action(struct dirtree *node) { FILE *fc; char *filename, *tokens[3], *line, *linecp; struct module_s *modent; int tcount = 0; if (!dirtree_notdotdot(node)) return 0; if (S_ISDIR(node->st.st_mode)) return DIRTREE_RECURSE; if (!S_ISREG(node->st.st_mode)) return 0; // process only regular file filename = dirtree_path(node, NULL); if (!(fc = fopen(filename, "r"))) { free(filename); return 0; } for (line = linecp = NULL; read_line(fc, &line) >= 0; free(line), free(linecp), line = linecp = NULL) { char *tk = NULL; if (!strlen(line)) continue; linecp = xstrdup(line); for (tk = strtok(linecp, "# \t"), tcount = 0; tk; tk = strtok(NULL, "# \t"), tcount++) { tokens[tcount] = tk; if (tcount == 2) { tokens[2] = line + strlen(tokens[0]) + strlen(tokens[1]) + 2; break; } } if (!tk) continue; // process the tokens[0] contains first word of config line. if (!strcmp(tokens[0], "alias")) { struct arg_list *temp; char aliase[MODNAME_LEN], *realname; if (!tokens[2]) continue; path2mod(tokens[1], aliase); for (temp = TT.probes; temp; temp = temp->next) { modent = (struct module_s *) temp->arg; if (fnmatch(aliase, modent->name, 0)) continue; realname = path2mod(tokens[2], NULL); llist_add(&modent->rnames, realname); if (modent->flags & MOD_NDDEPS) { modent->flags &= ~MOD_NDDEPS; TT.nudeps--; } modent = get_mod(realname, 1); if (!(modent->flags & MOD_NDDEPS)) { modent->flags |= MOD_NDDEPS; TT.nudeps++; } } } else if (!strcmp(tokens[0], "options")) { if (!tokens[2]) continue; modent = get_mod(tokens[1], 1); modent->opts = add_opts(modent->opts, tokens[2]); } else if (!strcmp(tokens[0], "include")) dirtree_read(tokens[1], config_action); else if (!strcmp(tokens[0], "blacklist")) get_mod(tokens[1], 1)->flags |= MOD_BLACKLIST; else if (!strcmp(tokens[0], "install")) continue; else if (!strcmp(tokens[0], "remove")) continue; else if (!FLAG(q)) error_msg("Invalid option %s found in file %s", tokens[0], filename); } fclose(fc); free(filename); return 0; } // Show matched modules else return -1 on failure. static int depmode_read_entry(char *cmdname) { char *line; int ret = -1; FILE *fe = xfopen("modules.dep", "r"); while (read_line(fe, &line) >= 0) { char *tmp = strchr(line, ':'); if (tmp) { *tmp = '\0'; char *name = basename(line); tmp = strchr(name, '.'); if (tmp) *tmp = '\0'; if (!cmdname || !fnmatch(cmdname, name, 0)) { if (tmp) *tmp = '.'; if (FLAG(v)) puts(line); ret = 0; } } free(line); } fclose(fe); return ret; } // Finds dependencies for modules from the modules.dep file. static void find_dep(void) { char *line = NULL; struct module_s *mod; FILE *fe = xfopen("modules.dep", "r"); for (; read_line(fe, &line) >= 0; free(line)) { char *tmp = strchr(line, ':'); if (tmp) { *tmp = '\0'; mod = get_mod(line, 0); if (!mod) continue; if ((mod->flags & MOD_ALOADED) && !(FLAG(r)|FLAG(D))) continue; mod->flags |= MOD_FNDDEPMOD; if ((mod->flags & MOD_NDDEPS) && !mod->dep) { TT.nudeps--; llist_add(&mod->dep, xstrdup(line)); tmp++; if (*tmp) { char *tok; while ((tok = strsep(&tmp, " \t"))) { if (!*tok) continue; llist_add_tail(&mod->dep, xstrdup(tok)); } } } } } fclose(fe); } // Remove a module from the Linux Kernel. if !modules does auto remove. static int rm_mod(char *modules, unsigned flags) { char *s; if (modules && (s = strend(modules, ".ko"))) *s = 0; errno = 0; syscall(__NR_delete_module, modules, flags ? : O_NONBLOCK|O_EXCL); return errno; } // Insert module same as insmod implementation. static int ins_mod(char *modules, char *flags) { char *buf = NULL; int len, res; int fd = xopenro(modules); while (flags && strlen(toybuf) + strlen(flags) + 2 < sizeof(toybuf)) { strcat(toybuf, flags); strcat(toybuf, " "); } #ifdef __NR_finit_module res = syscall(__NR_finit_module, fd, toybuf, 0); if (!res || errno != ENOSYS) { xclose(fd); return res; } #endif // TODO xreadfile() len = fdlength(fd); buf = xmalloc(len); xreadall(fd, buf, len); xclose(fd); res = syscall(__NR_init_module, buf, len, toybuf); if (CFG_TOYBOX_FREE && buf != toybuf) free(buf); return res; } // Add module in probes list, if not loaded. static void add_mod(char *name) { struct module_s *mod = get_mod(name, 1); if (!(FLAG(r)|FLAG(D)) && (mod->flags & MOD_ALOADED)) { if (FLAG(v)) printf("%s already loaded\n", name); return; } if (FLAG(v)) printf("queuing %s\n", name); mod->cmdname = name; mod->flags |= MOD_NDDEPS; llist_add_tail(&TT.probes, mod); TT.nudeps++; if (!strncmp(mod->name, "symbol:", 7)) TT.symreq = 1; } // Parse cmdline options suplied for module. static char *add_cmdopt(char **argv) { char *opt = xzalloc(1); int lopt = 0; while (*++argv) { char *fmt, *var, *val; var = *argv; opt = xrealloc(opt, lopt + 2 + strlen(var) + 2); // check for key=val or key = val. fmt = "%.*s%s "; for (val = var; *val && *val != '='; val++); if (*val && strchr(++val, ' ')) fmt = "%.*s\"%s\" "; lopt += sprintf(opt + lopt, fmt, (int) (val - var), var, val); } return opt; } // Probes a single module and loads all its dependencies. static int go_probe(struct module_s *m) { int rc = 0, first = 1; if (!(m->flags & MOD_FNDDEPMOD)) { if (!FLAG(q)) error_msg("module %s not found in modules.dep", m->name); return -ENOENT; } if (FLAG(v)) printf("go_prob'ing %s\n", m->name); if (!FLAG(r)) m->dep = llist_rev(m->dep); while (m->dep) { struct module_s *m2; char *fn, *options; rc = 0; fn = llist_popme(&m->dep); m2 = get_mod(fn, 1); // are we removing ? if (FLAG(r)) { if (m2->flags & MOD_ALOADED) { if ((rc = rm_mod(m2->name, O_EXCL))) { if (first) { perror_msg("can't unload module %s", m2->name); break; } } else m2->flags &= ~MOD_ALOADED; } first = 0; continue; } // TODO how does free work here without leaking? options = m2->opts; m2->opts = NULL; if (m == m2) options = add_opts(options, TT.cmdopts); // are we only checking dependencies ? if (FLAG(D)) { if (FLAG(v)) printf(options ? "insmod %s %s\n" : "insmod %s\n", fn, options); if (options) free(options); continue; } if (m2->flags & MOD_ALOADED) { if (FLAG(v)) printf("%s already loaded\n", fn); if (options) free(options); continue; } // none of above is true insert the module. errno = 0; rc = ins_mod(fn, options); if (FLAG(v)) printf("loaded %s '%s': %s\n", fn, options, strerror(errno)); if (errno == EEXIST) rc = 0; free(options); if (rc) { perror_msg("can't load module %s (%s)", m2->name, fn); break; } m2->flags |= MOD_ALOADED; } return rc; } void modprobe_main(void) { char **argv = toys.optargs, *procline = NULL; FILE *fs; struct module_s *module; struct arg_list *dirs; if (toys.optc<1 && !FLAG(r) == !FLAG(l)) help_exit("bad syntax"); // Check for -r flag without arg if yes then do auto remove. if (FLAG(r) && !toys.optc) { if (rm_mod(0, O_NONBLOCK|O_EXCL)) perror_exit("rmmod"); return; } if (!TT.dirs) { struct utsname uts; uname(&uts); TT.dirs = xzalloc(sizeof(struct arg_list)); TT.dirs->arg = xmprintf("/lib/modules/%s", uts.release); } // modules.dep processing for dependency check. if (FLAG(l)) { for (dirs = TT.dirs; dirs; dirs = dirs->next) { xchdir(dirs->arg); if (!depmode_read_entry(*toys.optargs)) return; } error_exit("no module found."); } // Read /proc/modules to get loaded modules. fs = xfopen("/proc/modules", "r"); while (read_line(fs, &procline) > 0) { *strchr(procline, ' ') = 0; get_mod(procline, 1)->flags = MOD_ALOADED; free(procline); procline = NULL; } fclose(fs); if (FLAG(a) || FLAG(r)) while (argv) add_mod(*argv++); else { add_mod(*argv); TT.cmdopts = add_cmdopt(argv); } if (!TT.probes) { if (FLAG(v)) puts("All modules loaded"); return; } dirtree_flagread("/etc/modprobe.conf", DIRTREE_SHUTUP, config_action); dirtree_flagread("/etc/modprobe.d", DIRTREE_SHUTUP, config_action); for (dirs = TT.dirs; dirs; dirs = dirs->next) { xchdir(dirs->arg); if (TT.symreq) dirtree_read("modules.symbols", config_action); if (TT.nudeps) dirtree_read("modules.alias", config_action); } for (dirs = TT.dirs; dirs; dirs = dirs->next) { xchdir(dirs->arg); find_dep(); } while ((module = llist_popme(&TT.probes))) { if (!module->rnames) { if (FLAG(v)) puts("probing by module name"); /* This is not an alias. Literal names are blacklisted * only if '-b' is given. */ if (!FLAG(b) || !(module->flags & MOD_BLACKLIST)) go_probe(module); continue; } do { // Probe all real names for the alias. char *real = ((struct arg_list *)llist_pop(&module->rnames))->arg; struct module_s *m2 = get_mod(real, 0); if (FLAG(v)) printf("probing alias %s by realname %s\n", module->name, real); if (!m2) continue; if (!(m2->flags & MOD_BLACKLIST) && (!(m2->flags & MOD_ALOADED) || FLAG(r) || FLAG(D))) go_probe(m2); free(real); } while (module->rnames); } }