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