1/* modprobe.c - modprobe utility.
2 *
3 * Copyright 2012 Madhur Verma <mad.flexi@gmail.com>
4 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
5 *
6 * No Standard.
7
8USE_MODPROBE(NEWTOY(modprobe, "alrqvsDbd*", TOYFLAG_SBIN))
9
10config MODPROBE
11  bool "modprobe"
12  default n
13  help
14    usage: modprobe [-alrqvsDb] [-d DIR] MODULE [symbol=value][...]
15
16    modprobe utility - inserts modules and dependencies.
17
18    -a  Load multiple MODULEs
19    -d  Load modules from DIR, option may be used multiple times
20    -l  List (MODULE is a pattern)
21    -r  Remove MODULE (stacks) or do autoclean
22    -q  Quiet
23    -v  Verbose
24    -s  Log to syslog
25    -D  Show dependencies
26    -b  Apply blacklist to module names too
27*/
28#define FOR_modprobe
29#include "toys.h"
30#include <sys/syscall.h>
31
32GLOBALS(
33  struct arg_list *dirs;
34
35  struct arg_list *probes;
36  struct arg_list *dbase[256];
37  char *cmdopts;
38  int nudeps;
39  uint8_t symreq;
40)
41
42/* Note: if "#define DBASE_SIZE" modified,
43 * Please update GLOBALS dbase[256] accordingly.
44 */
45#define DBASE_SIZE  256
46#define MODNAME_LEN 256
47
48// Modules flag definations
49#define MOD_ALOADED   0x0001
50#define MOD_BLACKLIST 0x0002
51#define MOD_FNDDEPMOD 0x0004
52#define MOD_NDDEPS    0x0008
53
54// Current probing modules info
55struct module_s {
56  uint32_t flags;
57  char *cmdname, *name, *depent, *opts;
58  struct arg_list *rnames, *dep;
59};
60
61// Converts path name FILE to module name.
62static char *path2mod(char *file, char *mod)
63{
64  int i;
65  char *from;
66
67  if (!file) return NULL;
68  if (!mod) mod = xmalloc(MODNAME_LEN);
69
70  from = getbasename(file);
71
72  for (i = 0; i < (MODNAME_LEN-1) && from[i] && from[i] != '.'; i++)
73    mod[i] = (from[i] == '-') ? '_' : from[i];
74  mod[i] = '\0';
75  return mod;
76}
77
78// Add options in opts from toadd.
79static char *add_opts(char *opts, char *toadd)
80{
81  if (toadd) {
82    int optlen = 0;
83
84    if (opts) optlen = strlen(opts);
85    opts = xrealloc(opts, optlen + strlen(toadd) + 2);
86    sprintf(opts + optlen, " %s", toadd);
87  }
88  return opts;
89}
90
91// Remove first element from the list and return it.
92static void *llist_popme(struct arg_list **head)
93{
94  char *data = NULL;
95  struct arg_list *temp = *head;
96
97  if (temp) {
98    data = temp->arg;
99    *head = temp->next;
100    free(temp);
101  }
102  return data;
103}
104
105// Add new node at the beginning of the list.
106static void llist_add(struct arg_list **old, void *data)
107{
108  struct arg_list *new = xmalloc(sizeof(struct arg_list));
109
110  new->arg = (char*)data;
111  new->next = *old;
112  *old = new;
113}
114
115// Add new node at tail of list.
116static void llist_add_tail(struct arg_list **head, void *data)
117{
118  while (*head) head = &(*head)->next;
119  *head = xzalloc(sizeof(struct arg_list));
120  (*head)->arg = (char*)data;
121}
122
123// Reverse list order.
124static struct arg_list *llist_rev(struct arg_list *list)
125{
126  struct arg_list *rev = NULL;
127
128  while (list) {
129    struct arg_list *next = list->next;
130
131    list->next = rev;
132    rev = list;
133    list = next;
134  }
135  return rev;
136}
137
138/*
139 * Returns struct module_s from the data base if found, NULL otherwise.
140 * if add - create module entry, add it to data base and return the same mod.
141 */
142static struct module_s *get_mod(char *mod, uint8_t add)
143{
144  char name[MODNAME_LEN];
145  struct module_s *modentry;
146  struct arg_list *temp;
147  unsigned i, hash = 0;
148
149  path2mod(mod, name);
150  for (i = 0; name[i]; i++) hash = ((hash*31) + hash) + name[i];
151  hash %= DBASE_SIZE;
152  for (temp = TT.dbase[hash]; temp; temp = temp->next) {
153    modentry = (struct module_s *) temp->arg;
154    if (!strcmp(modentry->name, name)) return modentry;
155  }
156  if (!add) return NULL;
157  modentry = xzalloc(sizeof(*modentry));
158  modentry->name = xstrdup(name);
159  llist_add(&TT.dbase[hash], modentry);
160  return modentry;
161}
162
163/*
164 * Read a line from file with \ continuation and escape commented line.
165 * Return the line in allocated string (*li)
166 */
167static int read_line(FILE *fl, char **li)
168{
169  char *nxtline = NULL, *line;
170  ssize_t len, nxtlen;
171  size_t linelen, nxtlinelen;
172
173  while (1) {
174    line = NULL;
175    linelen = nxtlinelen = 0;
176    len = getline(&line, &linelen, fl);
177    if (len <= 0) {
178      free(line);
179      return len;
180    }
181    // checking for commented lines.
182    if (line[0] != '#') break;
183    free(line);
184  }
185  for (;;) {
186    if (line[len - 1] == '\n') len--;
187    if (!len) {
188      free(line);
189      return len;
190    } else if (line[len - 1] != '\\') break;
191
192    len--;
193    nxtlen = getline(&nxtline, &nxtlinelen, fl);
194    if (nxtlen <= 0) break;
195    if (linelen < len + nxtlen + 1) {
196      linelen = len + nxtlen + 1;
197      line = xrealloc(line, linelen);
198    }
199    memcpy(&line[len], nxtline, nxtlen);
200    len += nxtlen;
201  }
202  line[len] = '\0';
203  *li = xstrdup(line);
204  free(line);
205  if (nxtline) free(nxtline);
206  return len;
207}
208
209/*
210 * Action to be taken on all config files in default directories
211 * checks for aliases, options, install, remove and blacklist
212 */
213static int config_action(struct dirtree *node)
214{
215  FILE *fc;
216  char *filename, *tokens[3], *line, *linecp;
217  struct module_s *modent;
218  int tcount = 0;
219
220  if (!dirtree_notdotdot(node)) return 0;
221  if (S_ISDIR(node->st.st_mode)) return DIRTREE_RECURSE;
222
223  if (!S_ISREG(node->st.st_mode)) return 0; // process only regular file
224  filename = dirtree_path(node, NULL);
225  if (!(fc = fopen(filename, "r"))) {
226    free(filename);
227    return 0;
228  }
229  for (line = linecp = NULL; read_line(fc, &line) > 0;
230      free(line), free(linecp), line = linecp = NULL) {
231    char *tk = NULL;
232
233    if (!strlen(line)) continue;
234    linecp = xstrdup(line);
235    for (tk = strtok(linecp, "# \t"), tcount = 0; tk;
236        tk = strtok(NULL, "# \t"), tcount++) {
237      tokens[tcount] = tk;
238      if (tcount == 2) {
239        tokens[2] = line + strlen(tokens[0]) + strlen(tokens[1]) + 2;
240        break;
241      }
242    }
243    if (!tk) continue;
244    // process the tokens[0] contains first word of config line.
245    if (!strcmp(tokens[0], "alias")) {
246      struct arg_list *temp;
247      char aliase[MODNAME_LEN], *realname;
248
249      if (!tokens[2]) continue;
250      path2mod(tokens[1], aliase);
251      for (temp = TT.probes; temp; temp = temp->next) {
252        modent = (struct module_s *) temp->arg;
253        if (fnmatch(aliase, modent->name, 0)) continue;
254        realname = path2mod(tokens[2], NULL);
255        llist_add(&modent->rnames, realname);
256        if (modent->flags & MOD_NDDEPS) {
257          modent->flags &= ~MOD_NDDEPS;
258          TT.nudeps--;
259        }
260        modent = get_mod(realname, 1);
261        if (!(modent->flags & MOD_NDDEPS)) {
262          modent->flags |= MOD_NDDEPS;
263          TT.nudeps++;
264        }
265      }
266    } else if (!strcmp(tokens[0], "options")) {
267      if (!tokens[2]) continue;
268      modent = get_mod(tokens[1], 1);
269      modent->opts = add_opts(modent->opts, tokens[2]);
270    } else if (!strcmp(tokens[0], "include"))
271      dirtree_read(tokens[1], config_action);
272    else if (!strcmp(tokens[0], "blacklist"))
273      get_mod(tokens[1], 1)->flags |= MOD_BLACKLIST;
274    else if (!strcmp(tokens[0], "install")) continue;
275    else if (!strcmp(tokens[0], "remove")) continue;
276    else if (toys.optflags & FLAG_q)
277      error_msg("Invalid option %s found in file %s", tokens[0], filename);
278  }
279  fclose(fc);
280  free(filename);
281  return 0;
282}
283
284// Show matched modules else return -1 on failure.
285static int depmode_read_entry(char *cmdname)
286{
287  char *line;
288  int ret = -1;
289  FILE *fe = xfopen("modules.dep", "r");
290
291  while (read_line(fe, &line) > 0) {
292    char *tmp = strchr(line, ':');
293
294    if (tmp) {
295      *tmp = '\0';
296     char *name = basename(line);
297
298      tmp = strchr(name, '.');
299      if (tmp) *tmp = '\0';
300      if (!cmdname || !fnmatch(cmdname, name, 0)) {
301        if (tmp) *tmp = '.';
302        if (toys.optflags&FLAG_v) puts(line);
303        ret = 0;
304      }
305    }
306    free(line);
307  }
308  fclose(fe);
309  return ret;
310}
311
312// Finds dependencies for modules from the modules.dep file.
313static void find_dep(void)
314{
315  char *line = NULL;
316  struct module_s *mod;
317  FILE *fe = xfopen("modules.dep", "r");
318
319  for (; read_line(fe, &line) > 0; free(line)) {
320    char *tmp = strchr(line, ':');
321
322    if (tmp) {
323      *tmp = '\0';
324      mod = get_mod(line, 0);
325      if (!mod) continue;
326      if ((mod->flags & MOD_ALOADED) &&
327          !(toys.optflags & (FLAG_r | FLAG_D))) continue;
328
329      mod->flags |= MOD_FNDDEPMOD;
330      if ((mod->flags & MOD_NDDEPS) && (!mod->dep)) {
331        TT.nudeps--;
332        llist_add(&mod->dep, xstrdup(line));
333        tmp++;
334        if (*tmp) {
335          char *tok;
336
337          while ((tok = strsep(&tmp, " \t"))) {
338            if (!*tok) continue;
339            llist_add_tail(&mod->dep, xstrdup(tok));
340          }
341        }
342      }
343    }
344  }
345  fclose(fe);
346}
347
348// Remove a module from the Linux Kernel. if !modules does auto remove.
349static int rm_mod(char *modules, uint32_t flags)
350{
351  if (modules) {
352    int len = strlen(modules);
353
354    if (len > 3 && !strcmp(modules+len-3, ".ko" )) modules[len-3] = 0;
355  }
356
357  errno = 0;
358  syscall(__NR_delete_module, modules, flags ? flags : O_NONBLOCK|O_EXCL);
359
360  return errno;
361}
362
363// Insert module same as insmod implementation.
364static int ins_mod(char *modules, char *flags)
365{
366  char *buf = NULL;
367  int len, res;
368  int fd = xopenro(modules);
369
370  while (flags && strlen(toybuf) + strlen(flags) + 2 < sizeof(toybuf)) {
371    strcat(toybuf, flags);
372    strcat(toybuf, " ");
373  }
374
375#ifdef __NR_finit_module
376  res = syscall(__NR_finit_module, fd, toybuf, 0);
377  if (!res || errno != ENOSYS) {
378	  xclose(fd);
379	  return res;
380  }
381#endif
382
383  // TODO xreadfile()
384
385  len = fdlength(fd);
386  buf = xmalloc(len);
387  xreadall(fd, buf, len);
388  xclose(fd);
389
390  res = syscall(__NR_init_module, buf, len, toybuf);
391  if (CFG_TOYBOX_FREE && buf != toybuf) free(buf);
392  return res;
393}
394
395// Add module in probes list, if not loaded.
396static void add_mod(char *name)
397{
398  struct module_s *mod = get_mod(name, 1);
399
400  if (!(toys.optflags & (FLAG_r | FLAG_D)) && (mod->flags & MOD_ALOADED)) {
401    if (toys.optflags&FLAG_v) printf("skipping %s, already loaded\n", name);
402    return;
403  }
404  if (toys.optflags&FLAG_v) printf("queuing %s\n", name);
405  mod->cmdname = name;
406  mod->flags |= MOD_NDDEPS;
407  llist_add_tail(&TT.probes, mod);
408  TT.nudeps++;
409  if (!strncmp(mod->name, "symbol:", 7)) TT.symreq = 1;
410}
411
412// Parse cmdline options suplied for module.
413static char *add_cmdopt(char **argv)
414{
415  char *opt = xzalloc(1);
416  int lopt = 0;
417
418  while (*++argv) {
419    char *fmt, *var, *val;
420
421    var = *argv;
422    opt = xrealloc(opt, lopt + 2 + strlen(var) + 2);
423    // check for key=val or key = val.
424    fmt = "%.*s%s ";
425    for (val = var; *val && *val != '='; val++);
426    if (*val && strchr(++val, ' ')) fmt = "%.*s\"%s\" ";
427    lopt += sprintf(opt + lopt, fmt, (int) (val - var), var, val);
428  }
429  return opt;
430}
431
432// Probes a single module and loads all its dependencies.
433static int go_probe(struct module_s *m)
434{
435  int rc = 0, first = 1;
436
437  if (!(m->flags & MOD_FNDDEPMOD)) {
438    if (!(toys.optflags & FLAG_q))
439      error_msg("module %s not found in modules.dep", m->name);
440    return -ENOENT;
441  }
442  if (toys.optflags & FLAG_v) printf("go_prob'ing %s\n", m->name);
443  if (!(toys.optflags & FLAG_r)) m->dep = llist_rev(m->dep);
444
445  while (m->dep) {
446    struct module_s *m2;
447    char *fn, *options;
448
449    rc = 0;
450    fn = llist_popme(&m->dep);
451    m2 = get_mod(fn, 1);
452    // are we removing ?
453    if (toys.optflags & FLAG_r) {
454      if (m2->flags & MOD_ALOADED) {
455        if ((rc = rm_mod(m2->name, O_EXCL))) {
456          if (first) {
457            perror_msg("can't unload module %s", m2->name);
458            break;
459          }
460        } else m2->flags &= ~MOD_ALOADED;
461      }
462      first = 0;
463      continue;
464    }
465    options = m2->opts;
466    m2->opts = NULL;
467    if (m == m2) options = add_opts(options, TT.cmdopts);
468
469    // are we only checking dependencies ?
470    if (toys.optflags & FLAG_D) {
471      if (toys.optflags & FLAG_v)
472        printf(options ? "insmod %s %s\n" : "insmod %s\n", fn, options);
473      if (options) free(options);
474      continue;
475    }
476    if (m2->flags & MOD_ALOADED) {
477      if (toys.optflags&FLAG_v)
478        printf("%s is already loaded, skipping\n", fn);
479      if (options) free(options);
480      continue;
481    }
482    // none of above is true insert the module.
483    rc = ins_mod(fn, options);
484    if (toys.optflags&FLAG_v)
485      printf("loaded %s '%s', rc:%d\n", fn, options, rc);
486    if (rc == EEXIST) rc = 0;
487    if (options) free(options);
488    if (rc) {
489      perror_msg("can't load module %s (%s)", m2->name, fn);
490      break;
491    }
492    m2->flags |= MOD_ALOADED;
493  }
494  return rc;
495}
496
497void modprobe_main(void)
498{
499  struct utsname uts;
500  char **argv = toys.optargs, *procline = NULL;
501  FILE *fs;
502  struct module_s *module;
503  unsigned flags = toys.optflags;
504  struct arg_list *dirs;
505
506  if ((toys.optc < 1) && (((flags & FLAG_r) && (flags & FLAG_l))
507        ||(!((flags & FLAG_r)||(flags & FLAG_l)))))
508  {
509	  help_exit("bad syntax");
510  }
511  // Check for -r flag without arg if yes then do auto remove.
512  if ((flags & FLAG_r) && !toys.optc) {
513    if (rm_mod(NULL, O_NONBLOCK | O_EXCL)) perror_exit("rmmod");
514    return;
515  }
516
517  if (!TT.dirs) {
518    uname(&uts);
519    TT.dirs = xzalloc(sizeof(struct arg_list));
520    TT.dirs->arg = xmprintf("/lib/modules/%s", uts.release);
521  }
522
523  // modules.dep processing for dependency check.
524  if (flags & FLAG_l) {
525    for (dirs = TT.dirs; dirs; dirs = dirs->next) {
526      xchdir(dirs->arg);
527      if (!depmode_read_entry(toys.optargs[0]))
528	      return;
529    }
530    error_exit("no module found.");
531  }
532
533  // Read /proc/modules to get loaded modules.
534  fs = xfopen("/proc/modules", "r");
535
536  while (read_line(fs, &procline) > 0) {
537    *(strchr(procline, ' ')) = '\0';
538    get_mod(procline, 1)->flags = MOD_ALOADED;
539    free(procline);
540    procline = NULL;
541  }
542  fclose(fs);
543  if ((flags & FLAG_a) || (flags & FLAG_r)) {
544    do {
545      add_mod(*argv++);
546    } while (*argv);
547  } else {
548    add_mod(argv[0]);
549    TT.cmdopts = add_cmdopt(argv);
550  }
551  if (!TT.probes) {
552    if (toys.optflags&FLAG_v) puts("All modules loaded");
553    return;
554  }
555  dirtree_read("/etc/modprobe.conf", config_action);
556  dirtree_read("/etc/modprobe.d", config_action);
557
558  for (dirs = TT.dirs; dirs; dirs = dirs->next) {
559    xchdir(dirs->arg);
560    if (TT.symreq) dirtree_read("modules.symbols", config_action);
561    if (TT.nudeps) dirtree_read("modules.alias", config_action);
562  }
563
564  for (dirs = TT.dirs; dirs; dirs = dirs->next) {
565	  xchdir(dirs->arg);
566	  find_dep();
567  }
568
569  while ((module = llist_popme(&TT.probes))) {
570    if (!module->rnames) {
571      if (toys.optflags&FLAG_v) puts("probing by module name");
572      /* This is not an alias. Literal names are blacklisted
573       * only if '-b' is given.
574       */
575      if (!(flags & FLAG_b) || !(module->flags & MOD_BLACKLIST))
576        go_probe(module);
577      continue;
578    }
579    do { // Probe all real names for the alias.
580      char *real = ((struct arg_list*)llist_pop(&module->rnames))->arg;
581      struct module_s *m2 = get_mod(real, 0);
582
583      if (toys.optflags&FLAG_v)
584        printf("probing alias %s by realname %s\n", module->name, real);
585      if (!m2) continue;
586      if (!(m2->flags & MOD_BLACKLIST)
587          && (!(m2->flags & MOD_ALOADED) || (flags & (FLAG_r | FLAG_D))))
588        go_probe(m2);
589      free(real);
590    } while (module->rnames);
591  }
592}
593