master xplshn/aruu / cmd / linux / modprobe.c
  1
  2#include "arg.h"
  3#include "fs.h"
  4#include "paths.h"
  5#include "util.h"
  6
  7#include <ctype.h>
  8#include <dirent.h>
  9#include <errno.h>
 10#include <fcntl.h>
 11#include <fnmatch.h>
 12#include <stdarg.h>
 13#include <stdio.h>
 14#include <stdlib.h>
 15#include <string.h>
 16#include <sys/stat.h>
 17#include <sys/syscall.h>
 18#include <sys/types.h>
 19#include <sys/utsname.h>
 20#include <unistd.h>
 21
 22#if FEATURE_MODPROBE_SYSLOG
 23#include <syslog.h>
 24#endif
 25
 26#define HASH_SIZE 256
 27#ifndef LINE_MAX
 28#define LINE_MAX 4096
 29#endif
 30
 31enum ModFlags {
 32  MOD_LOADED      = 1 << 0,
 33  MOD_BLACKLISTED = 1 << 1,
 34  MOD_QUEUED      = 1 << 2,
 35  MOD_SEEN_DEP    = 1 << 3,
 36};
 37
 38struct StrNode {
 39  char           *str;
 40  struct StrNode *next;
 41};
 42
 43struct Module {
 44  char           *name;
 45  char           *path;
 46  char           *options;
 47  struct StrNode *deps;
 48  struct StrNode *aliases;
 49  int             flags;
 50  struct Module  *next;
 51};
 52
 53static struct Module  *mod_db[HASH_SIZE];
 54static struct StrNode *probes  = NULL;
 55static struct StrNode *moddirs = NULL;
 56static char           *cmdopts = NULL;
 57
 58static int aflag = 0;
 59static int rflag = 0;
 60static int qflag = 0;
 61static int vflag = 0;
 62static int lflag = 0;
 63
 64#if FEATURE_MODPROBE_SHOW_DEPENDS
 65static int Dflag = 0;
 66#endif
 67
 68#if FEATURE_MODPROBE_BLACKLIST
 69static int bflag = 0;
 70#endif
 71
 72#if FEATURE_MODPROBE_SYSLOG
 73static int sflag = 0;
 74#endif
 75
 76/* logging wrappers: handles syslog delegation and quiet mode suppression */
 77
 78static void
 79pr_warn(const char *fmt, ...)
 80{
 81  va_list ap;
 82  int     saved_errno = errno;
 83#if FEATURE_MODPROBE_SYSLOG
 84  char buf[1024];
 85#endif
 86
 87  if (qflag)
 88    return;
 89
 90  va_start(ap, fmt);
 91#if FEATURE_MODPROBE_SYSLOG
 92  if (sflag) {
 93    vsnprintf(buf, sizeof(buf), fmt, ap);
 94    if (fmt[0] && fmt[strlen(fmt) - 1] == ':')
 95      syslog(LOG_ERR, "%s %s", buf, strerror(saved_errno));
 96    else
 97      syslog(LOG_ERR, "%s", buf);
 98    va_end(ap);
 99    return;
100  }
101#endif
102  fprintf(stderr, "%s: ", argv0);
103  vfprintf(stderr, fmt, ap);
104  if (fmt[0] && fmt[strlen(fmt) - 1] == ':')
105    fprintf(stderr, " %s\n", strerror(saved_errno));
106  else
107    fputc('\n', stderr);
108  va_end(ap);
109}
110
111static void
112pr_info(const char *fmt, ...)
113{
114  va_list ap;
115
116  if (qflag)
117    return;
118
119  va_start(ap, fmt);
120#if FEATURE_MODPROBE_SYSLOG
121  if (sflag) {
122    vsyslog(LOG_INFO, fmt, ap);
123    va_end(ap);
124    return;
125  }
126#endif
127  vprintf(fmt, ap);
128  putchar('\n');
129  va_end(ap);
130}
131
132/* string structures: singly linked lists used for directories, deps, and
133 * aliases */
134
135static void
136strlist_append(struct StrNode **list, const char *str)
137{
138  struct StrNode *n, *tail;
139
140  n      = ecalloc(1, sizeof(*n));
141  n->str = estrdup(str);
142
143  if (!*list) {
144    *list = n;
145    return;
146  }
147  for (tail = *list; tail->next; tail = tail->next)
148    ;
149  tail->next = n;
150}
151
152static char *
153append_opts(char *opts, const char *add)
154{
155  size_t olen, alen;
156  char  *newopts;
157
158  if (!add)
159    return opts;
160  if (!opts)
161    return estrdup(add);
162
163  olen    = strlen(opts);
164  alen    = strlen(add);
165  newopts = ecalloc(1, olen + alen + 2);
166  memcpy(newopts, opts, olen);
167  newopts[olen] = ' ';
168  memcpy(newopts + olen + 1, add, alen);
169
170  return newopts;
171}
172
173/* module database: hash table allows fast realname queries for aliases */
174
175static unsigned int
176hash(const char *s)
177{
178  unsigned int h = 5381;
179
180  while (*s)
181    h = ((h << 5) + h) + *s++;
182  return h % HASH_SIZE;
183}
184
185static void
186normalize_name(char *dst, const char *src)
187{
188  const char *base;
189  int         i;
190
191  base = strrchr(src, '/');
192  if (base)
193    base++;
194  else
195    base = src;
196
197  for (i = 0; i < 255 && base[i] && base[i] != '.'; i++)
198    dst[i] = (base[i] == '-') ? '_' : base[i];
199  dst[i] = '\0';
200}
201
202static struct Module *
203get_module(const char *path_or_name, int create)
204{
205  char           name[256];
206  unsigned int   h;
207  struct Module *m;
208
209  normalize_name(name, path_or_name);
210  h = hash(name);
211
212  for (m = mod_db[h]; m; m = m->next) {
213    if (strcmp(m->name, name) == 0)
214      return m;
215  }
216
217  if (!create)
218    return NULL;
219
220  m         = ecalloc(1, sizeof(*m));
221  m->name   = estrdup(name);
222  m->next   = mod_db[h];
223  mod_db[h] = m;
224
225  return m;
226}
227
228/* config parsing: recursively loads aliases and module options from dir tree */
229
230static void
231parse_config_file(const char *path)
232{
233  FILE          *fp;
234  char           line[LINE_MAX];
235  char          *p, *cmd, *arg1, *arg2;
236  struct Module *m;
237
238  if (!(fp = fopen(path, "r")))
239    return;
240
241  while (fgets(line, sizeof(line), fp)) {
242    p = strchr(line, '#');
243    if (p)
244      *p = '\0';
245
246    cmd = strtok(line, " \t\n");
247    if (!cmd)
248      continue;
249
250    arg1 = strtok(NULL, " \t\n");
251    if (!arg1)
252      continue;
253
254    if (strcmp(cmd, "alias") == 0) {
255      arg2 = strtok(NULL, " \t\n");
256      if (!arg2)
257        continue;
258      m = get_module(arg1, 1);
259      strlist_append(&m->aliases, arg2);
260    } else if (strcmp(cmd, "options") == 0) {
261      arg2 = strtok(NULL, "\n");
262      if (!arg2)
263        continue;
264      while (*arg2 == ' ' || *arg2 == '\t')
265        arg2++;
266      m          = get_module(arg1, 1);
267      m->options = append_opts(m->options, arg2);
268    }
269#if FEATURE_MODPROBE_BLACKLIST
270    else if (strcmp(cmd, "blacklist") == 0) {
271      m = get_module(arg1, 1);
272      m->flags |= MOD_BLACKLISTED;
273    }
274#endif
275  }
276  fclose(fp);
277}
278
279static void
280config_cb(int fd, const char *path, struct stat *st, void *data, struct recursor *r)
281{
282  size_t len;
283
284  (void)fd;
285  (void)data;
286  (void)r;
287
288  if (S_ISREG(st->st_mode)) {
289    len = strlen(path);
290    if (len > 5 && strcmp(path + len - 5, ".conf") == 0)
291      parse_config_file(path);
292  }
293}
294
295static void
296read_configs(void)
297{
298  struct recursor r = {.fn = config_cb, .maxdepth = 1, .follow = 'H', .flags = DIRFIRST};
299  struct stat     st;
300
301  parse_config_file("/etc/modprobe.conf");
302
303  if (stat("/etc/modprobe.d", &st) == 0 && S_ISDIR(st.st_mode))
304    recurse(AT_FDCWD, "/etc/modprobe.d", NULL, &r);
305}
306
307static void
308parse_dep_file(const char *path)
309{
310  FILE          *fp;
311  char           line[LINE_MAX];
312  char          *p, *tok;
313  struct Module *m;
314
315  if (!(fp = fopen(path, "r"))) {
316    pr_warn("fopen %s:", path);
317    return;
318  }
319
320  while (fgets(line, sizeof(line), fp)) {
321    p = strchr(line, ':');
322    if (!p)
323      continue;
324    *p = '\0';
325    p++;
326
327    m = get_module(line, 1);
328    if (!m->path)
329      m->path = estrdup(line);
330    m->flags |= MOD_SEEN_DEP;
331
332    while ((tok = strtok(p, " \t\n"))) {
333      p = NULL;
334      if (*tok)
335        strlist_append(&m->deps, tok);
336    }
337  }
338  fclose(fp);
339}
340
341static void
342mark_loaded(void)
343{
344  FILE          *fp;
345  char           line[LINE_MAX];
346  char          *p;
347  struct Module *m;
348
349  if (!(fp = fopen(ARUU_LINUX_PATH_PROC_MODULES, "r")))
350    return;
351
352  while (fgets(line, sizeof(line), fp)) {
353    p = strchr(line, ' ');
354    if (p)
355      *p = '\0';
356    else {
357      p = strchr(line, '\n');
358      if (p)
359        *p = '\0';
360    }
361    m = get_module(line, 1);
362    m->flags |= MOD_LOADED;
363  }
364  fclose(fp);
365}
366
367static void
368list_modules(const char *pattern)
369{
370  FILE *fp;
371  char  line[LINE_MAX];
372  char *p, *name;
373
374  if (!(fp = fopen("modules.dep", "r"))) {
375    pr_warn("fopen modules.dep:");
376    return;
377  }
378
379  while (fgets(line, sizeof(line), fp)) {
380    p = strchr(line, ':');
381    if (!p)
382      continue;
383    *p = '\0';
384
385    name = strrchr(line, '/');
386    name = name ? name + 1 : line;
387
388    p = strrchr(name, '.');
389    if (p)
390      *p = '\0';
391
392    if (!pattern || fnmatch(pattern, name, 0) == 0) {
393      if (p)
394        *p = '.';
395      printf("%s\n", line); /* intended output, not a log */
396    }
397  }
398  fclose(fp);
399}
400
401/* kernel interaction */
402
403static int
404load_module(const char *path, const char *opts)
405{
406  int fd, ret;
407
408  fd = open(path, O_RDONLY | O_CLOEXEC);
409  if (fd < 0) {
410    pr_warn("open %s:", path);
411    return -1;
412  }
413  ret = syscall(__NR_finit_module, fd, opts ? opts : "", 0);
414  close(fd);
415  return ret;
416}
417
418static int
419unload_module(const char *name)
420{
421  return syscall(__NR_delete_module, name, O_NONBLOCK);
422}
423
424/* modprobe actions: load and unload requested module graph */
425
426static void
427process_module(struct Module *m)
428{
429  struct StrNode *dep;
430  struct Module  *dm;
431  char           *opts;
432
433  if (!m->path) {
434    pr_warn("module %s not found in modules.dep", m->name);
435    return;
436  }
437
438  if (rflag) {
439    if (m->flags & MOD_LOADED) {
440      if (unload_module(m->name) == 0)
441        m->flags &= ~MOD_LOADED;
442      else
443        pr_warn("unload %s:", m->name);
444    }
445    return;
446  }
447
448  for (dep = m->deps; dep; dep = dep->next) {
449    dm = get_module(dep->str, 0);
450    if (dm && !(dm->flags & MOD_LOADED))
451      process_module(dm);
452  }
453
454  if (m->flags & MOD_LOADED) {
455    if (vflag)
456      pr_info("%s already loaded", m->name);
457    return;
458  }
459
460  opts = m->options;
461  if (cmdopts && probes && strcmp(probes->str, m->name) == 0)
462    opts = append_opts(opts, cmdopts);
463
464#if FEATURE_MODPROBE_SHOW_DEPENDS
465  if (Dflag) {
466    printf(opts ? "insmod %s %s\n" : "insmod %s\n", m->path, opts); /* output data */
467    if (opts != m->options)
468      free(opts);
469    return;
470  }
471#endif
472
473  if (load_module(m->path, opts) == 0) {
474    m->flags |= MOD_LOADED;
475    if (vflag)
476      pr_info("loaded %s '%s'", m->path, opts ? opts : "");
477  } else {
478    pr_warn("load %s:", m->path);
479  }
480
481  if (opts != m->options)
482    free(opts);
483}
484
485static void
486do_probe(const char *name)
487{
488  struct Module  *m, *am;
489  struct StrNode *alias;
490  char            norm[256];
491
492  normalize_name(norm, name);
493  m = get_module(norm, 1);
494
495#if FEATURE_MODPROBE_BLACKLIST
496  if (bflag && (m->flags & MOD_BLACKLISTED))
497    return;
498#endif
499
500  if (!m->aliases) {
501    if (vflag)
502      pr_info("probing %s by name", norm);
503    process_module(m);
504    return;
505  }
506
507  for (alias = m->aliases; alias; alias = alias->next) {
508    am = get_module(alias->str, 1);
509#if FEATURE_MODPROBE_BLACKLIST
510    if (am->flags & MOD_BLACKLISTED)
511      continue;
512#endif
513    if (vflag)
514      pr_info("probing alias %s -> %s", norm, alias->str);
515    process_module(am);
516  }
517}
518
519static void
520usage(void)
521{
522  eprintf(
523      "usage: %s [-alqrv"
524#if FEATURE_MODPROBE_SHOW_DEPENDS
525      "D"
526#endif
527#if FEATURE_MODPROBE_BLACKLIST
528      "b"
529#endif
530#if FEATURE_MODPROBE_SYSLOG
531      "s"
532#endif
533      "] "
534#if FEATURE_MODPROBE_DIR_OVERRIDE
535      "[-d dir] "
536#endif
537      "module [symbol=value ...]\n",
538      argv0
539  );
540}
541
542// ?man modprobe: add or remove modules from the Linux kernel
543// ?man arguments: module [symbol=value ...]
544// ?man modprobe loads or removes kernel modules from the running system.
545// ?man It reads modules.dep, modules.alias, and modules.symbols from the
546// appropriate ?man /lib/modules/release directory to resolve module names and
547// dependencies, ?man loading prerequisites first. ?man Without -r, modprobe
548// loads the named module (and any required dependencies) ?man into the kernel.
549int
550main(int argc, char *argv[])
551{
552  struct utsname  uts;
553  struct StrNode *pn;
554  char            path[PATH_MAX];
555  int             i, ret = 0;
556
557  ARGBEGIN
558  {
559    // ?man -a: specify a option
560    case 'a':
561      // ?man -a: Load all modules named on the command line (rather
562      // than stopping after the first).
563      aflag = 1;
564      break;
565    // ?man -r: specify r option
566    case 'r':
567      // ?man -r: Remove the named modules from the kernel.
568      // Dependencies are not automatically removed.
569      rflag = 1;
570      break;
571    // ?man -q: specify q option
572    case 'q':
573      // ?man -q: Quiet mode.  Suppress error messages.
574      qflag = 1;
575      break;
576    // ?man -v: specify v option
577    case 'v':
578      // ?man -v: Verbose mode.  Print each action taken.
579      vflag = 1;
580      break;
581    // ?man -l: specify l option
582    case 'l':
583      // ?man -l: List available modules matching the optional
584      // _pattern_ (a fnmatch(3) glob).
585      lflag = 1;
586      break;
587#if FEATURE_MODPROBE_SHOW_DEPENDS
588    // ?man -D: specify D option
589    case 'D':
590      // ?man -D: Print the sequence of insmod commands that would be
591      // used to load the module, without actually loading anything.
592      Dflag = 1;
593      break;
594#endif
595#if FEATURE_MODPROBE_BLACKLIST
596    // ?man -b: specify b option
597    case 'b':
598      // ?man -b: Skip modules listed as blacklist in
599      // /etc/modprobe.d/.
600      bflag = 1;
601      break;
602#endif
603#if FEATURE_MODPROBE_SYSLOG
604    // ?man -s: specify s option
605    case 's':
606      // ?man -s: Log messages to syslog(3) (facility LOG_DAEMON)
607      // instead of standard error.
608      sflag = 1;
609      break;
610#endif
611#if FEATURE_MODPROBE_DIR_OVERRIDE
612    // ?man -d:file: specify d option
613    case 'd':
614      // ?man -d dir: Use dir as the base directory for module files
615      // instead of /lib/modules/release.
616      strlist_append(&moddirs, EARGF(usage()));
617      break;
618#endif
619    default:
620      usage();
621  }
622  ARGEND
623
624  if (!argc && !rflag && !lflag)
625    usage();
626
627#if FEATURE_MODPROBE_SYSLOG
628  if (sflag)
629    openlog("modprobe", LOG_PID, LOG_DAEMON);
630#endif
631
632  if (!moddirs) {
633    if (uname(&uts) < 0)
634      eprintf("uname:");
635    snprintf(path, sizeof(path), "/lib/modules/%s", uts.release);
636    strlist_append(&moddirs, path);
637  }
638
639  mark_loaded();
640  read_configs();
641
642  /* traverse all provided or default base directories for symbol, alias,
643   * and dependency tracking */
644  for (pn = moddirs; pn; pn = pn->next) {
645    if (chdir(pn->str) < 0) {
646      pr_warn("chdir %s:", pn->str);
647      continue;
648    }
649
650    if (lflag) {
651      list_modules(argc ? argv[0] : NULL);
652      continue;
653    }
654
655    parse_config_file("modules.symbols");
656    parse_config_file("modules.alias");
657    parse_dep_file("modules.dep");
658  }
659
660  if (lflag)
661    goto end;
662
663  if (aflag || rflag) {
664    for (i = 0; i < argc; i++)
665      strlist_append(&probes, argv[i]);
666  } else if (argc > 0) {
667    strlist_append(&probes, argv[0]);
668    for (i = 1; i < argc; i++)
669      cmdopts = append_opts(cmdopts, argv[i]);
670  }
671
672  if (rflag && !argc) {
673    if (syscall(__NR_delete_module, NULL, O_NONBLOCK) < 0)
674      eprintf("delete_module:");
675    goto end;
676  }
677
678  for (pn = probes; pn; pn = pn->next) {
679    do_probe(pn->str);
680  }
681
682end:
683#if FEATURE_MODPROBE_SYSLOG
684  if (sflag)
685    closelog();
686#endif
687  if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
688    ret = 2;
689
690  // ?man
691  // ?man ## FILES
692  // ?man
693  // ?man `/etc/modprobe.conf`
694  // ?man : Global configuration file.
695  // ?man
696  // ?man Files under `/etc/modprobe.d/`
697  // ?man : Per-module configuration snippets.
698  // ?man
699  // ?man `/lib/modules/`_release_`/modules.dep`
700  // ?man : Module dependency map generated by `depmod(8)`.
701  // ?man
702  // ?man ## EXIT STATUS
703  // ?man
704  // ?man 0
705  // ?man : Success.
706  // ?man
707  // ?man 1
708  // ?man : Module not found or kernel rejected the operation.
709  // ?man
710  // ?man 2
711  // ?man : I/O error on stdout or stdin.
712  // ?man
713  // ?man ## SEE ALSO
714  // ?man
715  // ?man insmod(8), rmmod(8), lsmod(8), depmod(8)
716  // ?man
717
718  return ret;
719}