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