5a8f1d91867ec661cc0d4dd68e318cbe4f07863a
[oweals/busybox.git] / util-linux / mdev.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  *
4  * mdev - Mini udev for busybox
5  *
6  * Copyright 2005 Rob Landley <rob@landley.net>
7  * Copyright 2005 Frank Sorenson <frank@tuxrocks.com>
8  *
9  * Licensed under GPL version 2, see file LICENSE in this tarball for details.
10  */
11
12 #include "libbb.h"
13 #include "xregex.h"
14
15 struct globals {
16         int root_major, root_minor;
17 };
18 #define G (*(struct globals*)&bb_common_bufsiz1)
19 #define root_major (G.root_major)
20 #define root_minor (G.root_minor)
21
22 #define MAX_SYSFS_DEPTH 3 /* prevent infinite loops in /sys symlinks */
23
24 /* mknod in /dev based on a path like "/sys/block/hda/hda1" */
25 static void make_device(char *path, int delete)
26 {
27         const char *device_name;
28         int major, minor, type, len;
29         int mode = 0660;
30         uid_t uid = 0;
31         gid_t gid = 0;
32         char *temp = path + strlen(path);
33         char *command = NULL;
34         char *alias = NULL;
35
36         /* Force the configuration file settings exactly. */
37         umask(0);
38
39         /* Try to read major/minor string.  Note that the kernel puts \n after
40          * the data, so we don't need to worry about null terminating the string
41          * because sscanf() will stop at the first nondigit, which \n is.  We
42          * also depend on path having writeable space after it.
43          */
44         if (!delete) {
45                 strcat(path, "/dev");
46                 len = open_read_close(path, temp + 1, 64);
47                 *temp++ = 0;
48                 if (len < 1) {
49                         if (ENABLE_FEATURE_MDEV_EXEC)
50                                 /* no "dev" file, so just try to run script */
51                                 *temp = 0;
52                         else
53                                 return;
54                 }
55         }
56
57         /* Determine device name, type, major and minor */
58         device_name = bb_basename(path);
59         type = (path[5] == 'c' ? S_IFCHR : S_IFBLK);
60
61         if (ENABLE_FEATURE_MDEV_CONF) {
62                 FILE *fp;
63                 char *line, *vline;
64                 unsigned lineno = 0;
65
66                 /* If we have a config file, look up the user settings */
67                 fp = fopen_or_warn("/etc/mdev.conf", "r");
68                 if (!fp)
69                         goto end_parse;
70
71                 while ((vline = line = xmalloc_fgetline(fp)) != NULL) {
72                         int field;
73
74                         /* A pristine copy for command execution. */
75                         char *orig_line;
76                         if (ENABLE_FEATURE_MDEV_EXEC)
77                                 orig_line = xstrdup(line);
78
79                         ++lineno;
80
81                         /* Three fields: regex, uid:gid, mode */
82                         for (field = 0; field < (3 + ENABLE_FEATURE_MDEV_RENAME + ENABLE_FEATURE_MDEV_EXEC); ++field) {
83
84                                 /* Find a non-empty field */
85                                 char *val;
86                                 do {
87                                         val = strtok(vline, " \t");
88                                         vline = NULL;
89                                 } while (val && !*val);
90                                 if (!val) {
91                                         if (field)
92                                                 break;
93                                         else
94                                                 goto next_line;
95                                 }
96
97                                 if (field == 0) {
98
99                                         /* Regex to match this device */
100                                         regex_t match;
101                                         regmatch_t off;
102                                         int result;
103
104                                         /* Is this it? */
105                                         xregcomp(&match, val, REG_EXTENDED);
106                                         result = regexec(&match, device_name, 1, &off, 0);
107                                         regfree(&match);
108
109                                         /* If not this device, skip rest of line */
110                                         if (result || off.rm_so || off.rm_eo != strlen(device_name))
111                                                 goto next_line;
112
113                                 } else if (field == 1) {
114
115                                         /* uid:gid device ownership */
116                                         struct passwd *pass;
117                                         struct group *grp;
118
119                                         char *str_uid = val;
120                                         char *str_gid = strchr(val, ':');
121                                         if (str_gid)
122                                                 *str_gid = '\0', ++str_gid;
123
124                                         /* Parse UID */
125                                         pass = getpwnam(str_uid);
126                                         if (pass)
127                                                 uid = pass->pw_uid;
128                                         else
129                                                 uid = strtoul(str_uid, NULL, 10);
130
131                                         /* parse GID */
132                                         grp = getgrnam(str_gid);
133                                         if (grp)
134                                                 gid = grp->gr_gid;
135                                         else
136                                                 gid = strtoul(str_gid, NULL, 10);
137
138                                 } else if (field == 2) {
139
140                                         /* Mode device permissions */
141                                         mode = strtoul(val, NULL, 8);
142
143                                 } else if (ENABLE_FEATURE_MDEV_RENAME && field == 3) {
144
145                                         if (*val != '>')
146                                                 ++field;
147                                         else
148                                                 alias = xstrdup(val + 1);
149
150                                 }
151
152                                 if (ENABLE_FEATURE_MDEV_EXEC && field == 3 + ENABLE_FEATURE_MDEV_RENAME) {
153
154                                         /* Optional command to run */
155                                         const char *s = "@$*";
156                                         const char *s2 = strchr(s, *val);
157
158                                         if (!s2) {
159                                                 /* Force error */
160                                                 field = 1;
161                                                 break;
162                                         }
163
164                                         /* Correlate the position in the "@$*" with the delete
165                                          * step so that we get the proper behavior.
166                                          */
167                                         if ((s2 - s + 1) & (1 << delete))
168                                                 command = xstrdup(orig_line + (val + 1 - line));
169                                 }
170                         }
171
172                         /* Did everything parse happily? */
173                         if (field <= 2)
174                                 bb_error_msg_and_die("bad line %u", lineno);
175
176  next_line:
177                         free(line);
178                         if (ENABLE_FEATURE_MDEV_EXEC)
179                                 free(orig_line);
180                 }
181
182                 if (ENABLE_FEATURE_CLEAN_UP)
183                         fclose(fp);
184
185  end_parse:     /* nothing */ ;
186         }
187
188         if (!delete) {
189                 if (sscanf(temp, "%d:%d", &major, &minor) != 2) {
190                         if (ENABLE_FEATURE_MDEV_EXEC)
191                                 goto skip_creation;
192                         else
193                                 return;
194                 }
195
196                 if (ENABLE_FEATURE_MDEV_RENAME)
197                         unlink(device_name);
198
199                 if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
200                         bb_perror_msg_and_die("mknod %s", device_name);
201
202                 if (major == root_major && minor == root_minor)
203                         symlink(device_name, "root");
204
205                 if (ENABLE_FEATURE_MDEV_CONF) {
206                         chown(device_name, uid, gid);
207
208                         if (ENABLE_FEATURE_MDEV_RENAME && alias) {
209                                 char *dest;
210
211                                 temp = strrchr(alias, '/');
212                                 if (temp) {
213                                         if (temp[1] != '\0')
214                                                 /* given a file name, so rename it */
215                                                 *temp = '\0';
216                                         bb_make_directory(alias, 0755, FILEUTILS_RECUR);
217                                         dest = concat_path_file(alias, device_name);
218                                 } else
219                                         dest = alias;
220
221                                 rename(device_name, dest); // TODO: xrename?
222                                 symlink(dest, device_name);
223
224                                 if (alias != dest)
225                                         free(alias);
226                                 free(dest);
227                         }
228                 }
229  skip_creation: /* nothing */ ;
230         }
231         if (ENABLE_FEATURE_MDEV_EXEC && command) {
232                 /* setenv will leak memory, so use putenv */
233                 char *s = xasprintf("MDEV=%s", device_name);
234                 putenv(s);
235                 if (system(command) == -1)
236                         bb_perror_msg_and_die("cannot run %s", command);
237                 s[4] = '\0';
238                 unsetenv(s);
239                 free(s);
240                 free(command);
241         }
242         if (delete)
243                 remove_file(device_name, FILEUTILS_FORCE);
244 }
245
246 /* File callback for /sys/ traversal */
247 static int fileAction(const char *fileName,
248                       struct stat *statbuf ATTRIBUTE_UNUSED,
249                       void *userData,
250                       int depth ATTRIBUTE_UNUSED)
251 {
252         size_t len = strlen(fileName) - 4;
253         char *scratch = userData;
254
255         if (strcmp(fileName + len, "/dev"))
256                 return FALSE;
257
258         strcpy(scratch, fileName);
259         scratch[len] = 0;
260         make_device(scratch, 0);
261
262         return TRUE;
263 }
264
265 /* Directory callback for /sys/ traversal */
266 static int dirAction(const char *fileName ATTRIBUTE_UNUSED,
267                       struct stat *statbuf ATTRIBUTE_UNUSED,
268                       void *userData ATTRIBUTE_UNUSED,
269                       int depth)
270 {
271         return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
272 }
273
274 /* For the full gory details, see linux/Documentation/firmware_class/README
275  *
276  * Firmware loading works like this:
277  * - kernel sets FIRMWARE env var
278  * - userspace checks /lib/firmware/$FIRMWARE
279  * - userspace waits for /sys/$DEVPATH/loading to appear
280  * - userspace writes "1" to /sys/$DEVPATH/loading
281  * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data
282  * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading
283  * - kernel loads firmware into device
284  */
285 static void load_firmware(const char *const firmware, const char *const sysfs_path)
286 {
287         int cnt;
288         int firmware_fd, loading_fd, data_fd;
289
290         /* check for $FIRMWARE from kernel */
291         /* XXX: dont bother: open(NULL) works same as open("no-such-file")
292          * if (!firmware)
293          *      return;
294          */
295
296         /* check for /lib/firmware/$FIRMWARE */
297         xchdir("/lib/firmware");
298         firmware_fd = xopen(firmware, O_RDONLY);
299
300         /* in case we goto out ... */
301         data_fd = -1;
302
303         /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */
304         xchdir(sysfs_path);
305         for (cnt = 0; cnt < 30; ++cnt) {
306                 loading_fd = open("loading", O_WRONLY);
307                 if (loading_fd == -1)
308                         sleep(1);
309                 else
310                         break;
311         }
312         if (loading_fd == -1)
313                 goto out;
314
315         /* tell kernel we're loading by `echo 1 > /sys/$DEVPATH/loading` */
316         if (write(loading_fd, "1", 1) != 1)
317                 goto out;
318
319         /* load firmware by `cat /lib/firmware/$FIRMWARE > /sys/$DEVPATH/data */
320         data_fd = open("data", O_WRONLY);
321         if (data_fd == -1)
322                 goto out;
323         cnt = bb_copyfd_eof(firmware_fd, data_fd);
324
325         /* tell kernel result by `echo [0|-1] > /sys/$DEVPATH/loading` */
326         if (cnt > 0)
327                 write(loading_fd, "0", 1);
328         else
329                 write(loading_fd, "-1", 2);
330
331  out:
332         if (ENABLE_FEATURE_CLEAN_UP) {
333                 close(firmware_fd);
334                 close(loading_fd);
335                 close(data_fd);
336         }
337 }
338
339 int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
340 int mdev_main(int argc, char **argv)
341 {
342         char *action;
343         char *env_path;
344         RESERVE_CONFIG_BUFFER(temp,PATH_MAX);
345
346         xchdir("/dev");
347
348         if (argc == 2 && !strcmp(argv[1],"-s")) {
349
350                 /* Scan:
351                  * mdev -s
352                  */
353
354                 struct stat st;
355
356                 xstat("/", &st);
357                 root_major = major(st.st_dev);
358                 root_minor = minor(st.st_dev);
359
360                 recursive_action("/sys/block",
361                         ACTION_RECURSE | ACTION_FOLLOWLINKS,
362                         fileAction, dirAction, temp, 0);
363
364                 recursive_action("/sys/class",
365                         ACTION_RECURSE | ACTION_FOLLOWLINKS,
366                         fileAction, dirAction, temp, 0);
367
368         } else {
369
370                 /* Hotplug:
371                  * env ACTION=... DEVPATH=... mdev
372                  * ACTION can be "add" or "remove"
373                  * DEVPATH is like "/block/sda" or "/class/input/mice"
374                  */
375
376                 action = getenv("ACTION");
377                 env_path = getenv("DEVPATH");
378                 if (!action || !env_path)
379                         bb_show_usage();
380
381                 sprintf(temp, "/sys%s", env_path);
382                 if (!strcmp(action, "remove"))
383                         make_device(temp, 1);
384                 else if (!strcmp(action, "add")) {
385                         make_device(temp, 0);
386
387                         if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE)
388                                 load_firmware(getenv("FIRMWARE"), temp);
389                 }
390         }
391
392         if (ENABLE_FEATURE_CLEAN_UP)
393                 RELEASE_CONFIG_BUFFER(temp);
394
395         return 0;
396 }