update example udhcpc script
[oweals/busybox.git] / util-linux / mdev.c
index 956de15aef4fcf12c03f2dc80713c0f322bfd0cf..2451cca05d893505e451c0c41d18f213e6936813 100644 (file)
@@ -1,6 +1,5 @@
 /* vi: set sw=4 ts=4: */
 /*
- *
  * mdev - Mini udev for busybox
  *
  * Copyright 2005 Rob Landley <rob@landley.net>
@@ -8,16 +7,17 @@
  *
  * Licensed under GPL version 2, see file LICENSE in this tarball for details.
  */
-
 #include "libbb.h"
 #include "xregex.h"
 
 struct globals {
        int root_major, root_minor;
+       char *subsystem;
 };
 #define G (*(struct globals*)&bb_common_bufsiz1)
 #define root_major (G.root_major)
 #define root_minor (G.root_minor)
+#define subsystem (G.subsystem)
 
 /* Prevent infinite loops in /sys symlinks */
 #define MAX_SYSFS_DEPTH 3
@@ -55,21 +55,12 @@ static char *build_alias(char *alias, const char *device_name)
 /* NB: "mdev -s" may call us many times, do not leak memory/fds! */
 static void make_device(char *path, int delete)
 {
-       const char *device_name;
-       int major, minor, type, len;
-       int mode = 0660;
 #if ENABLE_FEATURE_MDEV_CONF
-       struct bb_uidgid_t ugid = { 0, 0 };
        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
+       const char *device_name;
+       int major, minor, type, len;
+       int mode;
        char *dev_maj_min = path + strlen(path);
 
        /* Force the configuration file settings exactly. */
@@ -105,18 +96,42 @@ static void make_device(char *path, int delete)
        if (strstr(path, "/block/"))
                type = S_IFBLK;
 
-#if ENABLE_FEATURE_MDEV_CONF
-       parser = config_open2("/etc/mdev.conf", fopen_for_read);
-
+#if !ENABLE_FEATURE_MDEV_CONF
+       mode = 0660;
+#else
        /* If we have config file, look up user settings */
-       while (config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) {
+       parser = config_open2("/etc/mdev.conf", fopen_for_read);
+       while (1) {
                regmatch_t off[1 + 9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
+               int keep_matching;
                char *val;
+               struct bb_uidgid_t ugid;
+               char *tokens[4];
+# if ENABLE_FEATURE_MDEV_EXEC
+               char *command = NULL;
+# endif
+# if ENABLE_FEATURE_MDEV_RENAME
+               char *alias = NULL;
+               char aliaslink = aliaslink; /* for compiler */
+# endif
+               /* Defaults in case we won't match any line */
+               ugid.uid = ugid.gid = 0;
+               keep_matching = 0;
+               mode = 0660;
+
+               if (!config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) {
+                       /* End of file, create dev node with default params */
+                       goto line_matches;
+               }
+
+               val = tokens[0];
+               keep_matching = ('-' == val[0]);
+               val += keep_matching; /* swallow leading dash */
 
                /* Fields: regex uid:gid mode [alias] [cmd] */
 
                /* 1st field: @<numeric maj,min>... */
-               if (tokens[0][0] == '@') {
+               if (val[0] == '@') {
                        /* @major,minor[-last] */
                        /* (useful when name is ambiguous:
                         * "/sys/class/usb/lp0" and
@@ -124,20 +139,25 @@ static void make_device(char *path, int delete)
                        int cmaj, cmin0, cmin1, sc;
                        if (major < 0)
                                continue; /* no dev, no match */
-                       sc = sscanf(tokens[0], "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
+                       sc = sscanf(val, "@%u,%u-%u", &cmaj, &cmin0, &cmin1);
                        if (sc < 1 || major != cmaj
                         || (sc == 2 && minor != cmin0)
                         || (sc == 3 && (minor < cmin0 || minor > cmin1))
                        ) {
-                               continue; /* no match */
+                               continue; /* this line doesn't match */
                        }
                } else { /* ... or regex to match device name */
                        regex_t match;
                        int result;
+                       const char *dev_name_or_subsystem = device_name;
+                       if ('/' == val[0] && subsystem) {
+                               dev_name_or_subsystem = subsystem;
+                               val++;
+                       }
 
                        /* Is this it? */
-                       xregcomp(&match, tokens[0], REG_EXTENDED);
-                       result = regexec(&match, device_name, ARRAY_SIZE(off), off, 0);
+                       xregcomp(&match, val, REG_EXTENDED);
+                       result = regexec(&match, dev_name_or_subsystem, ARRAY_SIZE(off), off, 0);
                        regfree(&match);
 
                        //bb_error_msg("matches:");
@@ -151,14 +171,14 @@ static void make_device(char *path, int delete)
                        /* 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))
+                        || ((int)off[0].rm_eo != (int)strlen(dev_name_or_subsystem))
                        ) {
-                               continue;
+                               continue; /* this line doesn't match */
                        }
                }
 
-               /* This line matches: stop parsing the file
-                * after parsing the rest of fields */
+               /* This line matches: stop parsing the file after parsing
+                * the rest of fields unless keep_matching == 1 */
 
                /* 2nd field: uid:gid - device ownership */
                parse_chown_usergroup_or_die(&ugid, tokens[1]);
@@ -167,21 +187,26 @@ static void make_device(char *path, int delete)
                mode = strtoul(tokens[2], NULL, 8);
 
                val = tokens[3];
-               /* 4th field (opt): >alias */
-#if ENABLE_FEATURE_MDEV_RENAME
+               /* 4th field (opt): >|=alias */
+# if ENABLE_FEATURE_MDEV_RENAME
                if (!val)
-                       break;
-               aliaslink = *val;
+                       goto line_matches;
+               aliaslink = val[0];
                if (aliaslink == '>' || aliaslink == '=') {
-                       char *s;
-#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+                       char *a, *s, *st;
+#  if ENABLE_FEATURE_MDEV_RENAME_REGEXP
                        char *p;
                        unsigned i, n;
-#endif
-                       char *a = val;
-                       s = strchr(val, ' ');
-                       val = (s && s[1]) ? s+1 : NULL;
-#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+#  endif
+                       a = val;
+                       s = strchrnul(val, ' ');
+                       st = strchrnul(val, '\t');
+                       if (st < s)
+                               s = st;
+                       val = (s[0] && s[1]) ? s+1 : NULL;
+                       s[0] = '\0';
+
+#  if ENABLE_FEATURE_MDEV_RENAME_REGEXP
                        /* substitute %1..9 with off[1..9], if any */
                        n = 0;
                        s = a;
@@ -205,19 +230,19 @@ static void make_device(char *path, int delete)
                                p++;
                                s++;
                        }
-#else
+#  else
                        alias = xstrdup(a + 1);
-#endif
+#  endif
                }
-#endif /* ENABLE_FEATURE_MDEV_RENAME */
+# endif /* ENABLE_FEATURE_MDEV_RENAME */
 
-#if ENABLE_FEATURE_MDEV_EXEC
-               /* The rest (opt): command to run */
+# if ENABLE_FEATURE_MDEV_EXEC
+               /* The rest (opt): @|$|*command */
                if (!val)
-                       break;
+                       goto line_matches;
                {
                        const char *s = "@$*";
-                       const char *s2 = strchr(s, *val);
+                       const char *s2 = strchr(s, val[0]);
 
                        if (!s2)
                                bb_error_msg_and_die("bad line %u", parser->lineno);
@@ -232,68 +257,72 @@ static void make_device(char *path, int delete)
                                command = xstrdup(val + 1);
                        }
                }
-#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
+               /* End of field parsing */
+ line_matches:
 #endif /* ENABLE_FEATURE_MDEV_CONF */
 
-       if (!delete && major >= 0) {
-
-               if (ENABLE_FEATURE_MDEV_RENAME)
-                       unlink(device_name);
-
-               if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
-                       bb_perror_msg_and_die("mknod %s", device_name);
-
-               if (major == root_major && minor == root_minor)
-                       symlink(device_name, "root");
+               /* "Execute" the line we found */
 
+               if (!delete && major >= 0) {
+                       if (ENABLE_FEATURE_MDEV_RENAME)
+                               unlink(device_name);
+                       if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
+                               bb_perror_msg_and_die("mknod %s", device_name);
+                       if (major == root_major && minor == root_minor)
+                               symlink(device_name, "root");
 #if ENABLE_FEATURE_MDEV_CONF
-               chown(device_name, ugid.uid, ugid.gid);
-
-#if ENABLE_FEATURE_MDEV_RENAME
-               if (alias) {
-                       alias = build_alias(alias, device_name);
-
-                       /* move the device, and optionally
-                        * make a symlink to moved device node */
-                       if (rename(device_name, alias) == 0 && aliaslink == '>')
-                               symlink(alias, device_name);
-
-                       free(alias);
-               }
-#endif
+                       chown(device_name, ugid.uid, ugid.gid);
+# if ENABLE_FEATURE_MDEV_RENAME
+                       if (alias) {
+                               alias = build_alias(alias, device_name);
+                               /* move the device, and optionally
+                                * make a symlink to moved device node */
+                               if (rename(device_name, alias) == 0 && aliaslink == '>')
+                                       symlink(alias, device_name);
+                               free(alias);
+                       }
+# endif
 #endif
-       }
-
+               }
 #if ENABLE_FEATURE_MDEV_EXEC
-       if (command) {
-               /* setenv will leak memory, use putenv/unsetenv/free */
-               char *s = xasprintf("MDEV=%s", device_name);
-               putenv(s);
-               if (system(command) == -1)
-                       bb_perror_msg_and_die("can't run '%s'", command);
-               unsetenv("MDEV");
-               free(s);
-               free(command);
-       }
+               if (command) {
+                       /* setenv will leak memory, use putenv/unsetenv/free */
+                       char *s = xasprintf("%s=%s", "MDEV", device_name);
+                       char *s1 = xasprintf("%s=%s", "SUBSYSTEM", subsystem);
+                       putenv(s);
+                       putenv(s1);
+                       if (system(command) == -1)
+                               bb_perror_msg_and_die("can't run '%s'", command);
+                       unsetenv("SUBSYSTEM");
+                       free(s1);
+                       unsetenv("MDEV");
+                       free(s);
+                       free(command);
+               }
 #endif
-
-       if (delete) {
-               unlink(device_name);
-               /* At creation time, device might have been moved
-                * and a symlink might have been created. Undo that. */
+               if (delete) {
+                       unlink(device_name);
+                       /* At creation time, device might have been moved
+                        * and a symlink might have been created. Undo that. */
 #if ENABLE_FEATURE_MDEV_RENAME
-               if (alias) {
-                       alias = build_alias(alias, device_name);
-                       unlink(alias);
-                       free(alias);
-               }
+                       if (alias) {
+                               alias = build_alias(alias, device_name);
+                               unlink(alias);
+                               free(alias);
+                       }
 #endif
-       }
+               }
+
+#if ENABLE_FEATURE_MDEV_CONF
+               /* We found matching line.
+                * Stop unless it was prefixed with '-' */
+               if (!keep_matching)
+                       break;
+       } /* end of "while line is read from /etc/mdev.conf" */
+
+       config_close(parser);
+#endif /* ENABLE_FEATURE_MDEV_CONF */
 }
 
 /* File callback for /sys/ traversal */
@@ -322,6 +351,15 @@ static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
                void *userData UNUSED_PARAM,
                int depth)
 {
+       /* Extract device subsystem -- the name of the directory
+        * under /sys/class/ */
+       if (1 == depth) {
+               free(subsystem);
+               subsystem = strrchr(fileName, '/');
+               if (subsystem)
+                       subsystem = xstrdup(subsystem + 1);
+       }
+
        return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
 }
 
@@ -359,17 +397,17 @@ static void load_firmware(const char *const firmware, const char *const sysfs_pa
        goto out;
 
  loading:
-       /* tell kernel we're loading by `echo 1 > /sys/$DEVPATH/loading` */
+       /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */
        if (full_write(loading_fd, "1", 1) != 1)
                goto out;
 
-       /* load firmware by `cat /lib/firmware/$FIRMWARE > /sys/$DEVPATH/data */
+       /* load firmware into /sys/$DEVPATH/data */
        data_fd = open("data", O_WRONLY);
        if (data_fd == -1)
                goto out;
        cnt = bb_copyfd_eof(firmware_fd, data_fd);
 
-       /* tell kernel result by `echo [0|-1] > /sys/$DEVPATH/loading` */
+       /* tell kernel result by "echo [0|-1] > /sys/$DEVPATH/loading" */
        if (cnt > 0)
                full_write(loading_fd, "0", 1);
        else
@@ -405,7 +443,7 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
 
        xchdir("/dev");
 
-       if (argv[1] && !strcmp(argv[1], "-s")) {
+       if (argv[1] && strcmp(argv[1], "-s") == 0) {
                /* Scan:
                 * mdev -s
                 */
@@ -419,37 +457,54 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
                 * /sys/block/loop* (for example) are symlinks to dirs,
                 * not real directories.
                 * (kernel's CONFIG_SYSFS_DEPRECATED makes them real dirs,
-                * but we can't enforce that on users) */
-               recursive_action("/sys/block",
-                       ACTION_RECURSE | ACTION_FOLLOWLINKS,
-                       fileAction, dirAction, temp, 0);
+                * but we can't enforce that on users)
+                */
+               if (access("/sys/class/block", F_OK) != 0) {
+                       /* Scan obsolete /sys/block only if /sys/class/block
+                        * doesn't exist. Otherwise we'll have dupes.
+                        * Also, do not complain if it doesn't exist.
+                        * Some people configure kernel to have no blockdevs.
+                        */
+                       recursive_action("/sys/block",
+                               ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,
+                               fileAction, dirAction, temp, 0);
+               }
                recursive_action("/sys/class",
                        ACTION_RECURSE | ACTION_FOLLOWLINKS,
                        fileAction, dirAction, temp, 0);
        } else {
+               char *fw;
                char *seq;
                char *action;
                char *env_path;
-               char seqbuf[sizeof(int)*3 + 2];
-               int seqlen = seqlen; /* for compiler */
 
                /* Hotplug:
-                * env ACTION=... DEVPATH=... [SEQNUM=...] mdev
+                * env ACTION=... DEVPATH=... SUBSYSTEM=... [SEQNUM=...] mdev
                 * ACTION can be "add" or "remove"
                 * DEVPATH is like "/block/sda" or "/class/input/mice"
                 */
                action = getenv("ACTION");
                env_path = getenv("DEVPATH");
-               if (!action || !env_path)
+               subsystem = getenv("SUBSYSTEM");
+               if (!action || !env_path /*|| !subsystem*/)
                        bb_show_usage();
+               fw = getenv("FIRMWARE");
 
+               /* If it exists, does /dev/mdev.seq match $SEQNUM?
+                * If it does not match, earlier mdev is running
+                * in parallel, and we need to wait */
                seq = getenv("SEQNUM");
                if (seq) {
-                       int timeout = 2000 / 32;
+                       int timeout = 2000 / 32; /* 2000 msec */
                        do {
+                               int seqlen;
+                               char seqbuf[sizeof(int)*3 + 2];
+
                                seqlen = open_read_close("mdev.seq", seqbuf, sizeof(seqbuf-1));
-                               if (seqlen < 0)
+                               if (seqlen < 0) {
+                                       seq = NULL;
                                        break;
+                               }
                                seqbuf[seqlen] = '\0';
                                if (seqbuf[0] == '\n' /* seed file? */
                                 || strcmp(seq, seqbuf) == 0 /* correct idx? */
@@ -461,19 +516,22 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
                }
 
                snprintf(temp, PATH_MAX, "/sys%s", env_path);
-               if (!strcmp(action, "remove"))
-                       make_device(temp, 1);
-               else if (!strcmp(action, "add")) {
+               if (strcmp(action, "remove") == 0) {
+                       /* Ignoring "remove firmware". It was reported
+                        * to happen and to cause erroneous deletion
+                        * of device nodes. */
+                       if (!fw)
+                               make_device(temp, 1);
+               }
+               else if (strcmp(action, "add") == 0) {
                        make_device(temp, 0);
-
                        if (ENABLE_FEATURE_MDEV_LOAD_FIRMWARE) {
-                               char *fw = getenv("FIRMWARE");
                                if (fw)
                                        load_firmware(fw, temp);
                        }
                }
 
-               if (seq && seqlen >= 0) {
+               if (seq) {
                        xopen_xwrite_close("mdev.seq", utoa(xatou(seq) + 1));
                }
        }
@@ -481,5 +539,5 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
        if (ENABLE_FEATURE_CLEAN_UP)
                RELEASE_CONFIG_BUFFER(temp);
 
-       return 0;
+       return EXIT_SUCCESS;
 }