changeset 1245:7b3c1238380a

Modprobe from Madhur Verma and Kyungwan Han.
author Rob Landley <rob@landley.net>
date Wed, 09 Apr 2014 07:55:08 -0500
parents 40ed517e0cda
children 04c69f3c7c9e
files toys/pending/modprobe.c
diffstat 1 files changed, 575 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toys/pending/modprobe.c	Wed Apr 09 07:55:08 2014 -0500
@@ -0,0 +1,575 @@
+/* modprobe.c - modprobe utility.
+ *
+ * Copyright 2012 Madhur Verma <mad.flexi@gmail.com>
+ * Copyright 2013 Kyungwan Han <asura321@gmail.com>
+ *
+ * No Standard.
+
+USE_MODPROBE(NEWTOY(modprobe, "alrqvsDb", TOYFLAG_SBIN))
+
+config MODPROBE
+  bool "modprobe"
+  default n
+  help
+    usage: modprobe [-alrqvsDb] MODULE [symbol=value][...]
+
+    modprobe utility - inserts modules and dependencies.
+
+    -a  Load multiple MODULEs
+    -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 <sys/syscall.h>
+#include <fnmatch.h>
+
+GLOBALS(
+  struct arg_list *probes;
+  struct arg_list *dbase[256];
+  char *cmdopts;
+  int nudeps;
+  uint8_t 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
+
+static void (*dbg)(char *format, ...);
+// dummy interface for debugging.
+static void dummy(char *format, ...)
+{
+}
+
+// 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, *lslash;
+
+	if (!file) return NULL;
+	if (!mod) mod = xmalloc(MODNAME_LEN);
+	
+  lslash = strrchr(file, '/');
+  if (!lslash || (lslash == file && !lslash[1])) from = file;
+  else from = lslash + 1;
+  
+  for (i = 0; i < (MODNAME_LEN-1) && from[i] && from[i] != '.'; i++)
+		mod[i] = (from[i] == '-') ? '_' : from[i];
+	mod[i] = '\0';
+	return mod;
+}
+
+// locate character in string.
+static char *strchr_nul(char *s, int c)
+{
+  while(*s != '\0' && *s != c) s++;
+  return (char*)s;
+}
+
+// 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 ps - create module entry, add it to data base and return the same mod.
+ */
+static struct module_s *get_mod(char *mod, uint8_t ps)
+{
+  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 (!ps) 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;
+  int len, nxtlen, linelen, nxtlinelen;
+
+  while (1) {
+    line = NULL;
+    linelen = nxtlinelen = 0;
+    len = getline(&line, (size_t*)&linelen, fl);
+    if (len <= 0) return len;
+    // checking for commented lines.
+    if (line[0] != '#') break;
+    free(line);
+  }
+  for (;;) {
+    if (line[len - 1] == '\n') len--;
+    // checking line continuation.
+    if (!len || line[len - 1] != '\\') break;
+    len--;
+    nxtlen = getline(&nxtline, (size_t*)&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);
+  if (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 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) {
+        if (tmp) *tmp = '.';
+        xprintf("%s\n", line);
+        ret = 0;
+      } else if (!fnmatch(cmdname, name, 0)) {
+        if (tmp) *tmp = '.';
+        xprintf("%s\n", line);
+        ret = 0;
+      }
+    }
+    free(line);
+  }
+  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) &&
+          !(toys.optflags & (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, uint32_t flags)
+{
+  errno = 0;
+  if (modules) {
+    int len = strlen(modules);
+
+    if (len > 3 && !strcmp(&modules[len-3], ".ko" )) modules[len-3] = 0;
+  }
+  if (!flags) flags = O_NONBLOCK|O_EXCL;
+  syscall(__NR_delete_module, modules, flags);
+  return errno;
+}
+
+// Insert module same as insmod implementation.
+static int ins_mod(char *modules, char *flags)
+{
+  char *buf = NULL;
+  int len, res;
+  int fd = xopen(modules, O_RDONLY);
+
+  len = fdlength(fd);
+  buf = xmalloc(len);
+  xreadall(fd, buf, len);
+  xclose(fd);
+
+  while (flags && strlen(toybuf) + strlen(flags) + 2 < sizeof(toybuf)) {
+    strcat(toybuf, flags);
+    strcat(toybuf, " ");
+  }
+  res = syscall(__NR_init_module, buf, len, toybuf);
+  if (CFG_TOYBOX_FREE && buf != toybuf) free(buf);
+  if (res) perror_exit("failed to load %s ", toys.optargs[0]);
+  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 (!(toys.optflags & (FLAG_r | FLAG_D)) && (mod->flags & MOD_ALOADED)) {
+    dbg("skipping %s, it is already loaded\n", name);
+    return;
+  }
+  dbg("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 ";
+    val = strchr_nul(var, '=');
+    if (*val) {
+      val++;
+      if (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 (!(toys.optflags & FLAG_s))
+      error_msg("module %s not found in modules.dep", m->name);
+    return -ENOENT;
+  }
+  dbg("go_prob'ing %s\n", m->name);
+  if (!(toys.optflags & 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 (toys.optflags & 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;
+    }
+    options = m2->opts;
+    m2->opts = NULL;
+    if (m == m2) options = add_opts(options, TT.cmdopts);
+
+    // are we only checking dependencies ?
+    if (toys.optflags & FLAG_D) {
+      dbg(options ? "insmod %s %s\n" : "insmod %s\n", fn, options);
+      if (options) free(options);
+      continue;
+    }
+    if (m2->flags & MOD_ALOADED) {
+      dbg("%s is already loaded, skipping\n", fn);
+      if (options) free(options);
+      continue;
+    }
+    // none of above is true insert the module.
+    rc = ins_mod(fn, options);
+    dbg("loaded %s '%s', rc:%d\n", fn, options, rc);
+    if (rc == EEXIST) rc = 0;
+    if (options) 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)
+{
+  struct utsname uts;
+  char **argv = toys.optargs, *procline = NULL;
+  FILE *fs;
+  struct module_s *module;
+  unsigned flags = toys.optflags;
+
+  dbg = dummy;
+  if (flags & FLAG_v) dbg = xprintf;
+
+  if ((toys.optc < 1) && (((flags & FLAG_r) && (flags & FLAG_l))
+        ||(!((flags & FLAG_r)||(flags & FLAG_l))))) {
+	  toys.exithelp++;
+	  error_exit(" Syntex Error.");
+  }
+  // Check for -r flag without arg if yes then do auto remove.
+  if ((flags & FLAG_r) && (!toys.optc)) {
+    if (rm_mod(NULL, O_NONBLOCK | O_EXCL) != 0)	perror_exit("rmmod");
+    return;
+  }
+
+  // change directory to /lib/modules/<release>/ 
+  xchdir("/lib/modules");
+  uname(&uts);
+  xchdir(uts.release);
+
+  // modules.dep processing for dependency check.
+  if (flags & FLAG_l) {
+    if (depmode_read_entry(toys.optargs[0])) error_exit("no module found.");
+    return;
+  }
+  // Read /proc/modules to get loadded 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 ((flags & FLAG_a) || (flags & FLAG_r)) {
+    do {
+      add_mod(*argv++);
+    } while (*argv);
+  } else {
+    add_mod(argv[0]);
+    TT.cmdopts = add_cmdopt(argv);
+  }
+  if (!TT.probes) {
+    fprintf(stderr, "All modules loaded successfully. \n");
+    return;
+  }
+  dirtree_read("/etc/modprobe.conf", config_action);
+  dirtree_read("/etc/modprobe.d", config_action);
+  if (TT.symreq) dirtree_read("modules.symbols", config_action);
+  if (TT.nudeps) dirtree_read("modules.alias", config_action);
+  find_dep();
+  while ((module = llist_popme(&TT.probes))) {
+    if (!module->rnames) {
+      dbg("probing by module name\n");
+      /* This is not an alias. Literal names are blacklisted
+       * only if '-b' is given.
+       */
+      if (!(flags & FLAG_b) || !(module->flags & MOD_BLACKLIST))
+        go_probe(module);
+      continue;
+    }
+    do { // Probe all real names for the alias.
+      char *real = llist_pop(&module->rnames);
+      struct module_s *m2 = get_mod(real, 0);
+      
+      dbg("probing alias %s by realname %s\n", module->name, real);
+      if (!m2) continue;
+      if (!(m2->flags & MOD_BLACKLIST) 
+          && (!(m2->flags & MOD_ALOADED) || (flags & (FLAG_r | FLAG_D))))
+        go_probe(m2);
+      free(real);
+    } while (module->rnames);
+  }
+}