source: MondoRescue/branches/3.3/mindi-busybox/util-linux/mdev.c@ 3625

Last change on this file since 3625 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 31.1 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * mdev - Mini udev for busybox
4 *
5 * Copyright 2005 Rob Landley <rob@landley.net>
6 * Copyright 2005 Frank Sorenson <frank@tuxrocks.com>
7 *
8 * Licensed under GPLv2, see file LICENSE in this source tree.
9 */
10
11//config:config MDEV
12//config: bool "mdev"
13//config: default y
14//config: select PLATFORM_LINUX
15//config: help
16//config: mdev is a mini-udev implementation for dynamically creating device
17//config: nodes in the /dev directory.
18//config:
19//config: For more information, please see docs/mdev.txt
20//config:
21//config:config FEATURE_MDEV_CONF
22//config: bool "Support /etc/mdev.conf"
23//config: default y
24//config: depends on MDEV
25//config: help
26//config: Add support for the mdev config file to control ownership and
27//config: permissions of the device nodes.
28//config:
29//config: For more information, please see docs/mdev.txt
30//config:
31//config:config FEATURE_MDEV_RENAME
32//config: bool "Support subdirs/symlinks"
33//config: default y
34//config: depends on FEATURE_MDEV_CONF
35//config: help
36//config: Add support for renaming devices and creating symlinks.
37//config:
38//config: For more information, please see docs/mdev.txt
39//config:
40//config:config FEATURE_MDEV_RENAME_REGEXP
41//config: bool "Support regular expressions substitutions when renaming device"
42//config: default y
43//config: depends on FEATURE_MDEV_RENAME
44//config: help
45//config: Add support for regular expressions substitutions when renaming
46//config: device.
47//config:
48//config:config FEATURE_MDEV_EXEC
49//config: bool "Support command execution at device addition/removal"
50//config: default y
51//config: depends on FEATURE_MDEV_CONF
52//config: help
53//config: This adds support for an optional field to /etc/mdev.conf for
54//config: executing commands when devices are created/removed.
55//config:
56//config: For more information, please see docs/mdev.txt
57//config:
58//config:config FEATURE_MDEV_LOAD_FIRMWARE
59//config: bool "Support loading of firmwares"
60//config: default y
61//config: depends on MDEV
62//config: help
63//config: Some devices need to load firmware before they can be usable.
64//config:
65//config: These devices will request userspace look up the files in
66//config: /lib/firmware/ and if it exists, send it to the kernel for
67//config: loading into the hardware.
68
69//applet:IF_MDEV(APPLET(mdev, BB_DIR_SBIN, BB_SUID_DROP))
70
71//kbuild:lib-$(CONFIG_MDEV) += mdev.o
72
73//usage:#define mdev_trivial_usage
74//usage: "[-s]"
75//usage:#define mdev_full_usage "\n\n"
76//usage: "mdev -s is to be run during boot to scan /sys and populate /dev.\n"
77//usage: "\n"
78//usage: "Bare mdev is a kernel hotplug helper. To activate it:\n"
79//usage: " echo /sbin/mdev >/proc/sys/kernel/hotplug\n"
80//usage: IF_FEATURE_MDEV_CONF(
81//usage: "\n"
82//usage: "It uses /etc/mdev.conf with lines\n"
83//usage: " [-][ENV=regex;]...DEVNAME UID:GID PERM"
84//usage: IF_FEATURE_MDEV_RENAME(" [>|=PATH]|[!]")
85//usage: IF_FEATURE_MDEV_EXEC(" [@|$|*PROG]")
86//usage: "\n"
87//usage: "where DEVNAME is device name regex, @major,minor[-minor2], or\n"
88//usage: "environment variable regex. A common use of the latter is\n"
89//usage: "to load modules for hotplugged devices:\n"
90//usage: " $MODALIAS=.* 0:0 660 @modprobe \"$MODALIAS\"\n"
91//usage: )
92//usage: "\n"
93//usage: "If /dev/mdev.seq file exists, mdev will wait for its value\n"
94//usage: "to match $SEQNUM variable. This prevents plug/unplug races.\n"
95//usage: "To activate this feature, create empty /dev/mdev.seq at boot.\n"
96//usage: "\n"
97//usage: "If /dev/mdev.log file exists, debug log will be appended to it."
98
99#include "libbb.h"
100#include "common_bufsiz.h"
101#include "xregex.h"
102
103/* "mdev -s" scans /sys/class/xxx, looking for directories which have dev
104 * file (it is of the form "M:m\n"). Example: /sys/class/tty/tty0/dev
105 * contains "4:0\n". Directory name is taken as device name, path component
106 * directly after /sys/class/ as subsystem. In this example, "tty0" and "tty".
107 * Then mdev creates the /dev/device_name node.
108 * If /sys/class/.../dev file does not exist, mdev still may act
109 * on this device: see "@|$|*command args..." parameter in config file.
110 *
111 * mdev w/o parameters is called as hotplug helper. It takes device
112 * and subsystem names from $DEVPATH and $SUBSYSTEM, extracts
113 * maj,min from "/sys/$DEVPATH/dev" and also examines
114 * $ACTION ("add"/"delete") and $FIRMWARE.
115 *
116 * If action is "add", mdev creates /dev/device_name similarly to mdev -s.
117 * (todo: explain "delete" and $FIRMWARE)
118 *
119 * If /etc/mdev.conf exists, it may modify /dev/device_name's properties.
120 *
121 * Leading minus in 1st field means "don't stop on this line", otherwise
122 * search is stopped after the matching line is encountered.
123 *
124 * $envvar=regex format is useful for loading modules for hot-plugged devices
125 * which do not have driver loaded yet. In this case /sys/class/.../dev
126 * does not exist, but $MODALIAS is set to needed module's name
127 * (actually, an alias to it) by kernel. This rule instructs mdev
128 * to load the module and exit:
129 * $MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"
130 * The kernel will generate another hotplug event when /sys/class/.../dev
131 * file appears.
132 *
133 * When line matches, the device node is created, chmod'ed and chown'ed,
134 * moved to path, and if >path, a symlink to moved node is created,
135 * all this if /sys/class/.../dev exists.
136 * Examples:
137 * =loop/ - moves to /dev/loop
138 * >disk/sda%1 - moves to /dev/disk/sdaN, makes /dev/sdaN a symlink
139 *
140 * Then "command args..." is executed (via sh -c 'command args...').
141 * @:execute on creation, $:on deletion, *:on both.
142 * This happens regardless of /sys/class/.../dev existence.
143 */
144
145/* Kernel's hotplug environment constantly changes.
146 * Here are new cases I observed on 3.1.0:
147 *
148 * Case with $DEVNAME and $DEVICE, not just $DEVPATH:
149 * ACTION=add
150 * BUSNUM=001
151 * DEVICE=/proc/bus/usb/001/003
152 * DEVNAME=bus/usb/001/003
153 * DEVNUM=003
154 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5
155 * DEVTYPE=usb_device
156 * MAJOR=189
157 * MINOR=2
158 * PRODUCT=18d1/4e12/227
159 * SUBSYSTEM=usb
160 * TYPE=0/0/0
161 *
162 * Case with $DEVICE, but no $DEVNAME - apparenty, usb iface notification?
163 * "Please load me a module" thing?
164 * ACTION=add
165 * DEVICE=/proc/bus/usb/001/003
166 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0
167 * DEVTYPE=usb_interface
168 * INTERFACE=8/6/80
169 * MODALIAS=usb:v18D1p4E12d0227dc00dsc00dp00ic08isc06ip50
170 * PRODUCT=18d1/4e12/227
171 * SUBSYSTEM=usb
172 * TYPE=0/0/0
173 *
174 * ACTION=add
175 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0/host5
176 * DEVTYPE=scsi_host
177 * SUBSYSTEM=scsi
178 *
179 * ACTION=add
180 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0/host5/scsi_host/host5
181 * SUBSYSTEM=scsi_host
182 *
183 * ACTION=add
184 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0/host5/target5:0:0
185 * DEVTYPE=scsi_target
186 * SUBSYSTEM=scsi
187 *
188 * Case with strange $MODALIAS:
189 * ACTION=add
190 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0/host5/target5:0:0/5:0:0:0
191 * DEVTYPE=scsi_device
192 * MODALIAS=scsi:t-0x00
193 * SUBSYSTEM=scsi
194 *
195 * ACTION=add
196 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0/host5/target5:0:0/5:0:0:0/scsi_disk/5:0:0:0
197 * SUBSYSTEM=scsi_disk
198 *
199 * ACTION=add
200 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0/host5/target5:0:0/5:0:0:0/scsi_device/5:0:0:0
201 * SUBSYSTEM=scsi_device
202 *
203 * Case with explicit $MAJOR/$MINOR (no need to read /sys/$DEVPATH/dev?):
204 * ACTION=add
205 * DEVNAME=bsg/5:0:0:0
206 * DEVPATH=/devices/pci0000:00/0000:00:02.1/usb1/1-5/1-5:1.0/host5/target5:0:0/5:0:0:0/bsg/5:0:0:0
207 * MAJOR=253
208 * MINOR=1
209 * SUBSYSTEM=bsg
210 *
211 * ACTION=add
212 * DEVPATH=/devices/virtual/bdi/8:16
213 * SUBSYSTEM=bdi
214 *
215 * ACTION=add
216 * DEVNAME=sdb
217 * DEVPATH=/block/sdb
218 * DEVTYPE=disk
219 * MAJOR=8
220 * MINOR=16
221 * SUBSYSTEM=block
222 *
223 * Case with ACTION=change:
224 * ACTION=change
225 * DEVNAME=sdb
226 * DEVPATH=/block/sdb
227 * DEVTYPE=disk
228 * DISK_MEDIA_CHANGE=1
229 * MAJOR=8
230 * MINOR=16
231 * SUBSYSTEM=block
232 */
233
234#define DEBUG_LVL 2
235
236#if DEBUG_LVL >= 1
237# define dbg1(...) do { if (G.verbose) bb_error_msg(__VA_ARGS__); } while(0)
238#else
239# define dbg1(...) ((void)0)
240#endif
241#if DEBUG_LVL >= 2
242# define dbg2(...) do { if (G.verbose >= 2) bb_error_msg(__VA_ARGS__); } while(0)
243#else
244# define dbg2(...) ((void)0)
245#endif
246#if DEBUG_LVL >= 3
247# define dbg3(...) do { if (G.verbose >= 3) bb_error_msg(__VA_ARGS__); } while(0)
248#else
249# define dbg3(...) ((void)0)
250#endif
251
252
253static const char keywords[] ALIGN1 = "add\0remove\0"; // "change\0"
254enum { OP_add, OP_remove };
255
256struct envmatch {
257 struct envmatch *next;
258 char *envname;
259 regex_t match;
260};
261
262struct rule {
263 bool keep_matching;
264 bool regex_compiled;
265 mode_t mode;
266 int maj, min0, min1;
267 struct bb_uidgid_t ugid;
268 char *envvar;
269 char *ren_mov;
270 IF_FEATURE_MDEV_EXEC(char *r_cmd;)
271 regex_t match;
272 struct envmatch *envmatch;
273};
274
275struct globals {
276 int root_major, root_minor;
277 smallint verbose;
278 char *subsystem;
279 char *subsys_env; /* for putenv("SUBSYSTEM=subsystem") */
280#if ENABLE_FEATURE_MDEV_CONF
281 const char *filename;
282 parser_t *parser;
283 struct rule **rule_vec;
284 unsigned rule_idx;
285#endif
286 struct rule cur_rule;
287 char timestr[sizeof("HH:MM:SS.123456")];
288} FIX_ALIASING;
289#define G (*(struct globals*)bb_common_bufsiz1)
290#define INIT_G() do { \
291 setup_common_bufsiz(); \
292 IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.maj = -1;) \
293 IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.mode = 0660;) \
294} while (0)
295
296
297/* Prevent infinite loops in /sys symlinks */
298#define MAX_SYSFS_DEPTH 3
299
300/* We use additional bytes in make_device() */
301#define SCRATCH_SIZE 128
302
303#if ENABLE_FEATURE_MDEV_CONF
304
305static void make_default_cur_rule(void)
306{
307 memset(&G.cur_rule, 0, sizeof(G.cur_rule));
308 G.cur_rule.maj = -1; /* "not a @major,minor rule" */
309 G.cur_rule.mode = 0660;
310}
311
312static void clean_up_cur_rule(void)
313{
314 struct envmatch *e;
315
316 free(G.cur_rule.envvar);
317 free(G.cur_rule.ren_mov);
318 if (G.cur_rule.regex_compiled)
319 regfree(&G.cur_rule.match);
320 IF_FEATURE_MDEV_EXEC(free(G.cur_rule.r_cmd);)
321 e = G.cur_rule.envmatch;
322 while (e) {
323 free(e->envname);
324 regfree(&e->match);
325 e = e->next;
326 }
327 make_default_cur_rule();
328}
329
330static char *parse_envmatch_pfx(char *val)
331{
332 struct envmatch **nextp = &G.cur_rule.envmatch;
333
334 for (;;) {
335 struct envmatch *e;
336 char *semicolon;
337 char *eq = strchr(val, '=');
338 if (!eq /* || eq == val? */)
339 return val;
340 if (endofname(val) != eq)
341 return val;
342 semicolon = strchr(eq, ';');
343 if (!semicolon)
344 return val;
345 /* ENVVAR=regex;... */
346 *nextp = e = xzalloc(sizeof(*e));
347 nextp = &e->next;
348 e->envname = xstrndup(val, eq - val);
349 *semicolon = '\0';
350 xregcomp(&e->match, eq + 1, REG_EXTENDED);
351 *semicolon = ';';
352 val = semicolon + 1;
353 }
354}
355
356static void parse_next_rule(void)
357{
358 /* Note: on entry, G.cur_rule is set to default */
359 while (1) {
360 char *tokens[4];
361 char *val;
362
363 /* No PARSE_EOL_COMMENTS, because command may contain '#' chars */
364 if (!config_read(G.parser, tokens, 4, 3, "# \t", PARSE_NORMAL & ~PARSE_EOL_COMMENTS))
365 break;
366
367 /* Fields: [-]regex uid:gid mode [alias] [cmd] */
368 dbg3("token1:'%s'", tokens[1]);
369
370 /* 1st field */
371 val = tokens[0];
372 G.cur_rule.keep_matching = ('-' == val[0]);
373 val += G.cur_rule.keep_matching; /* swallow leading dash */
374 val = parse_envmatch_pfx(val);
375 if (val[0] == '@') {
376 /* @major,minor[-minor2] */
377 /* (useful when name is ambiguous:
378 * "/sys/class/usb/lp0" and
379 * "/sys/class/printer/lp0")
380 */
381 int sc = sscanf(val, "@%u,%u-%u", &G.cur_rule.maj, &G.cur_rule.min0, &G.cur_rule.min1);
382 if (sc < 2 || G.cur_rule.maj < 0) {
383 bb_error_msg("bad @maj,min on line %d", G.parser->lineno);
384 goto next_rule;
385 }
386 if (sc == 2)
387 G.cur_rule.min1 = G.cur_rule.min0;
388 } else {
389 char *eq = strchr(val, '=');
390 if (val[0] == '$') {
391 /* $ENVVAR=regex ... */
392 val++;
393 if (!eq) {
394 bb_error_msg("bad $envvar=regex on line %d", G.parser->lineno);
395 goto next_rule;
396 }
397 G.cur_rule.envvar = xstrndup(val, eq - val);
398 val = eq + 1;
399 }
400 xregcomp(&G.cur_rule.match, val, REG_EXTENDED);
401 G.cur_rule.regex_compiled = 1;
402 }
403
404 /* 2nd field: uid:gid - device ownership */
405 if (get_uidgid(&G.cur_rule.ugid, tokens[1]) == 0) {
406 bb_error_msg("unknown user/group '%s' on line %d", tokens[1], G.parser->lineno);
407 goto next_rule;
408 }
409
410 /* 3rd field: mode - device permissions */
411 G.cur_rule.mode = bb_parse_mode(tokens[2], G.cur_rule.mode);
412
413 /* 4th field (opt): ">|=alias" or "!" to not create the node */
414 val = tokens[3];
415 if (ENABLE_FEATURE_MDEV_RENAME && val && strchr(">=!", val[0])) {
416 char *s = skip_non_whitespace(val);
417 G.cur_rule.ren_mov = xstrndup(val, s - val);
418 val = skip_whitespace(s);
419 }
420
421 if (ENABLE_FEATURE_MDEV_EXEC && val && val[0]) {
422 const char *s = "$@*";
423 const char *s2 = strchr(s, val[0]);
424 if (!s2) {
425 bb_error_msg("bad line %u", G.parser->lineno);
426 goto next_rule;
427 }
428 IF_FEATURE_MDEV_EXEC(G.cur_rule.r_cmd = xstrdup(val);)
429 }
430
431 return;
432 next_rule:
433 clean_up_cur_rule();
434 } /* while (config_read) */
435
436 dbg3("config_close(G.parser)");
437 config_close(G.parser);
438 G.parser = NULL;
439
440 return;
441}
442
443/* If mdev -s, we remember rules in G.rule_vec[].
444 * Otherwise, there is no point in doing it, and we just
445 * save only one parsed rule in G.cur_rule.
446 */
447static const struct rule *next_rule(void)
448{
449 struct rule *rule;
450
451 /* Open conf file if we didn't do it yet */
452 if (!G.parser && G.filename) {
453 dbg3("config_open('%s')", G.filename);
454 G.parser = config_open2(G.filename, fopen_for_read);
455 G.filename = NULL;
456 }
457
458 if (G.rule_vec) {
459 /* mdev -s */
460 /* Do we have rule parsed already? */
461 if (G.rule_vec[G.rule_idx]) {
462 dbg3("< G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
463 return G.rule_vec[G.rule_idx++];
464 }
465 make_default_cur_rule();
466 } else {
467 /* not mdev -s */
468 clean_up_cur_rule();
469 }
470
471 /* Parse one more rule if file isn't fully read */
472 rule = &G.cur_rule;
473 if (G.parser) {
474 parse_next_rule();
475 if (G.rule_vec) { /* mdev -s */
476 rule = xmemdup(&G.cur_rule, sizeof(G.cur_rule));
477 G.rule_vec = xrealloc_vector(G.rule_vec, 4, G.rule_idx);
478 G.rule_vec[G.rule_idx++] = rule;
479 dbg3("> G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
480 }
481 }
482
483 return rule;
484}
485
486static int env_matches(struct envmatch *e)
487{
488 while (e) {
489 int r;
490 char *val = getenv(e->envname);
491 if (!val)
492 return 0;
493 r = regexec(&e->match, val, /*size*/ 0, /*range[]*/ NULL, /*eflags*/ 0);
494 if (r != 0) /* no match */
495 return 0;
496 e = e->next;
497 }
498 return 1;
499}
500
501#else
502
503# define next_rule() (&G.cur_rule)
504
505#endif
506
507static void mkdir_recursive(char *name)
508{
509 /* if name has many levels ("dir1/dir2"),
510 * bb_make_directory() will create dir1 according to umask,
511 * not according to its "mode" parameter.
512 * Since we run with umask=0, need to temporarily switch it.
513 */
514 umask(022); /* "dir1" (if any) will be 0755 too */
515 bb_make_directory(name, 0755, FILEUTILS_RECUR);
516 umask(0);
517}
518
519/* Builds an alias path.
520 * This function potentionally reallocates the alias parameter.
521 * Only used for ENABLE_FEATURE_MDEV_RENAME
522 */
523static char *build_alias(char *alias, const char *device_name)
524{
525 char *dest;
526
527 /* ">bar/": rename to bar/device_name */
528 /* ">bar[/]baz": rename to bar[/]baz */
529 dest = strrchr(alias, '/');
530 if (dest) { /* ">bar/[baz]" ? */
531 *dest = '\0'; /* mkdir bar */
532 mkdir_recursive(alias);
533 *dest = '/';
534 if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */
535 dest = alias;
536 alias = concat_path_file(alias, device_name);
537 free(dest);
538 }
539 }
540
541 return alias;
542}
543
544/* mknod in /dev based on a path like "/sys/block/hda/hda1"
545 * NB1: path parameter needs to have SCRATCH_SIZE scratch bytes
546 * after NUL, but we promise to not mangle (IOW: to restore NUL if needed)
547 * path string.
548 * NB2: "mdev -s" may call us many times, do not leak memory/fds!
549 *
550 * device_name = $DEVNAME (may be NULL)
551 * path = /sys/$DEVPATH
552 */
553static void make_device(char *device_name, char *path, int operation)
554{
555 int major, minor, type, len;
556 char *path_end = path + strlen(path);
557
558 /* Try to read major/minor string. Note that the kernel puts \n after
559 * the data, so we don't need to worry about null terminating the string
560 * because sscanf() will stop at the first nondigit, which \n is.
561 * We also depend on path having writeable space after it.
562 */
563 major = -1;
564 if (operation == OP_add) {
565 strcpy(path_end, "/dev");
566 len = open_read_close(path, path_end + 1, SCRATCH_SIZE - 1);
567 *path_end = '\0';
568 if (len < 1) {
569 if (!ENABLE_FEATURE_MDEV_EXEC)
570 return;
571 /* no "dev" file, but we can still run scripts
572 * based on device name */
573 } else if (sscanf(path_end + 1, "%u:%u", &major, &minor) == 2) {
574 dbg1("dev %u,%u", major, minor);
575 } else {
576 major = -1;
577 }
578 }
579 /* else: for delete, -1 still deletes the node, but < -1 suppresses that */
580
581 /* Determine device name */
582 if (!device_name) {
583 /*
584 * There was no $DEVNAME envvar (for example, mdev -s never has).
585 * But it is very useful: it contains the *path*, not only basename,
586 * Thankfully, uevent file has it.
587 * Example of .../sound/card0/controlC0/uevent file on Linux-3.7.7:
588 * MAJOR=116
589 * MINOR=7
590 * DEVNAME=snd/controlC0
591 */
592 strcpy(path_end, "/uevent");
593 len = open_read_close(path, path_end + 1, SCRATCH_SIZE - 1);
594 if (len < 0)
595 len = 0;
596 *path_end = '\0';
597 path_end[1 + len] = '\0';
598 device_name = strstr(path_end + 1, "\nDEVNAME=");
599 if (device_name) {
600 device_name += sizeof("\nDEVNAME=")-1;
601 strchrnul(device_name, '\n')[0] = '\0';
602 } else {
603 /* Fall back to just basename */
604 device_name = (char*) bb_basename(path);
605 }
606 }
607 /* Determine device type */
608 /*
609 * http://kernel.org/doc/pending/hotplug.txt says that only
610 * "/sys/block/..." is for block devices. "/sys/bus" etc is not.
611 * But since 2.6.25 block devices are also in /sys/class/block.
612 * We use strstr("/block/") to forestall future surprises.
613 */
614 type = S_IFCHR;
615 if (strstr(path, "/block/") || (G.subsystem && is_prefixed_with(G.subsystem, "block")))
616 type = S_IFBLK;
617
618#if ENABLE_FEATURE_MDEV_CONF
619 G.rule_idx = 0; /* restart from the beginning (think mdev -s) */
620#endif
621 for (;;) {
622 const char *str_to_match;
623 regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP];
624 char *command;
625 char *alias;
626 char aliaslink = aliaslink; /* for compiler */
627 char *node_name;
628 const struct rule *rule;
629
630 str_to_match = device_name;
631
632 rule = next_rule();
633
634#if ENABLE_FEATURE_MDEV_CONF
635 if (!env_matches(rule->envmatch))
636 continue;
637 if (rule->maj >= 0) { /* @maj,min rule */
638 if (major != rule->maj)
639 continue;
640 if (minor < rule->min0 || minor > rule->min1)
641 continue;
642 memset(off, 0, sizeof(off));
643 goto rule_matches;
644 }
645 if (rule->envvar) { /* $envvar=regex rule */
646 str_to_match = getenv(rule->envvar);
647 dbg3("getenv('%s'):'%s'", rule->envvar, str_to_match);
648 if (!str_to_match)
649 continue;
650 }
651 /* else: str_to_match = device_name */
652
653 if (rule->regex_compiled) {
654 int regex_match = regexec(&rule->match, str_to_match, ARRAY_SIZE(off), off, 0);
655 dbg3("regex_match for '%s':%d", str_to_match, regex_match);
656 //bb_error_msg("matches:");
657 //for (int i = 0; i < ARRAY_SIZE(off); i++) {
658 // if (off[i].rm_so < 0) continue;
659 // bb_error_msg("match %d: '%.*s'\n", i,
660 // (int)(off[i].rm_eo - off[i].rm_so),
661 // device_name + off[i].rm_so);
662 //}
663
664 if (regex_match != 0
665 /* regexec returns whole pattern as "range" 0 */
666 || off[0].rm_so != 0
667 || (int)off[0].rm_eo != (int)strlen(str_to_match)
668 ) {
669 continue; /* this rule doesn't match */
670 }
671 }
672 /* else: it's final implicit "match-all" rule */
673 rule_matches:
674 dbg2("rule matched, line %d", G.parser ? G.parser->lineno : -1);
675#endif
676 /* Build alias name */
677 alias = NULL;
678 if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) {
679 aliaslink = rule->ren_mov[0];
680 if (aliaslink == '!') {
681 /* "!": suppress node creation/deletion */
682 major = -2;
683 }
684 else if (aliaslink == '>' || aliaslink == '=') {
685 if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) {
686 char *s;
687 char *p;
688 unsigned n;
689
690 /* substitute %1..9 with off[1..9], if any */
691 n = 0;
692 s = rule->ren_mov;
693 while (*s)
694 if (*s++ == '%')
695 n++;
696
697 p = alias = xzalloc(strlen(rule->ren_mov) + n * strlen(str_to_match));
698 s = rule->ren_mov + 1;
699 while (*s) {
700 *p = *s;
701 if ('%' == *s) {
702 unsigned i = (s[1] - '0');
703 if (i <= 9 && off[i].rm_so >= 0) {
704 n = off[i].rm_eo - off[i].rm_so;
705 strncpy(p, str_to_match + off[i].rm_so, n);
706 p += n - 1;
707 s++;
708 }
709 }
710 p++;
711 s++;
712 }
713 } else {
714 alias = xstrdup(rule->ren_mov + 1);
715 }
716 }
717 }
718 dbg3("alias:'%s'", alias);
719
720 command = NULL;
721 IF_FEATURE_MDEV_EXEC(command = rule->r_cmd;)
722 if (command) {
723 /* Are we running this command now?
724 * Run @cmd on create, $cmd on delete, *cmd on any
725 */
726 if ((command[0] == '@' && operation == OP_add)
727 || (command[0] == '$' && operation == OP_remove)
728 || (command[0] == '*')
729 ) {
730 command++;
731 } else {
732 command = NULL;
733 }
734 }
735 dbg3("command:'%s'", command);
736
737 /* "Execute" the line we found */
738 node_name = device_name;
739 if (ENABLE_FEATURE_MDEV_RENAME && alias) {
740 node_name = alias = build_alias(alias, device_name);
741 dbg3("alias2:'%s'", alias);
742 }
743
744 if (operation == OP_add && major >= 0) {
745 char *slash = strrchr(node_name, '/');
746 if (slash) {
747 *slash = '\0';
748 mkdir_recursive(node_name);
749 *slash = '/';
750 }
751 if (ENABLE_FEATURE_MDEV_CONF) {
752 dbg1("mknod %s (%d,%d) %o"
753 " %u:%u",
754 node_name, major, minor, rule->mode | type,
755 rule->ugid.uid, rule->ugid.gid
756 );
757 } else {
758 dbg1("mknod %s (%d,%d) %o",
759 node_name, major, minor, rule->mode | type
760 );
761 }
762 if (mknod(node_name, rule->mode | type, makedev(major, minor)) && errno != EEXIST)
763 bb_perror_msg("can't create '%s'", node_name);
764 if (ENABLE_FEATURE_MDEV_CONF) {
765 chmod(node_name, rule->mode);
766 chown(node_name, rule->ugid.uid, rule->ugid.gid);
767 }
768 if (major == G.root_major && minor == G.root_minor)
769 symlink(node_name, "root");
770 if (ENABLE_FEATURE_MDEV_RENAME && alias) {
771 if (aliaslink == '>') {
772//TODO: on devtmpfs, device_name already exists and symlink() fails.
773//End result is that instead of symlink, we have two nodes.
774//What should be done?
775 dbg1("symlink: %s", device_name);
776 symlink(node_name, device_name);
777 }
778 }
779 }
780
781 if (ENABLE_FEATURE_MDEV_EXEC && command) {
782 /* setenv will leak memory, use putenv/unsetenv/free */
783 char *s = xasprintf("%s=%s", "MDEV", node_name);
784 putenv(s);
785 dbg1("running: %s", command);
786 if (system(command) == -1)
787 bb_perror_msg("can't run '%s'", command);
788 bb_unsetenv_and_free(s);
789 }
790
791 if (operation == OP_remove && major >= -1) {
792 if (ENABLE_FEATURE_MDEV_RENAME && alias) {
793 if (aliaslink == '>') {
794 dbg1("unlink: %s", device_name);
795 unlink(device_name);
796 }
797 }
798 dbg1("unlink: %s", node_name);
799 unlink(node_name);
800 }
801
802 if (ENABLE_FEATURE_MDEV_RENAME)
803 free(alias);
804
805 /* We found matching line.
806 * Stop unless it was prefixed with '-'
807 */
808 if (!ENABLE_FEATURE_MDEV_CONF || !rule->keep_matching)
809 break;
810 } /* for (;;) */
811}
812
813/* File callback for /sys/ traversal */
814static int FAST_FUNC fileAction(const char *fileName,
815 struct stat *statbuf UNUSED_PARAM,
816 void *userData,
817 int depth UNUSED_PARAM)
818{
819 size_t len = strlen(fileName) - 4; /* can't underflow */
820 char *scratch = userData;
821
822 /* len check is for paranoid reasons */
823 if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
824 return FALSE;
825
826 strcpy(scratch, fileName);
827 scratch[len] = '\0';
828 make_device(/*DEVNAME:*/ NULL, scratch, OP_add);
829
830 return TRUE;
831}
832
833/* Directory callback for /sys/ traversal */
834static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
835 struct stat *statbuf UNUSED_PARAM,
836 void *userData UNUSED_PARAM,
837 int depth)
838{
839 /* Extract device subsystem -- the name of the directory
840 * under /sys/class/ */
841 if (1 == depth) {
842 free(G.subsystem);
843 if (G.subsys_env) {
844 bb_unsetenv_and_free(G.subsys_env);
845 G.subsys_env = NULL;
846 }
847 G.subsystem = strrchr(fileName, '/');
848 if (G.subsystem) {
849 G.subsystem = xstrdup(G.subsystem + 1);
850 G.subsys_env = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
851 putenv(G.subsys_env);
852 }
853 }
854
855 return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
856}
857
858/* For the full gory details, see linux/Documentation/firmware_class/README
859 *
860 * Firmware loading works like this:
861 * - kernel sets FIRMWARE env var
862 * - userspace checks /lib/firmware/$FIRMWARE
863 * - userspace waits for /sys/$DEVPATH/loading to appear
864 * - userspace writes "1" to /sys/$DEVPATH/loading
865 * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data
866 * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading
867 * - kernel loads firmware into device
868 */
869static void load_firmware(const char *firmware, const char *sysfs_path)
870{
871 int cnt;
872 int firmware_fd, loading_fd;
873
874 /* check for /lib/firmware/$FIRMWARE */
875 xchdir("/lib/firmware");
876 firmware_fd = open(firmware, O_RDONLY); /* can fail */
877
878 /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */
879 xchdir(sysfs_path);
880 for (cnt = 0; cnt < 30; ++cnt) {
881 loading_fd = open("loading", O_WRONLY);
882 if (loading_fd >= 0)
883 goto loading;
884 sleep(1);
885 }
886 goto out;
887
888 loading:
889 cnt = 0;
890 if (firmware_fd >= 0) {
891 int data_fd;
892
893 /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */
894 if (full_write(loading_fd, "1", 1) != 1)
895 goto out;
896
897 /* load firmware into /sys/$DEVPATH/data */
898 data_fd = open("data", O_WRONLY);
899 if (data_fd < 0)
900 goto out;
901 cnt = bb_copyfd_eof(firmware_fd, data_fd);
902 if (ENABLE_FEATURE_CLEAN_UP)
903 close(data_fd);
904 }
905
906 /* Tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading"
907 * Note: we emit -1 also if firmware file wasn't found.
908 * There are cases when otherwise kernel would wait for minutes
909 * before timing out.
910 */
911 if (cnt > 0)
912 full_write(loading_fd, "0", 1);
913 else
914 full_write(loading_fd, "-1", 2);
915
916 out:
917 xchdir("/dev");
918 if (ENABLE_FEATURE_CLEAN_UP) {
919 close(firmware_fd);
920 close(loading_fd);
921 }
922}
923
924static char *curtime(void)
925{
926 struct timeval tv;
927 gettimeofday(&tv, NULL);
928 sprintf(
929 strftime_HHMMSS(G.timestr, sizeof(G.timestr), &tv.tv_sec),
930 ".%06u",
931 (unsigned)tv.tv_usec
932 );
933 return G.timestr;
934}
935
936static void open_mdev_log(const char *seq, unsigned my_pid)
937{
938 int logfd = open("mdev.log", O_WRONLY | O_APPEND);
939 if (logfd >= 0) {
940 xmove_fd(logfd, STDERR_FILENO);
941 G.verbose = 2;
942 applet_name = xasprintf("%s[%s]", applet_name, seq ? seq : utoa(my_pid));
943 }
944}
945
946/* If it exists, does /dev/mdev.seq match $SEQNUM?
947 * If it does not match, earlier mdev is running
948 * in parallel, and we need to wait.
949 * Active mdev pokes us with SIGCHLD to check the new file.
950 */
951static int
952wait_for_seqfile(unsigned expected_seq)
953{
954 /* We time out after 2 sec */
955 static const struct timespec ts = { 0, 32*1000*1000 };
956 int timeout = 2000 / 32;
957 int seq_fd = -1;
958 int do_once = 1;
959 sigset_t set_CHLD;
960
961 sigemptyset(&set_CHLD);
962 sigaddset(&set_CHLD, SIGCHLD);
963 sigprocmask(SIG_BLOCK, &set_CHLD, NULL);
964
965 for (;;) {
966 int seqlen;
967 char seqbuf[sizeof(long)*3 + 2];
968 unsigned seqbufnum;
969
970 if (seq_fd < 0) {
971 seq_fd = open("mdev.seq", O_RDWR);
972 if (seq_fd < 0)
973 break;
974 close_on_exec_on(seq_fd);
975 }
976 seqlen = pread(seq_fd, seqbuf, sizeof(seqbuf) - 1, 0);
977 if (seqlen < 0) {
978 close(seq_fd);
979 seq_fd = -1;
980 break;
981 }
982 seqbuf[seqlen] = '\0';
983 if (seqbuf[0] == '\n' || seqbuf[0] == '\0') {
984 /* seed file: write out seq ASAP */
985 xwrite_str(seq_fd, utoa(expected_seq));
986 xlseek(seq_fd, 0, SEEK_SET);
987 dbg2("first seq written");
988 break;
989 }
990 seqbufnum = atoll(seqbuf);
991 if (seqbufnum == expected_seq) {
992 /* correct idx */
993 break;
994 }
995 if (seqbufnum > expected_seq) {
996 /* a later mdev runs already (this was seen by users to happen) */
997 /* do not overwrite seqfile on exit */
998 close(seq_fd);
999 seq_fd = -1;
1000 break;
1001 }
1002 if (do_once) {
1003 dbg2("%s mdev.seq='%s', need '%u'", curtime(), seqbuf, expected_seq);
1004 do_once = 0;
1005 }
1006 if (sigtimedwait(&set_CHLD, NULL, &ts) >= 0) {
1007 dbg3("woken up");
1008 continue; /* don't decrement timeout! */
1009 }
1010 if (--timeout == 0) {
1011 dbg1("%s mdev.seq='%s'", "timed out", seqbuf);
1012 break;
1013 }
1014 }
1015 sigprocmask(SIG_UNBLOCK, &set_CHLD, NULL);
1016 return seq_fd;
1017}
1018
1019static void signal_mdevs(unsigned my_pid)
1020{
1021 procps_status_t* p = NULL;
1022 while ((p = procps_scan(p, PSSCAN_ARGV0)) != NULL) {
1023 if (p->pid != my_pid
1024 && p->argv0
1025 && strcmp(bb_basename(p->argv0), "mdev") == 0
1026 ) {
1027 kill(p->pid, SIGCHLD);
1028 }
1029 }
1030}
1031
1032int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1033int mdev_main(int argc UNUSED_PARAM, char **argv)
1034{
1035 RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE);
1036
1037 INIT_G();
1038
1039#if ENABLE_FEATURE_MDEV_CONF
1040 G.filename = "/etc/mdev.conf";
1041#endif
1042
1043 /* We can be called as hotplug helper */
1044 /* Kernel cannot provide suitable stdio fds for us, do it ourself */
1045 bb_sanitize_stdio();
1046
1047 /* Force the configuration file settings exactly */
1048 umask(0);
1049
1050 xchdir("/dev");
1051
1052 if (argv[1] && strcmp(argv[1], "-s") == 0) {
1053 /*
1054 * Scan: mdev -s
1055 */
1056 struct stat st;
1057
1058#if ENABLE_FEATURE_MDEV_CONF
1059 /* Same as xrealloc_vector(NULL, 4, 0): */
1060 G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec));
1061#endif
1062 xstat("/", &st);
1063 G.root_major = major(st.st_dev);
1064 G.root_minor = minor(st.st_dev);
1065
1066 putenv((char*)"ACTION=add");
1067
1068 /* ACTION_FOLLOWLINKS is needed since in newer kernels
1069 * /sys/block/loop* (for example) are symlinks to dirs,
1070 * not real directories.
1071 * (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs,
1072 * but we can't enforce that on users)
1073 */
1074 if (access("/sys/class/block", F_OK) != 0) {
1075 /* Scan obsolete /sys/block only if /sys/class/block
1076 * doesn't exist. Otherwise we'll have dupes.
1077 * Also, do not complain if it doesn't exist.
1078 * Some people configure kernel to have no blockdevs.
1079 */
1080 recursive_action("/sys/block",
1081 ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
1082 fileAction, dirAction, temp, 0);
1083 }
1084 recursive_action("/sys/class",
1085 ACTION_RECURSE | ACTION_FOLLOWLINKS,
1086 fileAction, dirAction, temp, 0);
1087 } else {
1088 char *fw;
1089 char *seq;
1090 char *action;
1091 char *env_devname;
1092 char *env_devpath;
1093 unsigned my_pid;
1094 unsigned seqnum = seqnum; /* for compiler */
1095 int seq_fd;
1096 smalluint op;
1097
1098 /* Hotplug:
1099 * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
1100 * ACTION can be "add", "remove", "change"
1101 * DEVPATH is like "/block/sda" or "/class/input/mice"
1102 */
1103 env_devname = getenv("DEVNAME"); /* can be NULL */
1104 G.subsystem = getenv("SUBSYSTEM");
1105 action = getenv("ACTION");
1106 env_devpath = getenv("DEVPATH");
1107 if (!action || !env_devpath /*|| !G.subsystem*/)
1108 bb_show_usage();
1109 fw = getenv("FIRMWARE");
1110 seq = getenv("SEQNUM");
1111 op = index_in_strings(keywords, action);
1112
1113 my_pid = getpid();
1114 open_mdev_log(seq, my_pid);
1115
1116 seq_fd = -1;
1117 if (seq) {
1118 seqnum = atoll(seq);
1119 seq_fd = wait_for_seqfile(seqnum);
1120 }
1121
1122 dbg1("%s "
1123 "ACTION:%s SUBSYSTEM:%s DEVNAME:%s DEVPATH:%s"
1124 "%s%s",
1125 curtime(),
1126 action, G.subsystem, env_devname, env_devpath,
1127 fw ? " FW:" : "", fw ? fw : ""
1128 );
1129
1130 snprintf(temp, PATH_MAX, "/sys%s", env_devpath);
1131 if (op == OP_remove) {
1132 /* Ignoring "remove firmware". It was reported
1133 * to happen and to cause erroneous deletion
1134 * of device nodes. */
1135 if (!fw)
1136 make_device(env_devname, temp, op);
1137 }
1138 else {
1139 make_device(env_devname, temp, op);
1140 if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
1141 if (op == OP_add && fw)
1142 load_firmware(fw, temp);
1143 }
1144 }
1145
1146 dbg1("%s exiting", curtime());
1147 if (seq_fd >= 0) {
1148 xwrite_str(seq_fd, utoa(seqnum + 1));
1149 signal_mdevs(my_pid);
1150 }
1151 }
1152
1153 if (ENABLE_FEATURE_CLEAN_UP)
1154 RELEASE_CONFIG_BUFFER(temp);
1155
1156 return EXIT_SUCCESS;
1157}
Note: See TracBrowser for help on using the repository browser.