1 |
/* vi: set sw=4 ts=4: */ |
/* vi: set sw=4 ts=4: */ |
2 |
/* |
/* |
|
* |
|
3 |
* mdev - Mini udev for busybox |
* mdev - Mini udev for busybox |
4 |
* |
* |
5 |
* Copyright 2005 Rob Landley <rob@landley.net> |
* Copyright 2005 Rob Landley <rob@landley.net> |
7 |
* |
* |
8 |
* Licensed under GPL version 2, see file LICENSE in this tarball for details. |
* Licensed under GPL version 2, see file LICENSE in this tarball for details. |
9 |
*/ |
*/ |
|
|
|
10 |
#include "libbb.h" |
#include "libbb.h" |
11 |
#include "xregex.h" |
#include "xregex.h" |
12 |
|
|
13 |
|
/* "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 |
struct globals { |
struct globals { |
65 |
int root_major, root_minor; |
int root_major, root_minor; |
66 |
|
char *subsystem; |
67 |
}; |
}; |
68 |
#define G (*(struct globals*)&bb_common_bufsiz1) |
#define G (*(struct globals*)&bb_common_bufsiz1) |
69 |
#define root_major (G.root_major) |
#define root_major (G.root_major) |
70 |
#define root_minor (G.root_minor) |
#define root_minor (G.root_minor) |
71 |
|
#define subsystem (G.subsystem ) |
72 |
|
|
73 |
/* Prevent infinite loops in /sys symlinks */ |
/* Prevent infinite loops in /sys symlinks */ |
74 |
#define MAX_SYSFS_DEPTH 3 |
#define MAX_SYSFS_DEPTH 3 |
76 |
/* We use additional 64+ bytes in make_device() */ |
/* We use additional 64+ bytes in make_device() */ |
77 |
#define SCRATCH_SIZE 80 |
#define SCRATCH_SIZE 80 |
78 |
|
|
|
#if ENABLE_FEATURE_MDEV_RENAME |
|
79 |
/* Builds an alias path. |
/* Builds an alias path. |
80 |
* This function potentionally reallocates the alias parameter. |
* This function potentionally reallocates the alias parameter. |
81 |
|
* Only used for ENABLE_FEATURE_MDEV_RENAME |
82 |
*/ |
*/ |
83 |
static char *build_alias(char *alias, const char *device_name) |
static char *build_alias(char *alias, const char *device_name) |
84 |
{ |
{ |
100 |
|
|
101 |
return alias; |
return alias; |
102 |
} |
} |
|
#endif |
|
103 |
|
|
104 |
/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ |
/* mknod in /dev based on a path like "/sys/block/hda/hda1" |
105 |
/* NB: "mdev -s" may call us many times, do not leak memory/fds! */ |
* 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 |
static void make_device(char *path, int delete) |
static void make_device(char *path, int delete) |
111 |
{ |
{ |
112 |
const char *device_name; |
char *device_name; |
113 |
int major, minor, type, len; |
int major, minor, type, len; |
114 |
int mode = 0660; |
mode_t mode; |
|
#if ENABLE_FEATURE_MDEV_CONF |
|
|
struct bb_uidgid_t ugid = { 0, 0 }; |
|
115 |
parser_t *parser; |
parser_t *parser; |
|
char *tokens[5]; |
|
|
#endif |
|
|
#if ENABLE_FEATURE_MDEV_EXEC |
|
|
char *command = NULL; |
|
|
#endif |
|
|
#if ENABLE_FEATURE_MDEV_RENAME |
|
|
char *alias = NULL; |
|
|
char aliaslink = aliaslink; /* for compiler */ |
|
|
#endif |
|
|
char *dev_maj_min = path + strlen(path); |
|
|
|
|
|
/* Force the configuration file settings exactly. */ |
|
|
umask(0); |
|
116 |
|
|
117 |
/* Try to read major/minor string. Note that the kernel puts \n after |
/* 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 |
* the data, so we don't need to worry about null terminating the string |
121 |
*/ |
*/ |
122 |
major = -1; |
major = -1; |
123 |
if (!delete) { |
if (!delete) { |
124 |
|
char *dev_maj_min = path + strlen(path); |
125 |
|
|
126 |
strcpy(dev_maj_min, "/dev"); |
strcpy(dev_maj_min, "/dev"); |
127 |
len = open_read_close(path, dev_maj_min + 1, 64); |
len = open_read_close(path, dev_maj_min + 1, 64); |
128 |
*dev_maj_min++ = '\0'; |
*dev_maj_min = '\0'; |
129 |
if (len < 1) { |
if (len < 1) { |
130 |
if (!ENABLE_FEATURE_MDEV_EXEC) |
if (!ENABLE_FEATURE_MDEV_EXEC) |
131 |
return; |
return; |
132 |
/* no "dev" file, so just try to run script */ |
/* no "dev" file, but we can still run scripts |
133 |
*dev_maj_min = '\0'; |
* based on device name */ |
134 |
} else if (sscanf(dev_maj_min, "%u:%u", &major, &minor) != 2) { |
} else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) != 2) { |
135 |
major = -1; |
major = -1; |
136 |
} |
} |
137 |
} |
} |
138 |
|
|
139 |
/* Determine device name, type, major and minor */ |
/* Determine device name, type, major and minor */ |
140 |
device_name = bb_basename(path); |
device_name = (char*) bb_basename(path); |
141 |
/* http://kernel.org/doc/pending/hotplug.txt says that only |
/* http://kernel.org/doc/pending/hotplug.txt says that only |
142 |
* "/sys/block/..." is for block devices. "/sys/bus" etc is not. |
* "/sys/block/..." is for block devices. "/sys/bus" etc is not. |
143 |
* But since 2.6.25 block devices are also in /sys/class/block. |
* But since 2.6.25 block devices are also in /sys/class/block, |
144 |
* We use strstr("/block/") to forestall future surprises. */ |
* we use strstr("/block/") to forestall future surprises. */ |
145 |
type = S_IFCHR; |
type = S_IFCHR; |
146 |
if (strstr(path, "/block/")) |
if (strstr(path, "/block/")) |
147 |
type = S_IFBLK; |
type = S_IFBLK; |
148 |
|
|
149 |
#if ENABLE_FEATURE_MDEV_CONF |
/* Make path point to "subsystem/device_name" */ |
150 |
parser = config_open2("/etc/mdev.conf", fopen_for_read); |
if (path[5] == 'b') /* legacy /sys/block? */ |
151 |
|
path += sizeof("/sys/") - 1; |
152 |
|
else |
153 |
|
path += sizeof("/sys/class/") - 1; |
154 |
|
|
155 |
/* If we have config file, look up user settings */ |
/* If we have config file, look up user settings */ |
156 |
while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) { |
if (ENABLE_FEATURE_MDEV_CONF) |
157 |
regmatch_t off[1 + 9*ENABLE_FEATURE_MDEV_RENAME_REGEXP]; |
parser = config_open2("/etc/mdev.conf", fopen_for_read); |
158 |
char *val; |
|
159 |
|
do { |
160 |
/* Fields: regex uid:gid mode [alias] [cmd] */ |
int keep_matching; |
161 |
|
struct bb_uidgid_t ugid; |
162 |
/* 1st field: @<numeric maj,min>... */ |
char *tokens[4]; |
163 |
if (tokens[0][0] == '@') { |
char *command = NULL; |
164 |
/* @major,minor[-last] */ |
char *alias = NULL; |
165 |
/* (useful when name is ambiguous: |
char aliaslink = aliaslink; /* for compiler */ |
166 |
* "/sys/class/usb/lp0" and |
|
167 |
* "/sys/class/printer/lp0") */ |
/* Defaults in case we won't match any line */ |
168 |
int cmaj, cmin0, cmin1, sc; |
ugid.uid = ugid.gid = 0; |
169 |
if (major < 0) |
keep_matching = 0; |
170 |
continue; /* no dev, no match */ |
mode = 0660; |
171 |
sc = sscanf(tokens[0], "@%u,%u-%u", &cmaj, &cmin0, &cmin1); |
|
172 |
if (sc < 1 || major != cmaj |
if (ENABLE_FEATURE_MDEV_CONF |
173 |
|| (sc == 2 && minor != cmin0) |
&& config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL) |
174 |
|| (sc == 3 && (minor < cmin0 || minor > cmin1)) |
) { |
175 |
) { |
char *val; |
176 |
continue; /* no match */ |
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 |
} |
} |
206 |
} else { /* ... or regex to match device name */ |
if (val[0] == '$') { |
207 |
regex_t match; |
/* regex to match an environment variable */ |
208 |
int result; |
char *eq = strchr(++val, '='); |
209 |
|
if (!eq) |
210 |
/* Is this it? */ |
continue; |
211 |
xregcomp(&match, tokens[0], REG_EXTENDED); |
*eq = '\0'; |
212 |
result = regexec(&match, device_name, ARRAY_SIZE(off), off, 0); |
str_to_match = getenv(val); |
213 |
regfree(&match); |
if (!str_to_match) |
214 |
|
continue; |
215 |
//bb_error_msg("matches:"); |
str_to_match -= strlen(val) + 1; |
216 |
//for (int i = 0; i < ARRAY_SIZE(off); i++) { |
*eq = '='; |
|
// if (off[i].rm_so < 0) continue; |
|
|
// bb_error_msg("match %d: '%.*s'\n", i, |
|
|
// (int)(off[i].rm_eo - off[i].rm_so), |
|
|
// device_name + off[i].rm_so); |
|
|
//} |
|
|
|
|
|
/* If not this device, skip rest of line */ |
|
|
/* (regexec returns whole pattern as "range" 0) */ |
|
|
if (result || off[0].rm_so |
|
|
|| ((int)off[0].rm_eo != (int)strlen(device_name)) |
|
|
) { |
|
|
continue; |
|
217 |
} |
} |
218 |
} |
/* else: regex to match [subsystem/]device_name */ |
|
|
|
|
/* This line matches: stop parsing the file |
|
|
* after parsing the rest of fields */ |
|
|
|
|
|
/* 2nd field: uid:gid - device ownership */ |
|
|
parse_chown_usergroup_or_die(&ugid, tokens[1]); |
|
|
|
|
|
/* 3rd field: mode - device permissions */ |
|
|
mode = strtoul(tokens[2], NULL, 8); |
|
219 |
|
|
220 |
val = tokens[3]; |
{ |
221 |
/* 4th field (opt): >alias */ |
regex_t match; |
222 |
#if ENABLE_FEATURE_MDEV_RENAME |
int result; |
223 |
if (!val) |
|
224 |
break; |
xregcomp(&match, val, REG_EXTENDED); |
225 |
aliaslink = *val; |
result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0); |
226 |
if (aliaslink == '>' || aliaslink == '=') { |
regfree(&match); |
227 |
char *s; |
//bb_error_msg("matches:"); |
228 |
#if ENABLE_FEATURE_MDEV_RENAME_REGEXP |
//for (int i = 0; i < ARRAY_SIZE(off); i++) { |
229 |
char *p; |
// if (off[i].rm_so < 0) continue; |
230 |
unsigned i, n; |
// bb_error_msg("match %d: '%.*s'\n", i, |
231 |
#endif |
// (int)(off[i].rm_eo - off[i].rm_so), |
232 |
char *a = val; |
// device_name + off[i].rm_so); |
233 |
s = strchrnul(val, ' '); |
//} |
234 |
val = (s[0] && s[1]) ? s+1 : NULL; |
|
235 |
s[0] = '\0'; |
/* If no match, skip rest of line */ |
236 |
#if ENABLE_FEATURE_MDEV_RENAME_REGEXP |
/* (regexec returns whole pattern as "range" 0) */ |
237 |
/* substitute %1..9 with off[1..9], if any */ |
if (result || off[0].rm_so |
238 |
n = 0; |
|| ((int)off[0].rm_eo != (int)strlen(str_to_match)) |
239 |
s = a; |
) { |
240 |
while (*s) |
continue; /* this line doesn't match */ |
241 |
if (*s++ == '%') |
} |
242 |
n++; |
} |
243 |
|
line_matches: |
244 |
p = alias = xzalloc(strlen(a) + n * strlen(device_name)); |
/* This line matches. Stop parsing after parsing |
245 |
s = a + 1; |
* the rest the line unless keep_matching == 1 */ |
246 |
while (*s) { |
|
247 |
*p = *s; |
/* 2nd field: uid:gid - device ownership */ |
248 |
if ('%' == *s) { |
if (get_uidgid(&ugid, tokens[1], 1) == 0) |
249 |
i = (s[1] - '0'); |
bb_error_msg("unknown user/group %s on line %d", tokens[1], parser->lineno); |
250 |
if (i <= 9 && off[i].rm_so >= 0) { |
|
251 |
n = off[i].rm_eo - off[i].rm_so; |
/* 3rd field: mode - device permissions */ |
252 |
strncpy(p, device_name + off[i].rm_so, n); |
/* mode = strtoul(tokens[2], NULL, 8); */ |
253 |
p += n - 1; |
bb_parse_mode(tokens[2], &mode); |
254 |
s++; |
|
255 |
|
val = tokens[3]; |
256 |
|
/* 4th field (opt): >|=alias */ |
257 |
|
|
258 |
|
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 |
|
|
265 |
|
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 |
} |
} |
300 |
} |
} |
|
p++; |
|
|
s++; |
|
301 |
} |
} |
|
#else |
|
|
alias = xstrdup(a + 1); |
|
|
#endif |
|
|
} |
|
|
#endif /* ENABLE_FEATURE_MDEV_RENAME */ |
|
|
|
|
|
#if ENABLE_FEATURE_MDEV_EXEC |
|
|
/* The rest (opt): command to run */ |
|
|
if (!val) |
|
|
break; |
|
|
{ |
|
|
const char *s = "@$*"; |
|
|
const char *s2 = strchr(s, *val); |
|
302 |
|
|
303 |
if (!s2) |
if (ENABLE_FEATURE_MDEV_EXEC && val) { |
304 |
bb_error_msg_and_die("bad line %u", parser->lineno); |
const char *s = "$@*"; |
305 |
|
const char *s2 = strchr(s, val[0]); |
306 |
|
|
307 |
|
if (!s2) { |
308 |
|
bb_error_msg("bad line %u", parser->lineno); |
309 |
|
if (ENABLE_FEATURE_MDEV_RENAME) |
310 |
|
free(alias); |
311 |
|
continue; |
312 |
|
} |
313 |
|
|
314 |
/* Correlate the position in the "@$*" with the delete |
/* Are we running this command now? |
315 |
* step so that we get the proper behavior: |
* Run $cmd on delete, @cmd on create, *cmd on both |
316 |
* @cmd: run on create |
*/ |
317 |
* $cmd: run on delete |
if (s2-s != delete) |
318 |
* *cmd: run on both |
command = xstrdup(val + 1); |
|
*/ |
|
|
if ((s2 - s + 1) /*1/2/3*/ & /*1/2*/ (1 + delete)) { |
|
|
command = xstrdup(val + 1); |
|
319 |
} |
} |
320 |
} |
} |
|
#endif |
|
|
/* end of field parsing */ |
|
|
break; /* we found matching line, stop */ |
|
|
} /* end of "while line is read from /etc/mdev.conf" */ |
|
|
|
|
|
config_close(parser); |
|
|
#endif /* ENABLE_FEATURE_MDEV_CONF */ |
|
|
|
|
|
if (!delete && major >= 0) { |
|
|
|
|
|
if (ENABLE_FEATURE_MDEV_RENAME) |
|
|
unlink(device_name); |
|
321 |
|
|
322 |
if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) |
/* End of field parsing */ |
|
bb_perror_msg_and_die("mknod %s", device_name); |
|
323 |
|
|
324 |
if (major == root_major && minor == root_minor) |
/* "Execute" the line we found */ |
325 |
symlink(device_name, "root"); |
{ |
326 |
|
const char *node_name; |
327 |
|
|
328 |
#if ENABLE_FEATURE_MDEV_CONF |
node_name = device_name; |
329 |
chown(device_name, ugid.uid, ugid.gid); |
if (ENABLE_FEATURE_MDEV_RENAME && alias) |
330 |
|
node_name = alias = build_alias(alias, device_name); |
331 |
|
|
332 |
|
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 |
|
|
347 |
#if ENABLE_FEATURE_MDEV_RENAME |
if (ENABLE_FEATURE_MDEV_EXEC && command) { |
348 |
if (alias) { |
/* setenv will leak memory, use putenv/unsetenv/free */ |
349 |
alias = build_alias(alias, device_name); |
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 |
|
|
362 |
/* move the device, and optionally |
if (delete) { |
363 |
* make a symlink to moved device node */ |
if (ENABLE_FEATURE_MDEV_RENAME && alias) { |
364 |
if (rename(device_name, alias) == 0 && aliaslink == '>') |
if (aliaslink == '>') |
365 |
symlink(alias, device_name); |
unlink(device_name); |
366 |
|
} |
367 |
|
unlink(node_name); |
368 |
|
} |
369 |
|
|
370 |
free(alias); |
if (ENABLE_FEATURE_MDEV_RENAME) |
371 |
|
free(alias); |
372 |
} |
} |
|
#endif |
|
|
#endif |
|
|
} |
|
373 |
|
|
374 |
#if ENABLE_FEATURE_MDEV_EXEC |
/* We found matching line. |
375 |
if (command) { |
* Stop unless it was prefixed with '-' */ |
376 |
/* setenv will leak memory, use putenv/unsetenv/free */ |
if (ENABLE_FEATURE_MDEV_CONF && !keep_matching) |
377 |
char *s = xasprintf("MDEV=%s", device_name); |
break; |
|
putenv(s); |
|
|
if (system(command) == -1) |
|
|
bb_perror_msg_and_die("can't run '%s'", command); |
|
|
s[4] = '\0'; |
|
|
unsetenv(s); |
|
|
free(s); |
|
|
free(command); |
|
|
} |
|
|
#endif |
|
378 |
|
|
379 |
if (delete) { |
/* end of "while line is read from /etc/mdev.conf" */ |
380 |
unlink(device_name); |
} while (ENABLE_FEATURE_MDEV_CONF); |
381 |
/* At creation time, device might have been moved |
|
382 |
* and a symlink might have been created. Undo that. */ |
if (ENABLE_FEATURE_MDEV_CONF) |
383 |
#if ENABLE_FEATURE_MDEV_RENAME |
config_close(parser); |
|
if (alias) { |
|
|
alias = build_alias(alias, device_name); |
|
|
unlink(alias); |
|
|
free(alias); |
|
|
} |
|
|
#endif |
|
|
} |
|
384 |
} |
} |
385 |
|
|
386 |
/* File callback for /sys/ traversal */ |
/* File callback for /sys/ traversal */ |
409 |
void *userData UNUSED_PARAM, |
void *userData UNUSED_PARAM, |
410 |
int depth) |
int depth) |
411 |
{ |
{ |
412 |
|
/* 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 |
return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); |
return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); |
422 |
} |
} |
423 |
|
|
432 |
* - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading |
* - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading |
433 |
* - kernel loads firmware into device |
* - kernel loads firmware into device |
434 |
*/ |
*/ |
435 |
static void load_firmware(const char *const firmware, const char *const sysfs_path) |
static void load_firmware(const char *firmware, const char *sysfs_path) |
436 |
{ |
{ |
437 |
int cnt; |
int cnt; |
438 |
int firmware_fd, loading_fd, data_fd; |
int firmware_fd, loading_fd, data_fd; |
455 |
goto out; |
goto out; |
456 |
|
|
457 |
loading: |
loading: |
458 |
/* tell kernel we're loading by `echo 1 > /sys/$DEVPATH/loading` */ |
/* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */ |
459 |
if (full_write(loading_fd, "1", 1) != 1) |
if (full_write(loading_fd, "1", 1) != 1) |
460 |
goto out; |
goto out; |
461 |
|
|
462 |
/* load firmware by `cat /lib/firmware/$FIRMWARE > /sys/$DEVPATH/data */ |
/* load firmware into /sys/$DEVPATH/data */ |
463 |
data_fd = open("data", O_WRONLY); |
data_fd = open("data", O_WRONLY); |
464 |
if (data_fd == -1) |
if (data_fd == -1) |
465 |
goto out; |
goto out; |
466 |
cnt = bb_copyfd_eof(firmware_fd, data_fd); |
cnt = bb_copyfd_eof(firmware_fd, data_fd); |
467 |
|
|
468 |
/* tell kernel result by `echo [0|-1] > /sys/$DEVPATH/loading` */ |
/* tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading" */ |
469 |
if (cnt > 0) |
if (cnt > 0) |
470 |
full_write(loading_fd, "0", 1); |
full_write(loading_fd, "0", 1); |
471 |
else |
else |
486 |
|
|
487 |
/* We can be called as hotplug helper */ |
/* We can be called as hotplug helper */ |
488 |
/* Kernel cannot provide suitable stdio fds for us, do it ourself */ |
/* Kernel cannot provide suitable stdio fds for us, do it ourself */ |
|
#if 1 |
|
489 |
bb_sanitize_stdio(); |
bb_sanitize_stdio(); |
490 |
#else |
|
491 |
/* Debug code */ |
/* Force the configuration file settings exactly */ |
492 |
/* Replace LOGFILE by other file or device name if you need */ |
umask(0); |
|
#define LOGFILE "/dev/console" |
|
|
/* Just making sure fd 0 is not closed, |
|
|
* we don't really intend to read from it */ |
|
|
xmove_fd(xopen("/", O_RDONLY), STDIN_FILENO); |
|
|
xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDOUT_FILENO); |
|
|
xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDERR_FILENO); |
|
|
#endif |
|
493 |
|
|
494 |
xchdir("/dev"); |
xchdir("/dev"); |
495 |
|
|
496 |
if (argv[1] && !strcmp(argv[1], "-s")) { |
if (argv[1] && strcmp(argv[1], "-s") == 0) { |
497 |
/* Scan: |
/* Scan: |
498 |
* mdev -s |
* mdev -s |
499 |
*/ |
*/ |
507 |
* /sys/block/loop* (for example) are symlinks to dirs, |
* /sys/block/loop* (for example) are symlinks to dirs, |
508 |
* not real directories. |
* not real directories. |
509 |
* (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs, |
* (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs, |
510 |
* but we can't enforce that on users) */ |
* but we can't enforce that on users) |
511 |
recursive_action("/sys/block", |
*/ |
512 |
ACTION_RECURSE | ACTION_FOLLOWLINKS, |
if (access("/sys/class/block", F_OK) != 0) { |
513 |
fileAction, dirAction, temp, 0); |
/* 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 |
recursive_action("/sys/class", |
recursive_action("/sys/class", |
523 |
ACTION_RECURSE | ACTION_FOLLOWLINKS, |
ACTION_RECURSE | ACTION_FOLLOWLINKS, |
524 |
fileAction, dirAction, temp, 0); |
fileAction, dirAction, temp, 0); |
525 |
} else { |
} else { |
526 |
|
char *fw; |
527 |
char *seq; |
char *seq; |
528 |
char *action; |
char *action; |
529 |
char *env_path; |
char *env_path; |
530 |
char seqbuf[sizeof(int)*3 + 2]; |
static const char keywords[] ALIGN1 = "remove\0add\0"; |
531 |
int seqlen = seqlen; /* for compiler */ |
enum { OP_remove = 0, OP_add }; |
532 |
|
smalluint op; |
533 |
|
|
534 |
/* Hotplug: |
/* Hotplug: |
535 |
* env ACTION=... DEVPATH=... [SEQNUM=...] mdev |
* env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev |
536 |
* ACTION can be "add" or "remove" |
* ACTION can be "add" or "remove" |
537 |
* DEVPATH is like "/block/sda" or "/class/input/mice" |
* DEVPATH is like "/block/sda" or "/class/input/mice" |
538 |
*/ |
*/ |
539 |
action = getenv("ACTION"); |
action = getenv("ACTION"); |
540 |
env_path = getenv("DEVPATH"); |
env_path = getenv("DEVPATH"); |
541 |
if (!action || !env_path) |
subsystem = getenv("SUBSYSTEM"); |
542 |
|
if (!action || !env_path /*|| !subsystem*/) |
543 |
bb_show_usage(); |
bb_show_usage(); |
544 |
|
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 |
seq = getenv("SEQNUM"); |
seq = getenv("SEQNUM"); |
550 |
if (seq) { |
if (seq) { |
551 |
int timeout = 2000 / 32; |
int timeout = 2000 / 32; /* 2000 msec */ |
552 |
do { |
do { |
553 |
|
int seqlen; |
554 |
|
char seqbuf[sizeof(int)*3 + 2]; |
555 |
|
|
556 |
seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1)); |
seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1)); |
557 |
if (seqlen < 0) |
if (seqlen < 0) { |
558 |
|
seq = NULL; |
559 |
break; |
break; |
560 |
|
} |
561 |
seqbuf[seqlen] = '\0'; |
seqbuf[seqlen] = '\0'; |
562 |
if (seqbuf[0] == '\n' /* seed file? */ |
if (seqbuf[0] == '\n' /* seed file? */ |
563 |
|| strcmp(seq, seqbuf) == 0 /* correct idx? */ |
|| strcmp(seq, seqbuf) == 0 /* correct idx? */ |
569 |
} |
} |
570 |
|
|
571 |
snprintf(temp, PATH_MAX, "/sys%s", env_path); |
snprintf(temp, PATH_MAX, "/sys%s", env_path); |
572 |
if (!strcmp(action, "remove")) |
if (op == OP_remove) { |
573 |
make_device(temp, 1); |
/* Ignoring "remove firmware". It was reported |
574 |
else if (!strcmp(action, "add")) { |
* 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 |
make_device(temp, 0); |
make_device(temp, 0); |
|
|
|
581 |
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { |
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { |
|
char *fw = getenv("FIRMWARE"); |
|
582 |
if (fw) |
if (fw) |
583 |
load_firmware(fw, temp); |
load_firmware(fw, temp); |
584 |
} |
} |
585 |
} |
} |
586 |
|
|
587 |
if (seq && seqlen >= 0) { |
if (seq) { |
588 |
xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1)); |
xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1)); |
589 |
} |
} |
590 |
} |
} |
592 |
if (ENABLE_FEATURE_CLEAN_UP) |
if (ENABLE_FEATURE_CLEAN_UP) |
593 |
RELEASE_CONFIG_BUFFER(temp); |
RELEASE_CONFIG_BUFFER(temp); |
594 |
|
|
595 |
return 0; |
return EXIT_SUCCESS; |
596 |
} |
} |