Annotation of /tags/mkinitrd-6_2_0/util-linux/mdev.c
Parent Directory | Revision Log
Revision 994 -
(hide annotations)
(download)
Sun May 30 11:53:18 2010 UTC (14 years, 4 months ago) by niro
File MIME type: text/plain
File size: 17008 byte(s)
Sun May 30 11:53:18 2010 UTC (14 years, 4 months ago) by niro
File MIME type: text/plain
File size: 17008 byte(s)
tagged 'mkinitrd-6_2_0'
1 | niro | 532 | /* 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 GPL version 2, see file LICENSE in this tarball for details. | ||
9 | */ | ||
10 | niro | 816 | #include "libbb.h" |
11 | niro | 532 | #include "xregex.h" |
12 | |||
13 | niro | 984 | /* "mdev -s" scans /sys/class/xxx, looking for directories which have dev |
14 | * file (it is of the form "M:m\n"). Example: /sys/class/tty/tty0/dev | ||
15 | * contains "4:0\n". Directory name is taken as device name, path component | ||
16 | * directly after /sys/class/ as subsystem. In this example, "tty0" and "tty". | ||
17 | * Then mdev creates the /dev/device_name node. | ||
18 | * If /sys/class/.../dev file does not exist, mdev still may act | ||
19 | * on this device: see "@|$|*command args..." parameter in config file. | ||
20 | * | ||
21 | * mdev w/o parameters is called as hotplug helper. It takes device | ||
22 | * and subsystem names from $DEVPATH and $SUBSYSTEM, extracts | ||
23 | * maj,min from "/sys/$DEVPATH/dev" and also examines | ||
24 | * $ACTION ("add"/"delete") and $FIRMWARE. | ||
25 | * | ||
26 | * If action is "add", mdev creates /dev/device_name similarly to mdev -s. | ||
27 | * (todo: explain "delete" and $FIRMWARE) | ||
28 | * | ||
29 | * If /etc/mdev.conf exists, it may modify /dev/device_name's properties. | ||
30 | * /etc/mdev.conf file format: | ||
31 | * | ||
32 | * [-][subsystem/]device user:grp mode [>|=path] [@|$|*command args...] | ||
33 | * [-]@maj,min[-min2] user:grp mode [>|=path] [@|$|*command args...] | ||
34 | * [-]$envvar=val user:grp mode [>|=path] [@|$|*command args...] | ||
35 | * | ||
36 | * Leading minus in 1st field means "don't stop on this line", otherwise | ||
37 | * search is stopped after the matching line is encountered. | ||
38 | * | ||
39 | * The device name or "subsystem/device" combo is matched against 1st field | ||
40 | * (which is a regex), or maj,min is matched against 1st field, | ||
41 | * or specified environment variable (as regex) is matched against 1st field. | ||
42 | * | ||
43 | * $envvar=val format is useful for loading modules for hot-plugged devices | ||
44 | * which do not have driver loaded yet. In this case /sys/class/.../dev | ||
45 | * does not exist, but $MODALIAS is set to needed module's name | ||
46 | * (actually, an alias to it) by kernel. This rule instructs mdev | ||
47 | * to load the module and exit: | ||
48 | * $MODALIAS=.* 0:0 660 @modprobe "$MODALIAS" | ||
49 | * The kernel will generate another hotplug event when /sys/class/.../dev | ||
50 | * file appears. | ||
51 | * | ||
52 | * When line matches, the device node is created, chmod'ed and chown'ed, | ||
53 | * moved to path, and if >path, a symlink to moved node is created, | ||
54 | * all this if /sys/class/.../dev exists. | ||
55 | * Examples: | ||
56 | * =loop/ - moves to /dev/loop | ||
57 | * >disk/sda%1 - moves to /dev/disk/sdaN, makes /dev/sdaN a symlink | ||
58 | * | ||
59 | * Then "command args..." is executed (via sh -c 'command args...'). | ||
60 | * @:execute on creation, $:on deletion, *:on both. | ||
61 | * This happens regardless of /sys/class/.../dev existence. | ||
62 | */ | ||
63 | |||
64 | niro | 816 | struct globals { |
65 | int root_major, root_minor; | ||
66 | niro | 984 | char *subsystem; |
67 | niro | 816 | }; |
68 | #define G (*(struct globals*)&bb_common_bufsiz1) | ||
69 | #define root_major (G.root_major) | ||
70 | #define root_minor (G.root_minor) | ||
71 | niro | 984 | #define subsystem (G.subsystem ) |
72 | niro | 532 | |
73 | niro | 816 | /* Prevent infinite loops in /sys symlinks */ |
74 | #define MAX_SYSFS_DEPTH 3 | ||
75 | |||
76 | /* We use additional 64+ bytes in make_device() */ | ||
77 | #define SCRATCH_SIZE 80 | ||
78 | |||
79 | /* Builds an alias path. | ||
80 | * This function potentionally reallocates the alias parameter. | ||
81 | niro | 984 | * Only used for ENABLE_FEATURE_MDEV_RENAME |
82 | niro | 816 | */ |
83 | static char *build_alias(char *alias, const char *device_name) | ||
84 | niro | 532 | { |
85 | niro | 816 | char *dest; |
86 | niro | 532 | |
87 | niro | 816 | /* ">bar/": rename to bar/device_name */ |
88 | /* ">bar[/]baz": rename to bar[/]baz */ | ||
89 | dest = strrchr(alias, '/'); | ||
90 | if (dest) { /* ">bar/[baz]" ? */ | ||
91 | *dest = '\0'; /* mkdir bar */ | ||
92 | bb_make_directory(alias, 0755, FILEUTILS_RECUR); | ||
93 | *dest = '/'; | ||
94 | if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */ | ||
95 | dest = alias; | ||
96 | alias = concat_path_file(alias, device_name); | ||
97 | free(dest); | ||
98 | } | ||
99 | } | ||
100 | niro | 532 | |
101 | niro | 816 | return alias; |
102 | } | ||
103 | |||
104 | niro | 984 | /* mknod in /dev based on a path like "/sys/block/hda/hda1" |
105 | * NB1: path parameter needs to have SCRATCH_SIZE scratch bytes | ||
106 | * after NUL, but we promise to not mangle (IOW: to restore if needed) | ||
107 | * path string. | ||
108 | * NB2: "mdev -s" may call us many times, do not leak memory/fds! | ||
109 | */ | ||
110 | niro | 532 | static void make_device(char *path, int delete) |
111 | { | ||
112 | niro | 984 | char *device_name; |
113 | niro | 532 | int major, minor, type, len; |
114 | niro | 984 | mode_t mode; |
115 | niro | 816 | parser_t *parser; |
116 | niro | 532 | |
117 | /* Try to read major/minor string. Note that the kernel puts \n after | ||
118 | * the data, so we don't need to worry about null terminating the string | ||
119 | niro | 816 | * because sscanf() will stop at the first nondigit, which \n is. |
120 | * We also depend on path having writeable space after it. | ||
121 | */ | ||
122 | major = -1; | ||
123 | niro | 532 | if (!delete) { |
124 | niro | 984 | char *dev_maj_min = path + strlen(path); |
125 | |||
126 | niro | 816 | strcpy(dev_maj_min, "/dev"); |
127 | len = open_read_close(path, dev_maj_min + 1, 64); | ||
128 | niro | 984 | *dev_maj_min = '\0'; |
129 | niro | 816 | if (len < 1) { |
130 | if (!ENABLE_FEATURE_MDEV_EXEC) | ||
131 | return; | ||
132 | niro | 984 | /* no "dev" file, but we can still run scripts |
133 | * based on device name */ | ||
134 | } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2) { | ||
135 | niro | 816 | major = -1; |
136 | } | ||
137 | niro | 532 | } |
138 | |||
139 | /* Determine device name, type, major and minor */ | ||
140 | niro | 984 | device_name = (char*) bb_basename(path); |
141 | niro | 816 | /* http://kernel.org/doc/pending/hotplug.txt says that only |
142 | * "/sys/block/..." is for block devices. "/sys/bus" etc is not. | ||
143 | niro | 984 | * But since 2.6.25 block devices are also in /sys/class/block, |
144 | * we use strstr("/block/") to forestall future surprises. */ | ||
145 | niro | 816 | type = S_IFCHR; |
146 | if (strstr(path, "/block/")) | ||
147 | type = S_IFBLK; | ||
148 | niro | 532 | |
149 | niro | 984 | /* Make path point to "subsystem/device_name" */ |
150 | if (path[5] == 'b') /* legacy /sys/block? */ | ||
151 | path += sizeof("/sys/") - 1; | ||
152 | else | ||
153 | path += sizeof("/sys/class/") - 1; | ||
154 | niro | 532 | |
155 | niro | 816 | /* If we have config file, look up user settings */ |
156 | niro | 984 | if (ENABLE_FEATURE_MDEV_CONF) |
157 | parser = config_open2("/etc/mdev.conf", fopen_for_read); | ||
158 | niro | 532 | |
159 | niro | 984 | do { |
160 | int keep_matching; | ||
161 | struct bb_uidgid_t ugid; | ||
162 | char *tokens[4]; | ||
163 | char *command = NULL; | ||
164 | char *alias = NULL; | ||
165 | char aliaslink = aliaslink; /* for compiler */ | ||
166 | niro | 532 | |
167 | niro | 984 | /* Defaults in case we won't match any line */ |
168 | ugid.uid = ugid.gid = 0; | ||
169 | keep_matching = 0; | ||
170 | mode = 0660; | ||
171 | |||
172 | if (ENABLE_FEATURE_MDEV_CONF | ||
173 | && config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL) | ||
174 | ) { | ||
175 | char *val; | ||
176 | char *str_to_match; | ||
177 | regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP]; | ||
178 | |||
179 | val = tokens[0]; | ||
180 | keep_matching = ('-' == val[0]); | ||
181 | val += keep_matching; /* swallow leading dash */ | ||
182 | |||
183 | /* Match against either "subsystem/device_name" | ||
184 | * or "device_name" alone */ | ||
185 | str_to_match = strchr(val, '/') ? path : device_name; | ||
186 | |||
187 | /* Fields: regex uid:gid mode [alias] [cmd] */ | ||
188 | |||
189 | if (val[0] == '@') { | ||
190 | /* @major,minor[-minor2] */ | ||
191 | /* (useful when name is ambiguous: | ||
192 | * "/sys/class/usb/lp0" and | ||
193 | * "/sys/class/printer/lp0") */ | ||
194 | int cmaj, cmin0, cmin1, sc; | ||
195 | if (major < 0) | ||
196 | continue; /* no dev, no match */ | ||
197 | sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1); | ||
198 | if (sc < 1 || major != cmaj | ||
199 | || (sc == 2 && minor != cmin0) | ||
200 | || (sc == 3 && (minor < cmin0 || minor > cmin1)) | ||
201 | ) { | ||
202 | continue; /* this line doesn't match */ | ||
203 | } | ||
204 | goto line_matches; | ||
205 | niro | 816 | } |
206 | niro | 984 | if (val[0] == '$') { |
207 | /* regex to match an environment variable */ | ||
208 | char *eq = strchr(++val, '='); | ||
209 | if (!eq) | ||
210 | continue; | ||
211 | *eq = '\0'; | ||
212 | str_to_match = getenv(val); | ||
213 | if (!str_to_match) | ||
214 | continue; | ||
215 | str_to_match -= strlen(val) + 1; | ||
216 | *eq = '='; | ||
217 | } | ||
218 | /* else: regex to match [subsystem/]device_name */ | ||
219 | niro | 532 | |
220 | niro | 984 | { |
221 | regex_t match; | ||
222 | int result; | ||
223 | niro | 532 | |
224 | niro | 984 | xregcomp(&match, val, REG_EXTENDED); |
225 | result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0); | ||
226 | regfree(&match); | ||
227 | //bb_error_msg("matches:"); | ||
228 | //for (int i = 0; i < ARRAY_SIZE(off); i++) { | ||
229 | // if (off[i].rm_so < 0) continue; | ||
230 | // bb_error_msg("match %d: '%.*s'\n", i, | ||
231 | // (int)(off[i].rm_eo - off[i].rm_so), | ||
232 | // device_name + off[i].rm_so); | ||
233 | //} | ||
234 | niro | 532 | |
235 | niro | 984 | /* If no match, skip rest of line */ |
236 | /* (regexec returns whole pattern as "range" 0) */ | ||
237 | if (result || off[0].rm_so | ||
238 | || ((int)off[0].rm_eo != (int)strlen(str_to_match)) | ||
239 | ) { | ||
240 | continue; /* this line doesn't match */ | ||
241 | } | ||
242 | niro | 816 | } |
243 | niro | 984 | line_matches: |
244 | /* This line matches. Stop parsing after parsing | ||
245 | * the rest the line unless keep_matching == 1 */ | ||
246 | niro | 532 | |
247 | niro | 984 | /* 2nd field: uid:gid - device ownership */ |
248 | if (get_uidgid(&ugid, tokens[1], 1) == 0) | ||
249 | bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno); | ||
250 | niro | 532 | |
251 | niro | 984 | /* 3rd field: mode - device permissions */ |
252 | /* mode = strtoul(tokens[2], NULL, 8); */ | ||
253 | bb_parse_mode(tokens[2], &mode); | ||
254 | niro | 532 | |
255 | niro | 984 | val = tokens[3]; |
256 | /* 4th field (opt): >|=alias */ | ||
257 | niro | 532 | |
258 | niro | 984 | if (ENABLE_FEATURE_MDEV_RENAME && val) { |
259 | aliaslink = val[0]; | ||
260 | if (aliaslink == '>' || aliaslink == '=') { | ||
261 | char *a, *s, *st; | ||
262 | char *p; | ||
263 | unsigned i, n; | ||
264 | niro | 532 | |
265 | niro | 984 | a = val; |
266 | s = strchrnul(val, ' '); | ||
267 | st = strchrnul(val, '\t'); | ||
268 | if (st < s) | ||
269 | s = st; | ||
270 | val = (s[0] && s[1]) ? s+1 : NULL; | ||
271 | s[0] = '\0'; | ||
272 | |||
273 | if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) { | ||
274 | /* substitute %1..9 with off[1..9], if any */ | ||
275 | n = 0; | ||
276 | s = a; | ||
277 | while (*s) | ||
278 | if (*s++ == '%') | ||
279 | n++; | ||
280 | |||
281 | p = alias = xzalloc(strlen(a) + n * strlen(str_to_match)); | ||
282 | s = a + 1; | ||
283 | while (*s) { | ||
284 | *p = *s; | ||
285 | if ('%' == *s) { | ||
286 | i = (s[1] - '0'); | ||
287 | if (i <= 9 && off[i].rm_so >= 0) { | ||
288 | n = off[i].rm_eo - off[i].rm_so; | ||
289 | strncpy(p, str_to_match + off[i].rm_so, n); | ||
290 | p += n - 1; | ||
291 | s++; | ||
292 | } | ||
293 | } | ||
294 | p++; | ||
295 | s++; | ||
296 | } | ||
297 | } else { | ||
298 | alias = xstrdup(a + 1); | ||
299 | niro | 532 | } |
300 | } | ||
301 | niro | 816 | } |
302 | niro | 532 | |
303 | niro | 984 | if (ENABLE_FEATURE_MDEV_EXEC && val) { |
304 | const char *s = "$@*"; | ||
305 | const char *s2 = strchr(s, val[0]); | ||
306 | niro | 532 | |
307 | niro | 984 | if (!s2) { |
308 | bb_error_msg("bad line %u", parser->lineno); | ||
309 | if (ENABLE_FEATURE_MDEV_RENAME) | ||
310 | free(alias); | ||
311 | continue; | ||
312 | } | ||
313 | niro | 816 | |
314 | niro | 984 | /* Are we running this command now? |
315 | * Run $cmd on delete, @cmd on create, *cmd on both | ||
316 | */ | ||
317 | if (s2-s != delete) | ||
318 | command = xstrdup(val + 1); | ||
319 | niro | 532 | } |
320 | niro | 816 | } |
321 | niro | 532 | |
322 | niro | 984 | /* End of field parsing */ |
323 | niro | 532 | |
324 | niro | 984 | /* "Execute" the line we found */ |
325 | { | ||
326 | const char *node_name; | ||
327 | niro | 532 | |
328 | niro | 984 | node_name = device_name; |
329 | if (ENABLE_FEATURE_MDEV_RENAME && alias) | ||
330 | node_name = alias = build_alias(alias, device_name); | ||
331 | niro | 532 | |
332 | niro | 984 | if (!delete && major >= 0) { |
333 | if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST) | ||
334 | bb_perror_msg("can't create %s", node_name); | ||
335 | if (major == root_major && minor == root_minor) | ||
336 | symlink(node_name, "root"); | ||
337 | if (ENABLE_FEATURE_MDEV_CONF) { | ||
338 | chmod(node_name, mode); | ||
339 | chown(node_name, ugid.uid, ugid.gid); | ||
340 | } | ||
341 | if (ENABLE_FEATURE_MDEV_RENAME && alias) { | ||
342 | if (aliaslink == '>') | ||
343 | symlink(node_name, device_name); | ||
344 | } | ||
345 | } | ||
346 | niro | 532 | |
347 | niro | 984 | if (ENABLE_FEATURE_MDEV_EXEC && command) { |
348 | /* setenv will leak memory, use putenv/unsetenv/free */ | ||
349 | char *s = xasprintf("%s=%s", "MDEV", node_name); | ||
350 | char *s1 = xasprintf("%s=%s", "SUBSYSTEM", subsystem); | ||
351 | putenv(s); | ||
352 | putenv(s1); | ||
353 | if (system(command) == -1) | ||
354 | bb_perror_msg("can't run '%s'", command); | ||
355 | unsetenv("SUBSYSTEM"); | ||
356 | free(s1); | ||
357 | unsetenv("MDEV"); | ||
358 | free(s); | ||
359 | free(command); | ||
360 | } | ||
361 | niro | 532 | |
362 | niro | 984 | if (delete) { |
363 | if (ENABLE_FEATURE_MDEV_RENAME && alias) { | ||
364 | if (aliaslink == '>') | ||
365 | unlink(device_name); | ||
366 | } | ||
367 | unlink(node_name); | ||
368 | } | ||
369 | niro | 816 | |
370 | niro | 984 | if (ENABLE_FEATURE_MDEV_RENAME) |
371 | free(alias); | ||
372 | } | ||
373 | niro | 816 | |
374 | niro | 984 | /* We found matching line. |
375 | * Stop unless it was prefixed with '-' */ | ||
376 | if (ENABLE_FEATURE_MDEV_CONF && !keep_matching) | ||
377 | break; | ||
378 | niro | 816 | |
379 | niro | 984 | /* end of "while line is read from /etc/mdev.conf" */ |
380 | } while (ENABLE_FEATURE_MDEV_CONF); | ||
381 | niro | 816 | |
382 | niro | 984 | if (ENABLE_FEATURE_MDEV_CONF) |
383 | config_close(parser); | ||
384 | niro | 532 | } |
385 | |||
386 | niro | 816 | /* File callback for /sys/ traversal */ |
387 | static int FAST_FUNC fileAction(const char *fileName, | ||
388 | struct stat *statbuf UNUSED_PARAM, | ||
389 | void *userData, | ||
390 | int depth UNUSED_PARAM) | ||
391 | { | ||
392 | size_t len = strlen(fileName) - 4; /* can't underflow */ | ||
393 | char *scratch = userData; | ||
394 | niro | 532 | |
395 | niro | 816 | /* len check is for paranoid reasons */ |
396 | if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX) | ||
397 | return FALSE; | ||
398 | |||
399 | strcpy(scratch, fileName); | ||
400 | scratch[len] = '\0'; | ||
401 | make_device(scratch, 0); | ||
402 | |||
403 | return TRUE; | ||
404 | } | ||
405 | |||
406 | /* Directory callback for /sys/ traversal */ | ||
407 | static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM, | ||
408 | struct stat *statbuf UNUSED_PARAM, | ||
409 | void *userData UNUSED_PARAM, | ||
410 | int depth) | ||
411 | niro | 532 | { |
412 | niro | 984 | /* Extract device subsystem -- the name of the directory |
413 | * under /sys/class/ */ | ||
414 | if (1 == depth) { | ||
415 | free(subsystem); | ||
416 | subsystem = strrchr(fileName, '/'); | ||
417 | if (subsystem) | ||
418 | subsystem = xstrdup(subsystem + 1); | ||
419 | } | ||
420 | |||
421 | niro | 816 | return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); |
422 | } | ||
423 | niro | 532 | |
424 | niro | 816 | /* For the full gory details, see linux/Documentation/firmware_class/README |
425 | * | ||
426 | * Firmware loading works like this: | ||
427 | * - kernel sets FIRMWARE env var | ||
428 | * - userspace checks /lib/firmware/$FIRMWARE | ||
429 | * - userspace waits for /sys/$DEVPATH/loading to appear | ||
430 | * - userspace writes "1" to /sys/$DEVPATH/loading | ||
431 | * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data | ||
432 | * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading | ||
433 | * - kernel loads firmware into device | ||
434 | */ | ||
435 | niro | 984 | static void load_firmware(const char *firmware, const char *sysfs_path) |
436 | niro | 816 | { |
437 | int cnt; | ||
438 | int firmware_fd, loading_fd, data_fd; | ||
439 | niro | 532 | |
440 | niro | 816 | /* check for /lib/firmware/$FIRMWARE */ |
441 | xchdir("/lib/firmware"); | ||
442 | firmware_fd = xopen(firmware, O_RDONLY); | ||
443 | niro | 532 | |
444 | niro | 816 | /* in case we goto out ... */ |
445 | data_fd = -1; | ||
446 | niro | 532 | |
447 | niro | 816 | /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */ |
448 | xchdir(sysfs_path); | ||
449 | for (cnt = 0; cnt < 30; ++cnt) { | ||
450 | loading_fd = open("loading", O_WRONLY); | ||
451 | if (loading_fd != -1) | ||
452 | goto loading; | ||
453 | sleep(1); | ||
454 | } | ||
455 | goto out; | ||
456 | niro | 532 | |
457 | niro | 816 | loading: |
458 | niro | 984 | /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */ |
459 | niro | 816 | if (full_write(loading_fd, "1", 1) != 1) |
460 | goto out; | ||
461 | niro | 532 | |
462 | niro | 984 | /* load firmware into /sys/$DEVPATH/data */ |
463 | niro | 816 | data_fd = open("data", O_WRONLY); |
464 | if (data_fd == -1) | ||
465 | goto out; | ||
466 | cnt = bb_copyfd_eof(firmware_fd, data_fd); | ||
467 | niro | 532 | |
468 | niro | 984 | /* tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading" */ |
469 | niro | 816 | if (cnt > 0) |
470 | full_write(loading_fd, "0", 1); | ||
471 | else | ||
472 | full_write(loading_fd, "-1", 2); | ||
473 | niro | 532 | |
474 | niro | 816 | out: |
475 | if (ENABLE_FEATURE_CLEAN_UP) { | ||
476 | close(firmware_fd); | ||
477 | close(loading_fd); | ||
478 | close(data_fd); | ||
479 | niro | 532 | } |
480 | } | ||
481 | |||
482 | niro | 816 | int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
483 | int mdev_main(int argc UNUSED_PARAM, char **argv) | ||
484 | niro | 532 | { |
485 | niro | 816 | RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); |
486 | niro | 532 | |
487 | niro | 816 | /* We can be called as hotplug helper */ |
488 | /* Kernel cannot provide suitable stdio fds for us, do it ourself */ | ||
489 | bb_sanitize_stdio(); | ||
490 | niro | 532 | |
491 | niro | 984 | /* Force the configuration file settings exactly */ |
492 | umask(0); | ||
493 | |||
494 | niro | 816 | xchdir("/dev"); |
495 | niro | 532 | |
496 | niro | 984 | if (argv[1] && strcmp(argv[1], "-s") == 0) { |
497 | niro | 816 | /* Scan: |
498 | * mdev -s | ||
499 | */ | ||
500 | niro | 532 | struct stat st; |
501 | |||
502 | xstat("/", &st); | ||
503 | niro | 816 | root_major = major(st.st_dev); |
504 | root_minor = minor(st.st_dev); | ||
505 | niro | 532 | |
506 | niro | 816 | /* ACTION_FOLLOWLINKS is needed since in newer kernels |
507 | * /sys/block/loop* (for example) are symlinks to dirs, | ||
508 | * not real directories. | ||
509 | * (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs, | ||
510 | niro | 984 | * but we can't enforce that on users) |
511 | */ | ||
512 | if (access("/sys/class/block", F_OK) != 0) { | ||
513 | /* Scan obsolete /sys/block only if /sys/class/block | ||
514 | * doesn't exist. Otherwise we'll have dupes. | ||
515 | * Also, do not complain if it doesn't exist. | ||
516 | * Some people configure kernel to have no blockdevs. | ||
517 | */ | ||
518 | recursive_action("/sys/block", | ||
519 | ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET, | ||
520 | fileAction, dirAction, temp, 0); | ||
521 | } | ||
522 | niro | 816 | recursive_action("/sys/class", |
523 | ACTION_RECURSE | ACTION_FOLLOWLINKS, | ||
524 | fileAction, dirAction, temp, 0); | ||
525 | } else { | ||
526 | niro | 984 | char *fw; |
527 | niro | 816 | char *seq; |
528 | char *action; | ||
529 | char *env_path; | ||
530 | niro | 984 | static const char keywords[] ALIGN1 = "remove\0add\0"; |
531 | enum { OP_remove = 0, OP_add }; | ||
532 | smalluint op; | ||
533 | niro | 532 | |
534 | niro | 816 | /* Hotplug: |
535 | niro | 984 | * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev |
536 | niro | 816 | * ACTION can be "add" or "remove" |
537 | * DEVPATH is like "/block/sda" or "/class/input/mice" | ||
538 | */ | ||
539 | niro | 532 | action = getenv("ACTION"); |
540 | env_path = getenv("DEVPATH"); | ||
541 | niro | 984 | subsystem = getenv("SUBSYSTEM"); |
542 | if (!action || !env_path /*|| !subsystem*/) | ||
543 | niro | 532 | bb_show_usage(); |
544 | niro | 984 | fw = getenv("FIRMWARE"); |
545 | op = index_in_strings(keywords, action); | ||
546 | /* If it exists, does /dev/mdev.seq match $SEQNUM? | ||
547 | * If it does not match, earlier mdev is running | ||
548 | * in parallel, and we need to wait */ | ||
549 | niro | 816 | seq = getenv("SEQNUM"); |
550 | if (seq) { | ||
551 | niro | 984 | int timeout = 2000 / 32; /* 2000 msec */ |
552 | niro | 816 | do { |
553 | niro | 984 | int seqlen; |
554 | char seqbuf[sizeof(int)*3 + 2]; | ||
555 | |||
556 | niro | 816 | seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1)); |
557 | niro | 984 | if (seqlen < 0) { |
558 | seq = NULL; | ||
559 | niro | 816 | break; |
560 | niro | 984 | } |
561 | niro | 816 | seqbuf[seqlen] = '\0'; |
562 | if (seqbuf[0] == '\n' /* seed file? */ | ||
563 | || strcmp(seq, seqbuf) == 0 /* correct idx? */ | ||
564 | ) { | ||
565 | break; | ||
566 | } | ||
567 | usleep(32*1000); | ||
568 | } while (--timeout); | ||
569 | } | ||
570 | |||
571 | snprintf(temp, PATH_MAX, "/sys%s", env_path); | ||
572 | niro | 984 | if (op == OP_remove) { |
573 | /* Ignoring "remove firmware". It was reported | ||
574 | * to happen and to cause erroneous deletion | ||
575 | * of device nodes. */ | ||
576 | if (!fw) | ||
577 | make_device(temp, 1); | ||
578 | } | ||
579 | else if (op == OP_add) { | ||
580 | niro | 816 | make_device(temp, 0); |
581 | if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { | ||
582 | if (fw) | ||
583 | load_firmware(fw, temp); | ||
584 | } | ||
585 | } | ||
586 | |||
587 | niro | 984 | if (seq) { |
588 | niro | 816 | xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1)); |
589 | } | ||
590 | niro | 532 | } |
591 | |||
592 | niro | 816 | if (ENABLE_FEATURE_CLEAN_UP) |
593 | RELEASE_CONFIG_BUFFER(temp); | ||
594 | |||
595 | niro | 984 | return EXIT_SUCCESS; |
596 | niro | 532 | } |