9 |
* 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. |
10 |
*/ |
*/ |
11 |
|
|
12 |
#include "busybox.h" |
#include "libbb.h" |
13 |
#include "xregex.h" |
#include "xregex.h" |
14 |
|
|
15 |
#define DEV_PATH "/dev" |
struct globals { |
|
|
|
|
struct mdev_globals |
|
|
{ |
|
16 |
int root_major, root_minor; |
int root_major, root_minor; |
17 |
} mdev_globals; |
}; |
18 |
|
#define G (*(struct globals*)&bb_common_bufsiz1) |
19 |
|
#define root_major (G.root_major) |
20 |
|
#define root_minor (G.root_minor) |
21 |
|
|
22 |
|
/* Prevent infinite loops in /sys symlinks */ |
23 |
|
#define MAX_SYSFS_DEPTH 3 |
24 |
|
|
25 |
|
/* We use additional 64+ bytes in make_device() */ |
26 |
|
#define SCRATCH_SIZE 80 |
27 |
|
|
28 |
|
#if ENABLE_FEATURE_MDEV_RENAME |
29 |
|
/* Builds an alias path. |
30 |
|
* This function potentionally reallocates the alias parameter. |
31 |
|
*/ |
32 |
|
static char *build_alias(char *alias, const char *device_name) |
33 |
|
{ |
34 |
|
char *dest; |
35 |
|
|
36 |
#define bbg mdev_globals |
/* ">bar/": rename to bar/device_name */ |
37 |
|
/* ">bar[/]baz": rename to bar[/]baz */ |
38 |
|
dest = strrchr(alias, '/'); |
39 |
|
if (dest) { /* ">bar/[baz]" ? */ |
40 |
|
*dest = '\0'; /* mkdir bar */ |
41 |
|
bb_make_directory(alias, 0755, FILEUTILS_RECUR); |
42 |
|
*dest = '/'; |
43 |
|
if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */ |
44 |
|
dest = alias; |
45 |
|
alias = concat_path_file(alias, device_name); |
46 |
|
free(dest); |
47 |
|
} |
48 |
|
} |
49 |
|
|
50 |
|
return alias; |
51 |
|
} |
52 |
|
#endif |
53 |
|
|
54 |
/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ |
/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ |
55 |
|
/* NB: "mdev -s" may call us many times, do not leak memory/fds! */ |
56 |
static void make_device(char *path, int delete) |
static void make_device(char *path, int delete) |
57 |
{ |
{ |
58 |
char *device_name; |
const char *device_name; |
59 |
int major, minor, type, len; |
int major, minor, type, len; |
60 |
int mode = 0660; |
int mode = 0660; |
61 |
uid_t uid = 0; |
#if ENABLE_FEATURE_MDEV_CONF |
62 |
gid_t gid = 0; |
struct bb_uidgid_t ugid = { 0, 0 }; |
63 |
char *temp = path + strlen(path); |
parser_t *parser; |
64 |
|
char *tokens[5]; |
65 |
|
#endif |
66 |
|
#if ENABLE_FEATURE_MDEV_EXEC |
67 |
char *command = NULL; |
char *command = NULL; |
68 |
|
#endif |
69 |
|
#if ENABLE_FEATURE_MDEV_RENAME |
70 |
|
char *alias = NULL; |
71 |
|
char aliaslink = aliaslink; /* for compiler */ |
72 |
|
#endif |
73 |
|
char *dev_maj_min = path + strlen(path); |
74 |
|
|
75 |
|
/* Force the configuration file settings exactly. */ |
76 |
|
umask(0); |
77 |
|
|
78 |
/* 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 |
79 |
* 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 |
80 |
* because sscanf() will stop at the first nondigit, which \n is. We |
* because sscanf() will stop at the first nondigit, which \n is. |
81 |
* also depend on path having writeable space after it. */ |
* We also depend on path having writeable space after it. |
82 |
|
*/ |
83 |
|
major = -1; |
84 |
if (!delete) { |
if (!delete) { |
85 |
strcat(path, "/dev"); |
strcpy(dev_maj_min, "/dev"); |
86 |
len = open_read_close(path, temp + 1, 64); |
len = open_read_close(path, dev_maj_min + 1, 64); |
87 |
*temp++ = 0; |
*dev_maj_min++ = '\0'; |
88 |
if (len < 1) return; |
if (len < 1) { |
89 |
|
if (!ENABLE_FEATURE_MDEV_EXEC) |
90 |
|
return; |
91 |
|
/* no "dev" file, so just try to run script */ |
92 |
|
*dev_maj_min = '\0'; |
93 |
|
} else if (sscanf(dev_maj_min, "%u:%u", &major, &minor) != 2) { |
94 |
|
major = -1; |
95 |
|
} |
96 |
} |
} |
97 |
|
|
98 |
/* Determine device name, type, major and minor */ |
/* Determine device name, type, major and minor */ |
99 |
|
device_name = bb_basename(path); |
100 |
|
/* http://kernel.org/doc/pending/hotplug.txt says that only |
101 |
|
* "/sys/block/..." is for block devices. "/sys/bus" etc is not. |
102 |
|
* But since 2.6.25 block devices are also in /sys/class/block. |
103 |
|
* We use strstr("/block/") to forestall future surprises. */ |
104 |
|
type = S_IFCHR; |
105 |
|
if (strstr(path, "/block/")) |
106 |
|
type = S_IFBLK; |
107 |
|
|
108 |
|
#if ENABLE_FEATURE_MDEV_CONF |
109 |
|
parser = config_open2("/etc/mdev.conf", fopen_for_read); |
110 |
|
|
111 |
|
/* If we have config file, look up user settings */ |
112 |
|
while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) { |
113 |
|
regmatch_t off[1 + 9*ENABLE_FEATURE_MDEV_RENAME_REGEXP]; |
114 |
|
char *val; |
115 |
|
|
116 |
|
/* Fields: regex uid:gid mode [alias] [cmd] */ |
117 |
|
|
118 |
|
/* 1st field: @<numeric maj,min>... */ |
119 |
|
if (tokens[0][0] == '@') { |
120 |
|
/* @major,minor[-last] */ |
121 |
|
/* (useful when name is ambiguous: |
122 |
|
* "/sys/class/usb/lp0" and |
123 |
|
* "/sys/class/printer/lp0") */ |
124 |
|
int cmaj, cmin0, cmin1, sc; |
125 |
|
if (major < 0) |
126 |
|
continue; /* no dev, no match */ |
127 |
|
sc = sscanf(tokens[0], "@%u,%u-%u", &cmaj, &cmin0, &cmin1); |
128 |
|
if (sc < 1 || major != cmaj |
129 |
|
|| (sc == 2 && minor != cmin0) |
130 |
|
|| (sc == 3 && (minor < cmin0 || minor > cmin1)) |
131 |
|
) { |
132 |
|
continue; /* no match */ |
133 |
|
} |
134 |
|
} else { /* ... or regex to match device name */ |
135 |
|
regex_t match; |
136 |
|
int result; |
137 |
|
|
138 |
|
/* Is this it? */ |
139 |
|
xregcomp(&match, tokens[0], REG_EXTENDED); |
140 |
|
result = regexec(&match, device_name, ARRAY_SIZE(off), off, 0); |
141 |
|
regfree(&match); |
142 |
|
|
143 |
|
//bb_error_msg("matches:"); |
144 |
|
//for (int i = 0; i < ARRAY_SIZE(off); i++) { |
145 |
|
// if (off[i].rm_so < 0) continue; |
146 |
|
// bb_error_msg("match %d: '%.*s'\n", i, |
147 |
|
// (int)(off[i].rm_eo - off[i].rm_so), |
148 |
|
// device_name + off[i].rm_so); |
149 |
|
//} |
150 |
|
|
151 |
|
/* If not this device, skip rest of line */ |
152 |
|
/* (regexec returns whole pattern as "range" 0) */ |
153 |
|
if (result || off[0].rm_so |
154 |
|
|| ((int)off[0].rm_eo != (int)strlen(device_name)) |
155 |
|
) { |
156 |
|
continue; |
157 |
|
} |
158 |
|
} |
159 |
|
|
160 |
device_name = strrchr(path, '/') + 1; |
/* This line matches: stop parsing the file |
161 |
type = path[5]=='c' ? S_IFCHR : S_IFBLK; |
* after parsing the rest of fields */ |
|
|
|
|
/* If we have a config file, look up permissions for this device */ |
|
|
|
|
|
if (ENABLE_FEATURE_MDEV_CONF) { |
|
|
char *conf, *pos, *end; |
|
|
int line, fd; |
|
|
|
|
|
/* mmap the config file */ |
|
|
fd = open("/etc/mdev.conf", O_RDONLY); |
|
|
if (fd < 0) |
|
|
goto end_parse; |
|
|
len = xlseek(fd, 0, SEEK_END); |
|
|
conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); |
|
|
close(fd); |
|
|
if (!conf) |
|
|
goto end_parse; |
|
|
|
|
|
line = 0; |
|
|
/* Loop through lines in mmaped file*/ |
|
|
for (pos=conf; pos-conf<len;) { |
|
|
int field; |
|
|
char *end2; |
|
|
|
|
|
line++; |
|
|
/* find end of this line */ |
|
|
for (end=pos; end-conf<len && *end!='\n'; end++) |
|
|
; |
|
|
|
|
|
/* Three fields: regex, uid:gid, mode */ |
|
|
for (field=0; field < (3 + ENABLE_FEATURE_MDEV_EXEC); |
|
|
field++) |
|
|
{ |
|
|
/* Skip whitespace */ |
|
|
while (pos<end && isspace(*pos)) pos++; |
|
|
if (pos==end || *pos=='#') break; |
|
|
for (end2=pos; |
|
|
end2<end && !isspace(*end2) && *end2!='#'; end2++) |
|
|
; |
|
|
|
|
|
if (field == 0) { |
|
|
/* Regex to match this device */ |
|
|
|
|
|
char *regex = strndupa(pos, end2-pos); |
|
|
regex_t match; |
|
|
regmatch_t off; |
|
|
int result; |
|
|
|
|
|
/* Is this it? */ |
|
|
xregcomp(&match,regex, REG_EXTENDED); |
|
|
result = regexec(&match, device_name, 1, &off, 0); |
|
|
regfree(&match); |
|
|
|
|
|
/* If not this device, skip rest of line */ |
|
|
if (result || off.rm_so |
|
|
|| off.rm_eo != strlen(device_name)) |
|
|
break; |
|
|
} |
|
|
if (field == 1) { |
|
|
/* uid:gid */ |
|
162 |
|
|
163 |
char *s, *s2; |
/* 2nd field: uid:gid - device ownership */ |
164 |
|
parse_chown_usergroup_or_die(&ugid, tokens[1]); |
165 |
|
|
166 |
/* Find : */ |
/* 3rd field: mode - device permissions */ |
167 |
for (s=pos; s<end2 && *s!=':'; s++) |
mode = strtoul(tokens[2], NULL, 8); |
|
; |
|
|
if (s == end2) break; |
|
|
|
|
|
/* Parse UID */ |
|
|
uid = strtoul(pos, &s2, 10); |
|
|
if (s != s2) { |
|
|
struct passwd *pass; |
|
|
pass = getpwnam(strndupa(pos, s-pos)); |
|
|
if (!pass) break; |
|
|
uid = pass->pw_uid; |
|
|
} |
|
|
s++; |
|
|
/* parse GID */ |
|
|
gid = strtoul(s, &s2, 10); |
|
|
if (end2 != s2) { |
|
|
struct group *grp; |
|
|
grp = getgrnam(strndupa(s, end2-s)); |
|
|
if (!grp) break; |
|
|
gid = grp->gr_gid; |
|
|
} |
|
|
} |
|
|
if (field == 2) { |
|
|
/* mode */ |
|
168 |
|
|
169 |
mode = strtoul(pos, &pos, 8); |
val = tokens[3]; |
170 |
if (pos != end2) break; |
/* 4th field (opt): >alias */ |
171 |
} |
#if ENABLE_FEATURE_MDEV_RENAME |
172 |
if (ENABLE_FEATURE_MDEV_EXEC && field == 3) { |
if (!val) |
173 |
// Command to run |
break; |
174 |
char *s = "@$*", *s2; |
aliaslink = *val; |
175 |
s2 = strchr(s, *pos++); |
if (aliaslink == '>' || aliaslink == '=') { |
176 |
if (!s2) { |
char *s; |
177 |
// Force error |
#if ENABLE_FEATURE_MDEV_RENAME_REGEXP |
178 |
field = 1; |
char *p; |
179 |
break; |
unsigned i, n; |
180 |
|
#endif |
181 |
|
char *a = val; |
182 |
|
s = strchrnul(val, ' '); |
183 |
|
val = (s[0] && s[1]) ? s+1 : NULL; |
184 |
|
s[0] = '\0'; |
185 |
|
#if ENABLE_FEATURE_MDEV_RENAME_REGEXP |
186 |
|
/* substitute %1..9 with off[1..9], if any */ |
187 |
|
n = 0; |
188 |
|
s = a; |
189 |
|
while (*s) |
190 |
|
if (*s++ == '%') |
191 |
|
n++; |
192 |
|
|
193 |
|
p = alias = xzalloc(strlen(a) + n * strlen(device_name)); |
194 |
|
s = a + 1; |
195 |
|
while (*s) { |
196 |
|
*p = *s; |
197 |
|
if ('%' == *s) { |
198 |
|
i = (s[1] - '0'); |
199 |
|
if (i <= 9 && off[i].rm_so >= 0) { |
200 |
|
n = off[i].rm_eo - off[i].rm_so; |
201 |
|
strncpy(p, device_name + off[i].rm_so, n); |
202 |
|
p += n - 1; |
203 |
|
s++; |
204 |
} |
} |
|
if ((s2-s+1) & (1<<delete)) |
|
|
command = xstrndup(pos, end-pos); |
|
205 |
} |
} |
206 |
|
p++; |
207 |
|
s++; |
208 |
|
} |
209 |
|
#else |
210 |
|
alias = xstrdup(a + 1); |
211 |
|
#endif |
212 |
|
} |
213 |
|
#endif /* ENABLE_FEATURE_MDEV_RENAME */ |
214 |
|
|
215 |
pos = end2; |
#if ENABLE_FEATURE_MDEV_EXEC |
216 |
|
/* The rest (opt): command to run */ |
217 |
|
if (!val) |
218 |
|
break; |
219 |
|
{ |
220 |
|
const char *s = "@$*"; |
221 |
|
const char *s2 = strchr(s, *val); |
222 |
|
|
223 |
|
if (!s2) |
224 |
|
bb_error_msg_and_die("bad line %u", parser->lineno); |
225 |
|
|
226 |
|
/* Correlate the position in the "@$*" with the delete |
227 |
|
* step so that we get the proper behavior: |
228 |
|
* @cmd: run on create |
229 |
|
* $cmd: run on delete |
230 |
|
* *cmd: run on both |
231 |
|
*/ |
232 |
|
if ((s2 - s + 1) /*1/2/3*/ & /*1/2*/ (1 + delete)) { |
233 |
|
command = xstrdup(val + 1); |
234 |
} |
} |
235 |
|
} |
236 |
|
#endif |
237 |
|
/* end of field parsing */ |
238 |
|
break; /* we found matching line, stop */ |
239 |
|
} /* end of "while line is read from /etc/mdev.conf" */ |
240 |
|
|
241 |
/* Did everything parse happily? */ |
config_close(parser); |
242 |
|
#endif /* ENABLE_FEATURE_MDEV_CONF */ |
243 |
|
|
244 |
if (field > 2) break; |
if (!delete && major >= 0) { |
|
if (field) bb_error_msg_and_die("bad line %d",line); |
|
245 |
|
|
246 |
/* Next line */ |
if (ENABLE_FEATURE_MDEV_RENAME) |
247 |
pos = ++end; |
unlink(device_name); |
|
} |
|
|
munmap(conf, len); |
|
|
end_parse: /* nothing */ ; |
|
|
} |
|
248 |
|
|
|
umask(0); |
|
|
if (!delete) { |
|
|
if (sscanf(temp, "%d:%d", &major, &minor) != 2) return; |
|
249 |
if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) |
if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) |
250 |
bb_perror_msg_and_die("mknod %s", device_name); |
bb_perror_msg_and_die("mknod %s", device_name); |
251 |
|
|
252 |
if (major == bbg.root_major && minor == bbg.root_minor) |
if (major == root_major && minor == root_minor) |
253 |
symlink(device_name, "root"); |
symlink(device_name, "root"); |
254 |
|
|
255 |
if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); |
#if ENABLE_FEATURE_MDEV_CONF |
256 |
|
chown(device_name, ugid.uid, ugid.gid); |
257 |
|
|
258 |
|
#if ENABLE_FEATURE_MDEV_RENAME |
259 |
|
if (alias) { |
260 |
|
alias = build_alias(alias, device_name); |
261 |
|
|
262 |
|
/* move the device, and optionally |
263 |
|
* make a symlink to moved device node */ |
264 |
|
if (rename(device_name, alias) == 0 && aliaslink == '>') |
265 |
|
symlink(alias, device_name); |
266 |
|
|
267 |
|
free(alias); |
268 |
|
} |
269 |
|
#endif |
270 |
|
#endif |
271 |
} |
} |
|
if (command) { |
|
|
int rc; |
|
|
char *s; |
|
272 |
|
|
273 |
s = xasprintf("MDEV=%s", device_name); |
#if ENABLE_FEATURE_MDEV_EXEC |
274 |
putenv(s); |
if (command) { |
275 |
rc = system(command); |
/* setenv will leak memory, use putenv/unsetenv/free */ |
276 |
s[4] = 0; |
char *s = xasprintf("MDEV=%s", device_name); |
277 |
putenv(s); |
putenv(s); |
278 |
|
if (system(command) == -1) |
279 |
|
bb_perror_msg_and_die("can't run '%s'", command); |
280 |
|
s[4] = '\0'; |
281 |
|
unsetenv(s); |
282 |
free(s); |
free(s); |
283 |
free(command); |
free(command); |
|
if (rc == -1) bb_perror_msg_and_die("cannot run %s", command); |
|
284 |
} |
} |
285 |
if (delete) unlink(device_name); |
#endif |
|
} |
|
286 |
|
|
287 |
/* Recursive search of /sys/block or /sys/class. path must be a writeable |
if (delete) { |
288 |
* buffer of size PATH_MAX containing the directory string to start at. */ |
unlink(device_name); |
289 |
|
/* At creation time, device might have been moved |
290 |
|
* and a symlink might have been created. Undo that. */ |
291 |
|
#if ENABLE_FEATURE_MDEV_RENAME |
292 |
|
if (alias) { |
293 |
|
alias = build_alias(alias, device_name); |
294 |
|
unlink(alias); |
295 |
|
free(alias); |
296 |
|
} |
297 |
|
#endif |
298 |
|
} |
299 |
|
} |
300 |
|
|
301 |
static void find_dev(char *path) |
/* File callback for /sys/ traversal */ |
302 |
|
static int FAST_FUNC fileAction(const char *fileName, |
303 |
|
struct stat *statbuf UNUSED_PARAM, |
304 |
|
void *userData, |
305 |
|
int depth UNUSED_PARAM) |
306 |
{ |
{ |
307 |
DIR *dir; |
size_t len = strlen(fileName) - 4; /* can't underflow */ |
308 |
size_t len = strlen(path); |
char *scratch = userData; |
|
struct dirent *entry; |
|
|
|
|
|
dir = opendir(path); |
|
|
if (dir == NULL) |
|
|
return; |
|
|
|
|
|
while ((entry = readdir(dir)) != NULL) { |
|
|
struct stat st; |
|
|
|
|
|
/* Skip "." and ".." (also skips hidden files, which is ok) */ |
|
309 |
|
|
310 |
if (entry->d_name[0] == '.') |
/* len check is for paranoid reasons */ |
311 |
continue; |
if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX) |
312 |
|
return FALSE; |
313 |
|
|
314 |
|
strcpy(scratch, fileName); |
315 |
|
scratch[len] = '\0'; |
316 |
|
make_device(scratch, 0); |
317 |
|
|
318 |
// uClibc doesn't fill out entry->d_type reliably. so we use lstat(). |
return TRUE; |
319 |
|
} |
320 |
|
|
321 |
snprintf(path+len, PATH_MAX-len, "/%s", entry->d_name); |
/* Directory callback for /sys/ traversal */ |
322 |
if (!lstat(path, &st) && S_ISDIR(st.st_mode)) find_dev(path); |
static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM, |
323 |
path[len] = 0; |
struct stat *statbuf UNUSED_PARAM, |
324 |
|
void *userData UNUSED_PARAM, |
325 |
|
int depth) |
326 |
|
{ |
327 |
|
return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE); |
328 |
|
} |
329 |
|
|
330 |
/* If there's a dev entry, mknod it */ |
/* For the full gory details, see linux/Documentation/firmware_class/README |
331 |
|
* |
332 |
|
* Firmware loading works like this: |
333 |
|
* - kernel sets FIRMWARE env var |
334 |
|
* - userspace checks /lib/firmware/$FIRMWARE |
335 |
|
* - userspace waits for /sys/$DEVPATH/loading to appear |
336 |
|
* - userspace writes "1" to /sys/$DEVPATH/loading |
337 |
|
* - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data |
338 |
|
* - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading |
339 |
|
* - kernel loads firmware into device |
340 |
|
*/ |
341 |
|
static void load_firmware(const char *const firmware, const char *const sysfs_path) |
342 |
|
{ |
343 |
|
int cnt; |
344 |
|
int firmware_fd, loading_fd, data_fd; |
345 |
|
|
346 |
if (!strcmp(entry->d_name, "dev")) make_device(path, 0); |
/* check for /lib/firmware/$FIRMWARE */ |
347 |
|
xchdir("/lib/firmware"); |
348 |
|
firmware_fd = xopen(firmware, O_RDONLY); |
349 |
|
|
350 |
|
/* in case we goto out ... */ |
351 |
|
data_fd = -1; |
352 |
|
|
353 |
|
/* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */ |
354 |
|
xchdir(sysfs_path); |
355 |
|
for (cnt = 0; cnt < 30; ++cnt) { |
356 |
|
loading_fd = open("loading", O_WRONLY); |
357 |
|
if (loading_fd != -1) |
358 |
|
goto loading; |
359 |
|
sleep(1); |
360 |
} |
} |
361 |
|
goto out; |
362 |
|
|
363 |
closedir(dir); |
loading: |
364 |
|
/* tell kernel we're loading by `echo 1 > /sys/$DEVPATH/loading` */ |
365 |
|
if (full_write(loading_fd, "1", 1) != 1) |
366 |
|
goto out; |
367 |
|
|
368 |
|
/* load firmware by `cat /lib/firmware/$FIRMWARE > /sys/$DEVPATH/data */ |
369 |
|
data_fd = open("data", O_WRONLY); |
370 |
|
if (data_fd == -1) |
371 |
|
goto out; |
372 |
|
cnt = bb_copyfd_eof(firmware_fd, data_fd); |
373 |
|
|
374 |
|
/* tell kernel result by `echo [0|-1] > /sys/$DEVPATH/loading` */ |
375 |
|
if (cnt > 0) |
376 |
|
full_write(loading_fd, "0", 1); |
377 |
|
else |
378 |
|
full_write(loading_fd, "-1", 2); |
379 |
|
|
380 |
|
out: |
381 |
|
if (ENABLE_FEATURE_CLEAN_UP) { |
382 |
|
close(firmware_fd); |
383 |
|
close(loading_fd); |
384 |
|
close(data_fd); |
385 |
|
} |
386 |
} |
} |
387 |
|
|
388 |
int mdev_main(int argc, char *argv[]) |
int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
389 |
|
int mdev_main(int argc UNUSED_PARAM, char **argv) |
390 |
{ |
{ |
391 |
char *action; |
RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); |
|
char *env_path; |
|
|
RESERVE_CONFIG_BUFFER(temp,PATH_MAX); |
|
|
|
|
|
xchdir(DEV_PATH); |
|
392 |
|
|
393 |
/* Scan */ |
/* We can be called as hotplug helper */ |
394 |
|
/* Kernel cannot provide suitable stdio fds for us, do it ourself */ |
395 |
if (argc == 2 && !strcmp(argv[1],"-s")) { |
#if 1 |
396 |
|
bb_sanitize_stdio(); |
397 |
|
#else |
398 |
|
/* Debug code */ |
399 |
|
/* Replace LOGFILE by other file or device name if you need */ |
400 |
|
#define LOGFILE "/dev/console" |
401 |
|
/* Just making sure fd 0 is not closed, |
402 |
|
* we don't really intend to read from it */ |
403 |
|
xmove_fd(xopen("/", O_RDONLY), STDIN_FILENO); |
404 |
|
xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDOUT_FILENO); |
405 |
|
xmove_fd(xopen(LOGFILE, O_WRONLY|O_APPEND), STDERR_FILENO); |
406 |
|
#endif |
407 |
|
|
408 |
|
xchdir("/dev"); |
409 |
|
|
410 |
|
if (argv[1] && !strcmp(argv[1], "-s")) { |
411 |
|
/* Scan: |
412 |
|
* mdev -s |
413 |
|
*/ |
414 |
struct stat st; |
struct stat st; |
415 |
|
|
416 |
xstat("/", &st); |
xstat("/", &st); |
417 |
bbg.root_major = major(st.st_dev); |
root_major = major(st.st_dev); |
418 |
bbg.root_minor = minor(st.st_dev); |
root_minor = minor(st.st_dev); |
|
strcpy(temp,"/sys/block"); |
|
|
find_dev(temp); |
|
|
strcpy(temp,"/sys/class"); |
|
|
find_dev(temp); |
|
|
|
|
|
/* Hotplug */ |
|
419 |
|
|
420 |
|
/* ACTION_FOLLOWLINKS is needed since in newer kernels |
421 |
|
* /sys/block/loop* (for example) are symlinks to dirs, |
422 |
|
* not real directories. |
423 |
|
* (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs, |
424 |
|
* but we can't enforce that on users) */ |
425 |
|
recursive_action("/sys/block", |
426 |
|
ACTION_RECURSE | ACTION_FOLLOWLINKS, |
427 |
|
fileAction, dirAction, temp, 0); |
428 |
|
recursive_action("/sys/class", |
429 |
|
ACTION_RECURSE | ACTION_FOLLOWLINKS, |
430 |
|
fileAction, dirAction, temp, 0); |
431 |
} else { |
} else { |
432 |
|
char *seq; |
433 |
|
char *action; |
434 |
|
char *env_path; |
435 |
|
char seqbuf[sizeof(int)*3 + 2]; |
436 |
|
int seqlen = seqlen; /* for compiler */ |
437 |
|
|
438 |
|
/* Hotplug: |
439 |
|
* env ACTION=... DEVPATH=... [SEQNUM=...] mdev |
440 |
|
* ACTION can be "add" or "remove" |
441 |
|
* DEVPATH is like "/block/sda" or "/class/input/mice" |
442 |
|
*/ |
443 |
action = getenv("ACTION"); |
action = getenv("ACTION"); |
444 |
env_path = getenv("DEVPATH"); |
env_path = getenv("DEVPATH"); |
445 |
if (!action || !env_path) |
if (!action || !env_path) |
446 |
bb_show_usage(); |
bb_show_usage(); |
447 |
|
|
448 |
sprintf(temp, "/sys%s", env_path); |
seq = getenv("SEQNUM"); |
449 |
if (!strcmp(action, "add")) make_device(temp,0); |
if (seq) { |
450 |
else if (!strcmp(action, "remove")) make_device(temp,1); |
int timeout = 2000 / 32; |
451 |
|
do { |
452 |
|
seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1)); |
453 |
|
if (seqlen < 0) |
454 |
|
break; |
455 |
|
seqbuf[seqlen] = '\0'; |
456 |
|
if (seqbuf[0] == '\n' /* seed file? */ |
457 |
|
|| strcmp(seq, seqbuf) == 0 /* correct idx? */ |
458 |
|
) { |
459 |
|
break; |
460 |
|
} |
461 |
|
usleep(32*1000); |
462 |
|
} while (--timeout); |
463 |
|
} |
464 |
|
|
465 |
|
snprintf(temp, PATH_MAX, "/sys%s", env_path); |
466 |
|
if (!strcmp(action, "remove")) |
467 |
|
make_device(temp, 1); |
468 |
|
else if (!strcmp(action, "add")) { |
469 |
|
make_device(temp, 0); |
470 |
|
|
471 |
|
if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) { |
472 |
|
char *fw = getenv("FIRMWARE"); |
473 |
|
if (fw) |
474 |
|
load_firmware(fw, temp); |
475 |
|
} |
476 |
|
} |
477 |
|
|
478 |
|
if (seq && seqlen >= 0) { |
479 |
|
xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1)); |
480 |
|
} |
481 |
} |
} |
482 |
|
|
483 |
if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); |
if (ENABLE_FEATURE_CLEAN_UP) |
484 |
|
RELEASE_CONFIG_BUFFER(temp); |
485 |
|
|
486 |
return 0; |
return 0; |
487 |
} |
} |