diff options
Diffstat (limited to 'util-linux/mdev.c')
-rw-r--r-- | util-linux/mdev.c | 676 |
1 files changed, 424 insertions, 252 deletions
diff --git a/util-linux/mdev.c b/util-linux/mdev.c index 7cabb1df6..c6be1b872 100644 --- a/util-linux/mdev.c +++ b/util-linux/mdev.c | |||
@@ -8,39 +8,91 @@ | |||
8 | * Licensed under GPLv2, see file LICENSE in this source tree. | 8 | * Licensed under GPLv2, see file LICENSE in this source tree. |
9 | */ | 9 | */ |
10 | 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 | |||
11 | //usage:#define mdev_trivial_usage | 73 | //usage:#define mdev_trivial_usage |
12 | //usage: "[-s]" | 74 | //usage: "[-s]" |
13 | //usage:#define mdev_full_usage "\n\n" | 75 | //usage:#define mdev_full_usage "\n\n" |
14 | //usage: " -s Scan /sys and populate /dev during system boot\n" | 76 | //usage: "mdev -s is to be run during boot to scan /sys and populate /dev.\n" |
15 | //usage: "\n" | 77 | //usage: "\n" |
16 | //usage: "It can be run by kernel as a hotplug helper. To activate it:\n" | 78 | //usage: "Bare mdev is a kernel hotplug helper. To activate it:\n" |
17 | //usage: " echo /sbin/mdev > /proc/sys/kernel/hotplug\n" | 79 | //usage: " echo /sbin/mdev >/proc/sys/kernel/hotplug\n" |
18 | //usage: IF_FEATURE_MDEV_CONF( | 80 | //usage: IF_FEATURE_MDEV_CONF( |
81 | //usage: "\n" | ||
19 | //usage: "It uses /etc/mdev.conf with lines\n" | 82 | //usage: "It uses /etc/mdev.conf with lines\n" |
20 | //usage: "[-]DEVNAME UID:GID PERM" | 83 | //usage: " [-]DEVNAME UID:GID PERM" |
21 | //usage: IF_FEATURE_MDEV_RENAME(" [>|=PATH]") | 84 | //usage: IF_FEATURE_MDEV_RENAME(" [>|=PATH]|[!]") |
22 | //usage: IF_FEATURE_MDEV_EXEC(" [@|$|*PROG]") | 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" | ||
23 | //usage: ) | 91 | //usage: ) |
24 | //usage: | 92 | //usage: "\n" |
25 | //usage:#define mdev_notes_usage "" | 93 | //usage: "If /dev/mdev.seq file exists, mdev will wait for its value\n" |
26 | //usage: IF_FEATURE_MDEV_CONFIG( | 94 | //usage: "to match $SEQNUM variable. This prevents plug/unplug races.\n" |
27 | //usage: "The mdev config file contains lines that look like:\n" | 95 | //usage: "To activate this feature, create empty /dev/mdev.seq at boot." |
28 | //usage: " hd[a-z][0-9]* 0:3 660\n\n" | ||
29 | //usage: "That's device name (with regex match), uid:gid, and permissions.\n\n" | ||
30 | //usage: IF_FEATURE_MDEV_EXEC( | ||
31 | //usage: "Optionally, that can be followed (on the same line) by a special character\n" | ||
32 | //usage: "and a command line to run after creating/before deleting the corresponding\n" | ||
33 | //usage: "device(s). The environment variable $MDEV indicates the active device node\n" | ||
34 | //usage: "(which is useful if it's a regex match). For example:\n\n" | ||
35 | //usage: " hdc root:cdrom 660 *ln -s $MDEV cdrom\n\n" | ||
36 | //usage: "The special characters are @ (run after creating), $ (run before deleting),\n" | ||
37 | //usage: "and * (run both after creating and before deleting). The commands run in\n" | ||
38 | //usage: "the /dev directory, and use system() which calls /bin/sh.\n\n" | ||
39 | //usage: ) | ||
40 | //usage: "Config file parsing stops on the first matching line. If no config\n" | ||
41 | //usage: "entry is matched, devices are created with default 0:0 660. (Make\n" | ||
42 | //usage: "the last line match .* to override this.)\n\n" | ||
43 | //usage: ) | ||
44 | 96 | ||
45 | #include "libbb.h" | 97 | #include "libbb.h" |
46 | #include "xregex.h" | 98 | #include "xregex.h" |
@@ -62,20 +114,11 @@ | |||
62 | * (todo: explain "delete" and $FIRMWARE) | 114 | * (todo: explain "delete" and $FIRMWARE) |
63 | * | 115 | * |
64 | * If /etc/mdev.conf exists, it may modify /dev/device_name's properties. | 116 | * If /etc/mdev.conf exists, it may modify /dev/device_name's properties. |
65 | * /etc/mdev.conf file format: | ||
66 | * | ||
67 | * [-][subsystem/]device user:grp mode [>|=path] [@|$|*command args...] | ||
68 | * [-]@maj,min[-min2] user:grp mode [>|=path] [@|$|*command args...] | ||
69 | * [-]$envvar=val user:grp mode [>|=path] [@|$|*command args...] | ||
70 | * | 117 | * |
71 | * Leading minus in 1st field means "don't stop on this line", otherwise | 118 | * Leading minus in 1st field means "don't stop on this line", otherwise |
72 | * search is stopped after the matching line is encountered. | 119 | * search is stopped after the matching line is encountered. |
73 | * | 120 | * |
74 | * The device name or "subsystem/device" combo is matched against 1st field | 121 | * $envvar=regex format is useful for loading modules for hot-plugged devices |
75 | * (which is a regex), or maj,min is matched against 1st field, | ||
76 | * or specified environment variable (as regex) is matched against 1st field. | ||
77 | * | ||
78 | * $envvar=val format is useful for loading modules for hot-plugged devices | ||
79 | * which do not have driver loaded yet. In this case /sys/class/.../dev | 122 | * which do not have driver loaded yet. In this case /sys/class/.../dev |
80 | * does not exist, but $MODALIAS is set to needed module's name | 123 | * does not exist, but $MODALIAS is set to needed module's name |
81 | * (actually, an alias to it) by kernel. This rule instructs mdev | 124 | * (actually, an alias to it) by kernel. This rule instructs mdev |
@@ -96,11 +139,33 @@ | |||
96 | * This happens regardless of /sys/class/.../dev existence. | 139 | * This happens regardless of /sys/class/.../dev existence. |
97 | */ | 140 | */ |
98 | 141 | ||
142 | struct rule { | ||
143 | bool keep_matching; | ||
144 | bool regex_compiled; | ||
145 | bool regex_has_slash; | ||
146 | mode_t mode; | ||
147 | int maj, min0, min1; | ||
148 | struct bb_uidgid_t ugid; | ||
149 | char *envvar; | ||
150 | char *ren_mov; | ||
151 | IF_FEATURE_MDEV_EXEC(char *r_cmd;) | ||
152 | regex_t match; | ||
153 | }; | ||
154 | |||
99 | struct globals { | 155 | struct globals { |
100 | int root_major, root_minor; | 156 | int root_major, root_minor; |
101 | char *subsystem; | 157 | char *subsystem; |
158 | #if ENABLE_FEATURE_MDEV_CONF | ||
159 | const char *filename; | ||
160 | parser_t *parser; | ||
161 | struct rule **rule_vec; | ||
162 | unsigned rule_idx; | ||
163 | #endif | ||
164 | struct rule cur_rule; | ||
102 | } FIX_ALIASING; | 165 | } FIX_ALIASING; |
103 | #define G (*(struct globals*)&bb_common_bufsiz1) | 166 | #define G (*(struct globals*)&bb_common_bufsiz1) |
167 | #define INIT_G() do { } while (0) | ||
168 | |||
104 | 169 | ||
105 | /* Prevent infinite loops in /sys symlinks */ | 170 | /* Prevent infinite loops in /sys symlinks */ |
106 | #define MAX_SYSFS_DEPTH 3 | 171 | #define MAX_SYSFS_DEPTH 3 |
@@ -108,6 +173,165 @@ struct globals { | |||
108 | /* We use additional 64+ bytes in make_device() */ | 173 | /* We use additional 64+ bytes in make_device() */ |
109 | #define SCRATCH_SIZE 80 | 174 | #define SCRATCH_SIZE 80 |
110 | 175 | ||
176 | #if 0 | ||
177 | # define dbg(...) bb_error_msg(__VA_ARGS__) | ||
178 | #else | ||
179 | # define dbg(...) ((void)0) | ||
180 | #endif | ||
181 | |||
182 | |||
183 | #if ENABLE_FEATURE_MDEV_CONF | ||
184 | |||
185 | static void make_default_cur_rule(void) | ||
186 | { | ||
187 | memset(&G.cur_rule, 0, sizeof(G.cur_rule)); | ||
188 | G.cur_rule.maj = -1; /* "not a @major,minor rule" */ | ||
189 | G.cur_rule.mode = 0660; | ||
190 | } | ||
191 | |||
192 | static void clean_up_cur_rule(void) | ||
193 | { | ||
194 | free(G.cur_rule.envvar); | ||
195 | if (G.cur_rule.regex_compiled) | ||
196 | regfree(&G.cur_rule.match); | ||
197 | free(G.cur_rule.ren_mov); | ||
198 | IF_FEATURE_MDEV_EXEC(free(G.cur_rule.r_cmd);) | ||
199 | make_default_cur_rule(); | ||
200 | } | ||
201 | |||
202 | static void parse_next_rule(void) | ||
203 | { | ||
204 | /* Note: on entry, G.cur_rule is set to default */ | ||
205 | while (1) { | ||
206 | char *tokens[4]; | ||
207 | char *val; | ||
208 | |||
209 | if (!config_read(G.parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) | ||
210 | break; | ||
211 | |||
212 | /* Fields: [-]regex uid:gid mode [alias] [cmd] */ | ||
213 | dbg("token1:'%s'", tokens[1]); | ||
214 | |||
215 | /* 1st field */ | ||
216 | val = tokens[0]; | ||
217 | G.cur_rule.keep_matching = ('-' == val[0]); | ||
218 | val += G.cur_rule.keep_matching; /* swallow leading dash */ | ||
219 | if (val[0] == '@') { | ||
220 | /* @major,minor[-minor2] */ | ||
221 | /* (useful when name is ambiguous: | ||
222 | * "/sys/class/usb/lp0" and | ||
223 | * "/sys/class/printer/lp0") | ||
224 | */ | ||
225 | int sc = sscanf(val, "@%u,%u-%u", &G.cur_rule.maj, &G.cur_rule.min0, &G.cur_rule.min1); | ||
226 | if (sc < 2 || G.cur_rule.maj < 0) { | ||
227 | bb_error_msg("bad @maj,min on line %d", G.parser->lineno); | ||
228 | goto next_rule; | ||
229 | } | ||
230 | if (sc == 2) | ||
231 | G.cur_rule.min1 = G.cur_rule.min0; | ||
232 | } else { | ||
233 | if (val[0] == '$') { | ||
234 | char *eq = strchr(++val, '='); | ||
235 | if (!eq) { | ||
236 | bb_error_msg("bad $envvar=regex on line %d", G.parser->lineno); | ||
237 | goto next_rule; | ||
238 | } | ||
239 | G.cur_rule.envvar = xstrndup(val, eq - val); | ||
240 | val = eq + 1; | ||
241 | } | ||
242 | xregcomp(&G.cur_rule.match, val, REG_EXTENDED); | ||
243 | G.cur_rule.regex_compiled = 1; | ||
244 | G.cur_rule.regex_has_slash = (strchr(val, '/') != NULL); | ||
245 | } | ||
246 | |||
247 | /* 2nd field: uid:gid - device ownership */ | ||
248 | if (get_uidgid(&G.cur_rule.ugid, tokens[1], /*allow_numeric:*/ 1) == 0) { | ||
249 | bb_error_msg("unknown user/group '%s' on line %d", tokens[1], G.parser->lineno); | ||
250 | goto next_rule; | ||
251 | } | ||
252 | |||
253 | /* 3rd field: mode - device permissions */ | ||
254 | bb_parse_mode(tokens[2], &G.cur_rule.mode); | ||
255 | |||
256 | /* 4th field (opt): ">|=alias" or "!" to not create the node */ | ||
257 | val = tokens[3]; | ||
258 | if (ENABLE_FEATURE_MDEV_RENAME && val && strchr(">=!", val[0])) { | ||
259 | char *s = skip_non_whitespace(val); | ||
260 | G.cur_rule.ren_mov = xstrndup(val, s - val); | ||
261 | val = skip_whitespace(s); | ||
262 | } | ||
263 | |||
264 | if (ENABLE_FEATURE_MDEV_EXEC && val && val[0]) { | ||
265 | const char *s = "$@*"; | ||
266 | const char *s2 = strchr(s, val[0]); | ||
267 | if (!s2) { | ||
268 | bb_error_msg("bad line %u", G.parser->lineno); | ||
269 | goto next_rule; | ||
270 | } | ||
271 | IF_FEATURE_MDEV_EXEC(G.cur_rule.r_cmd = xstrdup(val);) | ||
272 | } | ||
273 | |||
274 | return; | ||
275 | next_rule: | ||
276 | clean_up_cur_rule(); | ||
277 | } /* while (config_read) */ | ||
278 | |||
279 | dbg("config_close(G.parser)"); | ||
280 | config_close(G.parser); | ||
281 | G.parser = NULL; | ||
282 | |||
283 | return; | ||
284 | } | ||
285 | |||
286 | /* If mdev -s, we remember rules in G.rule_vec[]. | ||
287 | * Otherwise, there is no point in doing it, and we just | ||
288 | * save only one parsed rule in G.cur_rule. | ||
289 | */ | ||
290 | static const struct rule *next_rule(void) | ||
291 | { | ||
292 | struct rule *rule; | ||
293 | |||
294 | /* Open conf file if we didn't do it yet */ | ||
295 | if (!G.parser && G.filename) { | ||
296 | dbg("config_open('%s')", G.filename); | ||
297 | G.parser = config_open2(G.filename, fopen_for_read); | ||
298 | G.filename = NULL; | ||
299 | } | ||
300 | |||
301 | if (G.rule_vec) { | ||
302 | /* mdev -s */ | ||
303 | /* Do we have rule parsed already? */ | ||
304 | if (G.rule_vec[G.rule_idx]) { | ||
305 | dbg("< G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]); | ||
306 | return G.rule_vec[G.rule_idx++]; | ||
307 | } | ||
308 | make_default_cur_rule(); | ||
309 | } else { | ||
310 | /* not mdev -s */ | ||
311 | clean_up_cur_rule(); | ||
312 | } | ||
313 | |||
314 | /* Parse one more rule if file isn't fully read */ | ||
315 | rule = &G.cur_rule; | ||
316 | if (G.parser) { | ||
317 | parse_next_rule(); | ||
318 | if (G.rule_vec) { /* mdev -s */ | ||
319 | rule = memcpy(xmalloc(sizeof(G.cur_rule)), &G.cur_rule, sizeof(G.cur_rule)); | ||
320 | G.rule_vec = xrealloc_vector(G.rule_vec, 4, G.rule_idx); | ||
321 | G.rule_vec[G.rule_idx++] = rule; | ||
322 | dbg("> G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]); | ||
323 | } | ||
324 | } | ||
325 | |||
326 | return rule; | ||
327 | } | ||
328 | |||
329 | #else | ||
330 | |||
331 | # define next_rule() (&G.cur_rule) | ||
332 | |||
333 | #endif | ||
334 | |||
111 | /* Builds an alias path. | 335 | /* Builds an alias path. |
112 | * This function potentionally reallocates the alias parameter. | 336 | * This function potentionally reallocates the alias parameter. |
113 | * Only used for ENABLE_FEATURE_MDEV_RENAME | 337 | * Only used for ENABLE_FEATURE_MDEV_RENAME |
@@ -143,8 +367,8 @@ static void make_device(char *path, int delete) | |||
143 | { | 367 | { |
144 | char *device_name, *subsystem_slash_devname; | 368 | char *device_name, *subsystem_slash_devname; |
145 | int major, minor, type, len; | 369 | int major, minor, type, len; |
146 | mode_t mode; | 370 | |
147 | parser_t *parser; | 371 | dbg("%s('%s', delete:%d)", __func__, path, delete); |
148 | 372 | ||
149 | /* Try to read major/minor string. Note that the kernel puts \n after | 373 | /* Try to read major/minor string. Note that the kernel puts \n after |
150 | * the data, so we don't need to worry about null terminating the string | 374 | * the data, so we don't need to worry about null terminating the string |
@@ -197,246 +421,184 @@ static void make_device(char *path, int delete) | |||
197 | path = subsystem_slash_devname; | 421 | path = subsystem_slash_devname; |
198 | } | 422 | } |
199 | 423 | ||
200 | /* If we have config file, look up user settings */ | 424 | #if ENABLE_FEATURE_MDEV_CONF |
201 | if (ENABLE_FEATURE_MDEV_CONF) | 425 | G.rule_idx = 0; /* restart from the beginning (think mdev -s) */ |
202 | parser = config_open2("/etc/mdev.conf", fopen_for_read); | 426 | #endif |
203 | 427 | for (;;) { | |
204 | do { | 428 | const char *str_to_match; |
205 | int keep_matching; | 429 | regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP]; |
206 | struct bb_uidgid_t ugid; | 430 | char *command; |
207 | char *tokens[4]; | 431 | char *alias; |
208 | char *command = NULL; | ||
209 | char *alias = NULL; | ||
210 | char aliaslink = aliaslink; /* for compiler */ | 432 | char aliaslink = aliaslink; /* for compiler */ |
433 | const char *node_name; | ||
434 | const struct rule *rule; | ||
211 | 435 | ||
212 | /* Defaults in case we won't match any line */ | 436 | str_to_match = ""; |
213 | ugid.uid = ugid.gid = 0; | 437 | |
214 | keep_matching = 0; | 438 | rule = next_rule(); |
215 | mode = 0660; | 439 | |
216 | 440 | #if ENABLE_FEATURE_MDEV_CONF | |
217 | if (ENABLE_FEATURE_MDEV_CONF | 441 | if (rule->maj >= 0) { /* @maj,min rule */ |
218 | && config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL) | 442 | if (major != rule->maj) |
219 | ) { | 443 | continue; |
220 | char *val; | 444 | if (minor < rule->min0 || minor > rule->min1) |
221 | char *str_to_match; | 445 | continue; |
222 | regmatch_t off[1 + 9 * ENABLE_FEATURE_MDEV_RENAME_REGEXP]; | 446 | memset(off, 0, sizeof(off)); |
223 | 447 | goto rule_matches; | |
224 | val = tokens[0]; | 448 | } |
225 | keep_matching = ('-' == val[0]); | 449 | if (rule->envvar) { /* $envvar=regex rule */ |
226 | val += keep_matching; /* swallow leading dash */ | 450 | str_to_match = getenv(rule->envvar); |
227 | 451 | dbg("getenv('%s'):'%s'", rule->envvar, str_to_match); | |
228 | /* Match against either "subsystem/device_name" | 452 | if (!str_to_match) |
229 | * or "device_name" alone */ | 453 | continue; |
230 | str_to_match = strchr(val, '/') ? path : device_name; | 454 | } else { |
231 | 455 | /* regex to match [subsystem/]device_name */ | |
232 | /* Fields: regex uid:gid mode [alias] [cmd] */ | 456 | str_to_match = (rule->regex_has_slash ? path : device_name); |
233 | 457 | } | |
234 | if (val[0] == '@') { | 458 | |
235 | /* @major,minor[-minor2] */ | 459 | if (rule->regex_compiled) { |
236 | /* (useful when name is ambiguous: | 460 | int regex_match = regexec(&rule->match, str_to_match, ARRAY_SIZE(off), off, 0); |
237 | * "/sys/class/usb/lp0" and | 461 | dbg("regex_match for '%s':%d", str_to_match, regex_match); |
238 | * "/sys/class/printer/lp0") */ | 462 | //bb_error_msg("matches:"); |
239 | int cmaj, cmin0, cmin1, sc; | 463 | //for (int i = 0; i < ARRAY_SIZE(off); i++) { |
240 | if (major < 0) | 464 | // if (off[i].rm_so < 0) continue; |
241 | continue; /* no dev, no match */ | 465 | // bb_error_msg("match %d: '%.*s'\n", i, |
242 | sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1); | 466 | // (int)(off[i].rm_eo - off[i].rm_so), |
243 | if (sc < 1 | 467 | // device_name + off[i].rm_so); |
244 | || major != cmaj | 468 | //} |
245 | || (sc == 2 && minor != cmin0) | 469 | |
246 | || (sc == 3 && (minor < cmin0 || minor > cmin1)) | 470 | if (regex_match != 0 |
247 | ) { | 471 | /* regexec returns whole pattern as "range" 0 */ |
248 | continue; /* this line doesn't match */ | 472 | || off[0].rm_so != 0 |
249 | } | 473 | || (int)off[0].rm_eo != (int)strlen(str_to_match) |
250 | goto line_matches; | 474 | ) { |
251 | } | 475 | continue; /* this rule doesn't match */ |
252 | if (val[0] == '$') { | ||
253 | /* regex to match an environment variable */ | ||
254 | char *eq = strchr(++val, '='); | ||
255 | if (!eq) | ||
256 | continue; | ||
257 | *eq = '\0'; | ||
258 | str_to_match = getenv(val); | ||
259 | if (!str_to_match) | ||
260 | continue; | ||
261 | str_to_match -= strlen(val) + 1; | ||
262 | *eq = '='; | ||
263 | } | 476 | } |
264 | /* else: regex to match [subsystem/]device_name */ | 477 | } |
265 | 478 | /* else: it's final implicit "match-all" rule */ | |
266 | { | 479 | #endif |
267 | regex_t match; | 480 | |
268 | int result; | 481 | rule_matches: |
269 | 482 | dbg("rule matched"); | |
270 | xregcomp(&match, val, REG_EXTENDED); | 483 | |
271 | result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0); | 484 | /* Build alias name */ |
272 | regfree(&match); | 485 | alias = NULL; |
273 | //bb_error_msg("matches:"); | 486 | if (ENABLE_FEATURE_MDEV_RENAME && rule->ren_mov) { |
274 | //for (int i = 0; i < ARRAY_SIZE(off); i++) { | 487 | aliaslink = rule->ren_mov[0]; |
275 | // if (off[i].rm_so < 0) continue; | 488 | if (aliaslink == '!') { |
276 | // bb_error_msg("match %d: '%.*s'\n", i, | 489 | /* "!": suppress node creation/deletion */ |
277 | // (int)(off[i].rm_eo - off[i].rm_so), | 490 | major = -2; |
278 | // device_name + off[i].rm_so); | ||
279 | //} | ||
280 | |||
281 | /* If no match, skip rest of line */ | ||
282 | /* (regexec returns whole pattern as "range" 0) */ | ||
283 | if (result | ||
284 | || off[0].rm_so | ||
285 | || ((int)off[0].rm_eo != (int)strlen(str_to_match)) | ||
286 | ) { | ||
287 | continue; /* this line doesn't match */ | ||
288 | } | ||
289 | } | 491 | } |
290 | line_matches: | 492 | else if (aliaslink == '>' || aliaslink == '=') { |
291 | /* This line matches. Stop parsing after parsing | 493 | if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) { |
292 | * the rest the line unless keep_matching == 1 */ | 494 | char *s; |
293 | 495 | char *p; | |
294 | /* 2nd field: uid:gid - device ownership */ | 496 | unsigned n; |
295 | if (get_uidgid(&ugid, tokens[1], /*allow_numeric:*/ 1) == 0) | 497 | |
296 | bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno); | 498 | /* substitute %1..9 with off[1..9], if any */ |
297 | 499 | n = 0; | |
298 | /* 3rd field: mode - device permissions */ | 500 | s = rule->ren_mov; |
299 | bb_parse_mode(tokens[2], &mode); | 501 | while (*s) |
300 | 502 | if (*s++ == '%') | |
301 | val = tokens[3]; | 503 | n++; |
302 | /* 4th field (opt): ">|=alias" or "!" to not create the node */ | 504 | |
303 | 505 | p = alias = xzalloc(strlen(rule->ren_mov) + n * strlen(str_to_match)); | |
304 | if (ENABLE_FEATURE_MDEV_RENAME && val) { | 506 | s = rule->ren_mov + 1; |
305 | char *a, *s, *st; | 507 | while (*s) { |
306 | 508 | *p = *s; | |
307 | a = val; | 509 | if ('%' == *s) { |
308 | s = strchrnul(val, ' '); | 510 | unsigned i = (s[1] - '0'); |
309 | st = strchrnul(val, '\t'); | 511 | if (i <= 9 && off[i].rm_so >= 0) { |
310 | if (st < s) | 512 | n = off[i].rm_eo - off[i].rm_so; |
311 | s = st; | 513 | strncpy(p, str_to_match + off[i].rm_so, n); |
312 | st = (s[0] && s[1]) ? s+1 : NULL; | 514 | p += n - 1; |
313 | 515 | s++; | |
314 | aliaslink = a[0]; | ||
315 | if (aliaslink == '!' && s == a+1) { | ||
316 | val = st; | ||
317 | /* "!": suppress node creation/deletion */ | ||
318 | major = -2; | ||
319 | } | ||
320 | else if (aliaslink == '>' || aliaslink == '=') { | ||
321 | val = st; | ||
322 | s[0] = '\0'; | ||
323 | if (ENABLE_FEATURE_MDEV_RENAME_REGEXP) { | ||
324 | char *p; | ||
325 | unsigned i, n; | ||
326 | |||
327 | /* substitute %1..9 with off[1..9], if any */ | ||
328 | n = 0; | ||
329 | s = a; | ||
330 | while (*s) | ||
331 | if (*s++ == '%') | ||
332 | n++; | ||
333 | |||
334 | p = alias = xzalloc(strlen(a) + n * strlen(str_to_match)); | ||
335 | s = a + 1; | ||
336 | while (*s) { | ||
337 | *p = *s; | ||
338 | if ('%' == *s) { | ||
339 | i = (s[1] - '0'); | ||
340 | if (i <= 9 && off[i].rm_so >= 0) { | ||
341 | n = off[i].rm_eo - off[i].rm_so; | ||
342 | strncpy(p, str_to_match + off[i].rm_so, n); | ||
343 | p += n - 1; | ||
344 | s++; | ||
345 | } | ||
346 | } | 516 | } |
347 | p++; | ||
348 | s++; | ||
349 | } | 517 | } |
350 | } else { | 518 | p++; |
351 | alias = xstrdup(a + 1); | 519 | s++; |
352 | } | 520 | } |
521 | } else { | ||
522 | alias = xstrdup(rule->ren_mov + 1); | ||
353 | } | 523 | } |
354 | } | 524 | } |
525 | } | ||
526 | dbg("alias:'%s'", alias); | ||
355 | 527 | ||
356 | if (ENABLE_FEATURE_MDEV_EXEC && val) { | 528 | command = NULL; |
357 | const char *s = "$@*"; | 529 | IF_FEATURE_MDEV_EXEC(command = rule->r_cmd;) |
358 | const char *s2 = strchr(s, val[0]); | 530 | if (command) { |
359 | 531 | const char *s = "$@*"; | |
360 | if (!s2) { | 532 | const char *s2 = strchr(s, command[0]); |
361 | bb_error_msg("bad line %u", parser->lineno); | ||
362 | if (ENABLE_FEATURE_MDEV_RENAME) | ||
363 | free(alias); | ||
364 | continue; | ||
365 | } | ||
366 | 533 | ||
367 | /* Are we running this command now? | 534 | /* Are we running this command now? |
368 | * Run $cmd on delete, @cmd on create, *cmd on both | 535 | * Run $cmd on delete, @cmd on create, *cmd on both |
536 | */ | ||
537 | if (s2 - s != delete) { | ||
538 | /* We are here if: '*', | ||
539 | * or: '@' and delete = 0, | ||
540 | * or: '$' and delete = 1 | ||
369 | */ | 541 | */ |
370 | if (s2 - s != delete) { | 542 | command++; |
371 | /* We are here if: '*', | 543 | } else { |
372 | * or: '@' and delete = 0, | 544 | command = NULL; |
373 | * or: '$' and delete = 1 | ||
374 | */ | ||
375 | command = xstrdup(val + 1); | ||
376 | } | ||
377 | } | 545 | } |
378 | } | 546 | } |
379 | 547 | dbg("command:'%s'", command); | |
380 | /* End of field parsing */ | ||
381 | 548 | ||
382 | /* "Execute" the line we found */ | 549 | /* "Execute" the line we found */ |
383 | { | 550 | node_name = device_name; |
384 | const char *node_name; | 551 | if (ENABLE_FEATURE_MDEV_RENAME && alias) { |
385 | 552 | node_name = alias = build_alias(alias, device_name); | |
386 | node_name = device_name; | 553 | dbg("alias2:'%s'", alias); |
387 | if (ENABLE_FEATURE_MDEV_RENAME && alias) | 554 | } |
388 | node_name = alias = build_alias(alias, device_name); | ||
389 | |||
390 | if (!delete && major >= 0) { | ||
391 | if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST) | ||
392 | bb_perror_msg("can't create '%s'", node_name); | ||
393 | if (major == G.root_major && minor == G.root_minor) | ||
394 | symlink(node_name, "root"); | ||
395 | if (ENABLE_FEATURE_MDEV_CONF) { | ||
396 | chmod(node_name, mode); | ||
397 | chown(node_name, ugid.uid, ugid.gid); | ||
398 | } | ||
399 | if (ENABLE_FEATURE_MDEV_RENAME && alias) { | ||
400 | if (aliaslink == '>') | ||
401 | symlink(node_name, device_name); | ||
402 | } | ||
403 | } | ||
404 | 555 | ||
405 | if (ENABLE_FEATURE_MDEV_EXEC && command) { | 556 | if (!delete && major >= 0) { |
406 | /* setenv will leak memory, use putenv/unsetenv/free */ | 557 | dbg("mknod('%s',%o,(%d,%d))", node_name, rule->mode | type, major, minor); |
407 | char *s = xasprintf("%s=%s", "MDEV", node_name); | 558 | if (mknod(node_name, rule->mode | type, makedev(major, minor)) && errno != EEXIST) |
408 | char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem); | 559 | bb_perror_msg("can't create '%s'", node_name); |
409 | putenv(s); | 560 | if (major == G.root_major && minor == G.root_minor) |
410 | putenv(s1); | 561 | symlink(node_name, "root"); |
411 | if (system(command) == -1) | 562 | if (ENABLE_FEATURE_MDEV_CONF) { |
412 | bb_perror_msg("can't run '%s'", command); | 563 | chmod(node_name, rule->mode); |
413 | bb_unsetenv_and_free(s1); | 564 | chown(node_name, rule->ugid.uid, rule->ugid.gid); |
414 | bb_unsetenv_and_free(s); | ||
415 | free(command); | ||
416 | } | 565 | } |
417 | 566 | if (ENABLE_FEATURE_MDEV_RENAME && alias) { | |
418 | if (delete && major >= -1) { | 567 | if (aliaslink == '>') |
419 | if (ENABLE_FEATURE_MDEV_RENAME && alias) { | 568 | symlink(node_name, device_name); |
420 | if (aliaslink == '>') | ||
421 | unlink(device_name); | ||
422 | } | ||
423 | unlink(node_name); | ||
424 | } | 569 | } |
570 | } | ||
571 | |||
572 | if (ENABLE_FEATURE_MDEV_EXEC && command) { | ||
573 | /* setenv will leak memory, use putenv/unsetenv/free */ | ||
574 | char *s = xasprintf("%s=%s", "MDEV", node_name); | ||
575 | char *s1 = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem); | ||
576 | putenv(s); | ||
577 | putenv(s1); | ||
578 | if (system(command) == -1) | ||
579 | bb_perror_msg("can't run '%s'", command); | ||
580 | bb_unsetenv_and_free(s1); | ||
581 | bb_unsetenv_and_free(s); | ||
582 | } | ||
425 | 583 | ||
426 | if (ENABLE_FEATURE_MDEV_RENAME) | 584 | if (delete && major >= -1) { |
427 | free(alias); | 585 | if (ENABLE_FEATURE_MDEV_RENAME && alias) { |
586 | if (aliaslink == '>') | ||
587 | unlink(device_name); | ||
588 | } | ||
589 | unlink(node_name); | ||
428 | } | 590 | } |
429 | 591 | ||
592 | if (ENABLE_FEATURE_MDEV_RENAME) | ||
593 | free(alias); | ||
594 | |||
430 | /* We found matching line. | 595 | /* We found matching line. |
431 | * Stop unless it was prefixed with '-' */ | 596 | * Stop unless it was prefixed with '-' |
432 | if (ENABLE_FEATURE_MDEV_CONF && !keep_matching) | 597 | */ |
598 | if (!ENABLE_FEATURE_MDEV_CONF || !rule->keep_matching) | ||
433 | break; | 599 | break; |
600 | } /* for (;;) */ | ||
434 | 601 | ||
435 | /* end of "while line is read from /etc/mdev.conf" */ | ||
436 | } while (ENABLE_FEATURE_MDEV_CONF); | ||
437 | |||
438 | if (ENABLE_FEATURE_MDEV_CONF) | ||
439 | config_close(parser); | ||
440 | free(subsystem_slash_devname); | 602 | free(subsystem_slash_devname); |
441 | } | 603 | } |
442 | 604 | ||
@@ -541,6 +703,12 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) | |||
541 | { | 703 | { |
542 | RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); | 704 | RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); |
543 | 705 | ||
706 | INIT_G(); | ||
707 | |||
708 | #if ENABLE_FEATURE_MDEV_CONF | ||
709 | G.filename = "/etc/mdev.conf"; | ||
710 | #endif | ||
711 | |||
544 | /* We can be called as hotplug helper */ | 712 | /* We can be called as hotplug helper */ |
545 | /* Kernel cannot provide suitable stdio fds for us, do it ourself */ | 713 | /* Kernel cannot provide suitable stdio fds for us, do it ourself */ |
546 | bb_sanitize_stdio(); | 714 | bb_sanitize_stdio(); |
@@ -556,6 +724,10 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) | |||
556 | */ | 724 | */ |
557 | struct stat st; | 725 | struct stat st; |
558 | 726 | ||
727 | #if ENABLE_FEATURE_MDEV_CONF | ||
728 | /* Same as xrealloc_vector(NULL, 4, 0): */ | ||
729 | G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec)); | ||
730 | #endif | ||
559 | xstat("/", &st); | 731 | xstat("/", &st); |
560 | G.root_major = major(st.st_dev); | 732 | G.root_major = major(st.st_dev); |
561 | G.root_minor = minor(st.st_dev); | 733 | G.root_minor = minor(st.st_dev); |