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