tls: in AES-GCM decoding, avoid memmove
[oweals/busybox.git] / util-linux / mdev.c
index 5fe6bbbdeec4a580670d63b72f176a1e79ec7def..4b4eeafba558354e4b5139d5c0b6490931bde1e4 100644 (file)
@@ -7,64 +7,63 @@
  *
  * Licensed under GPLv2, see file LICENSE in this source tree.
  */
-
 //config:config MDEV
-//config:      bool "mdev"
+//config:      bool "mdev (16 kb)"
 //config:      default y
 //config:      select PLATFORM_LINUX
 //config:      help
-//config:        mdev is a mini-udev implementation for dynamically creating device
-//config:        nodes in the /dev directory.
+//config:      mdev is a mini-udev implementation for dynamically creating device
+//config:      nodes in the /dev directory.
 //config:
-//config:        For more information, please see docs/mdev.txt
+//config:      For more information, please see docs/mdev.txt
 //config:
 //config:config FEATURE_MDEV_CONF
 //config:      bool "Support /etc/mdev.conf"
 //config:      default y
 //config:      depends on MDEV
 //config:      help
-//config:        Add support for the mdev config file to control ownership and
-//config:        permissions of the device nodes.
+//config:      Add support for the mdev config file to control ownership and
+//config:      permissions of the device nodes.
 //config:
-//config:        For more information, please see docs/mdev.txt
+//config:      For more information, please see docs/mdev.txt
 //config:
 //config:config FEATURE_MDEV_RENAME
 //config:      bool "Support subdirs/symlinks"
 //config:      default y
 //config:      depends on FEATURE_MDEV_CONF
 //config:      help
-//config:        Add support for renaming devices and creating symlinks.
+//config:      Add support for renaming devices and creating symlinks.
 //config:
-//config:        For more information, please see docs/mdev.txt
+//config:      For more information, please see docs/mdev.txt
 //config:
 //config:config FEATURE_MDEV_RENAME_REGEXP
 //config:      bool "Support regular expressions substitutions when renaming device"
 //config:      default y
 //config:      depends on FEATURE_MDEV_RENAME
 //config:      help
-//config:        Add support for regular expressions substitutions when renaming
-//config:        device.
+//config:      Add support for regular expressions substitutions when renaming
+//config:      device.
 //config:
 //config:config FEATURE_MDEV_EXEC
 //config:      bool "Support command execution at device addition/removal"
 //config:      default y
 //config:      depends on FEATURE_MDEV_CONF
 //config:      help
-//config:        This adds support for an optional field to /etc/mdev.conf for
-//config:        executing commands when devices are created/removed.
+//config:      This adds support for an optional field to /etc/mdev.conf for
+//config:      executing commands when devices are created/removed.
 //config:
-//config:        For more information, please see docs/mdev.txt
+//config:      For more information, please see docs/mdev.txt
 //config:
 //config:config FEATURE_MDEV_LOAD_FIRMWARE
-//config:      bool "Support loading of firmwares"
+//config:      bool "Support loading of firmware"
 //config:      default y
 //config:      depends on MDEV
 //config:      help
-//config:        Some devices need to load firmware before they can be usable.
+//config:      Some devices need to load firmware before they can be usable.
 //config:
-//config:        These devices will request userspace look up the files in
-//config:        /lib/firmware/ and if it exists, send it to the kernel for
-//config:        loading into the hardware.
+//config:      These devices will request userspace look up the files in
+//config:      /lib/firmware/ and if it exists, send it to the kernel for
+//config:      loading into the hardware.
 
 //applet:IF_MDEV(APPLET(mdev, BB_DIR_SBIN, BB_SUID_DROP))
 
@@ -97,6 +96,7 @@
 //usage:       "If /dev/mdev.log file exists, debug log will be appended to it."
 
 #include "libbb.h"
+#include "common_bufsiz.h"
 #include "xregex.h"
 
 /* "mdev -s" scans /sys/class/xxx, looking for directories which have dev
@@ -283,10 +283,11 @@ struct globals {
        unsigned rule_idx;
 #endif
        struct rule cur_rule;
-       char timestr[sizeof("60.123456")];
+       char timestr[sizeof("HH:MM:SS.123456")];
 } FIX_ALIASING;
-#define G (*(struct globals*)&bb_common_bufsiz1)
+#define G (*(struct globals*)bb_common_bufsiz1)
 #define INIT_G() do { \
+       setup_common_bufsiz(); \
        IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.maj = -1;) \
        IF_NOT_FEATURE_MDEV_CONF(G.cur_rule.mode = 0660;) \
 } while (0)
@@ -295,8 +296,8 @@ struct globals {
 /* Prevent infinite loops in /sys symlinks */
 #define MAX_SYSFS_DEPTH 3
 
-/* We use additional 64+ bytes in make_device() */
-#define SCRATCH_SIZE 80
+/* We use additional bytes in make_device() */
+#define SCRATCH_SIZE 128
 
 #if ENABLE_FEATURE_MDEV_CONF
 
@@ -400,13 +401,13 @@ static void parse_next_rule(void)
                }
 
                /* 2nd field: uid:gid - device ownership */
-               if (get_uidgid(&G.cur_rule.ugid, tokens[1], /*allow_numeric:*/ 1) == 0) {
+               if (get_uidgid(&G.cur_rule.ugid, tokens[1]) == 0) {
                        bb_error_msg("unknown user/group '%s' on line %d", tokens[1], G.parser->lineno);
                        goto next_rule;
                }
 
                /* 3rd field: mode - device permissions */
-               bb_parse_mode(tokens[2], &G.cur_rule.mode);
+               G.cur_rule.mode = bb_parse_mode(tokens[2], G.cur_rule.mode);
 
                /* 4th field (opt): ">|=alias" or "!" to not create the node */
                val = tokens[3];
@@ -471,7 +472,7 @@ static const struct rule *next_rule(void)
        if (G.parser) {
                parse_next_rule();
                if (G.rule_vec) { /* mdev -s */
-                       rule = memcpy(xmalloc(sizeof(G.cur_rule)), &G.cur_rule, sizeof(G.cur_rule));
+                       rule = xmemdup(&G.cur_rule, sizeof(G.cur_rule));
                        G.rule_vec = xrealloc_vector(G.rule_vec, 4, G.rule_idx);
                        G.rule_vec[G.rule_idx++] = rule;
                        dbg3("> G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
@@ -541,8 +542,7 @@ static char *build_alias(char *alias, const char *device_name)
 
 /* mknod in /dev based on a path like "/sys/block/hda/hda1"
  * NB1: path parameter needs to have SCRATCH_SIZE scratch bytes
- * after NUL, but we promise to not mangle (IOW: to restore if needed)
- * path string.
+ * after NUL, but we promise to not mangle it (IOW: to restore NUL if needed).
  * NB2: "mdev -s" may call us many times, do not leak memory/fds!
  *
  * device_name = $DEVNAME (may be NULL)
@@ -551,6 +551,7 @@ static char *build_alias(char *alias, const char *device_name)
 static void make_device(char *device_name, char *path, int operation)
 {
        int major, minor, type, len;
+       char *path_end = path + strlen(path);
 
        /* Try to read major/minor string.  Note that the kernel puts \n after
         * the data, so we don't need to worry about null terminating the string
@@ -559,17 +560,15 @@ static void make_device(char *device_name, char *path, int operation)
         */
        major = -1;
        if (operation == OP_add) {
-               char *dev_maj_min = path + strlen(path);
-
-               strcpy(dev_maj_min, "/dev");
-               len = open_read_close(path, dev_maj_min + 1, 64);
-               *dev_maj_min = '\0';
+               strcpy(path_end, "/dev");
+               len = open_read_close(path, path_end + 1, SCRATCH_SIZE - 1);
+               *path_end = '\0';
                if (len < 1) {
                        if (!ENABLE_FEATURE_MDEV_EXEC)
                                return;
                        /* no "dev" file, but we can still run scripts
                         * based on device name */
-               } else if (sscanf(++dev_maj_min, "%u:%u", &major, &minor) == 2) {
+               } else if (sscanf(path_end + 1, "%u:%u", &major, &minor) == 2) {
                        dbg1("dev %u,%u", major, minor);
                } else {
                        major = -1;
@@ -577,9 +576,33 @@ static void make_device(char *device_name, char *path, int operation)
        }
        /* else: for delete, -1 still deletes the node, but < -1 suppresses that */
 
-       /* Determine device name, type, major and minor */
-       if (!device_name)
-               device_name = (char*) bb_basename(path);
+       /* Determine device name */
+       if (!device_name) {
+               /*
+                * There was no $DEVNAME envvar (for example, mdev -s never has).
+                * But it is very useful: it contains the *path*, not only basename,
+                * Thankfully, uevent file has it.
+                * Example of .../sound/card0/controlC0/uevent file on Linux-3.7.7:
+                * MAJOR=116
+                * MINOR=7
+                * DEVNAME=snd/controlC0
+                */
+               strcpy(path_end, "/uevent");
+               len = open_read_close(path, path_end + 1, SCRATCH_SIZE - 1);
+               if (len < 0)
+                       len = 0;
+               *path_end = '\0';
+               path_end[1 + len] = '\0';
+               device_name = strstr(path_end + 1, "\nDEVNAME=");
+               if (device_name) {
+                       device_name += sizeof("\nDEVNAME=")-1;
+                       strchrnul(device_name, '\n')[0] = '\0';
+               } else {
+                       /* Fall back to just basename */
+                       device_name = (char*) bb_basename(path);
+               }
+       }
+       /* Determine device type */
        /*
         * http://kernel.org/doc/pending/hotplug.txt says that only
         * "/sys/block/..." is for block devices. "/sys/bus" etc is not.
@@ -587,7 +610,7 @@ static void make_device(char *device_name, char *path, int operation)
         * We use strstr("/block/") to forestall future surprises.
         */
        type = S_IFCHR;
-       if (strstr(path, "/block/") || (G.subsystem && strncmp(G.subsystem, "block", 5) == 0))
+       if (strstr(path, "/block/") || (G.subsystem && is_prefixed_with(G.subsystem, "block")))
                type = S_IFBLK;
 
 #if ENABLE_FEATURE_MDEV_CONF
@@ -785,41 +808,39 @@ static void make_device(char *device_name, char *path, int operation)
        } /* for (;;) */
 }
 
-/* File callback for /sys/ traversal */
+/* File callback for /sys/ traversal.
+ * We act only on "/sys/.../dev" (pseudo)file
+ */
 static int FAST_FUNC fileAction(const char *fileName,
                struct stat *statbuf UNUSED_PARAM,
                void *userData,
                int depth UNUSED_PARAM)
 {
        size_t len = strlen(fileName) - 4; /* can't underflow */
-       char *scratch = userData;
-
-       /* len check is for paranoid reasons */
-       if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX)
-               return FALSE;
-
-       strcpy(scratch, fileName);
-       scratch[len] = '\0';
-       make_device(/*DEVNAME:*/ NULL, scratch, OP_add);
-
-       return TRUE;
-}
-
-/* Directory callback for /sys/ traversal */
-static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
-               struct stat *statbuf UNUSED_PARAM,
-               void *userData UNUSED_PARAM,
-               int depth)
-{
-       /* Extract device subsystem -- the name of the directory
-        * under /sys/class/ */
-       if (1 == depth) {
+       char *path = userData;  /* char array[PATH_MAX + SCRATCH_SIZE] */
+       char subsys[PATH_MAX];
+       int res;
+
+       /* Is it a ".../dev" file? (len check is for paranoid reasons) */
+       if (strcmp(fileName + len, "/dev") != 0 || len >= PATH_MAX - 32)
+               return FALSE; /* not .../dev */
+
+       strcpy(path, fileName);
+       path[len] = '\0';
+
+       /* Read ".../subsystem" symlink in the same directory where ".../dev" is */
+       strcpy(subsys, path);
+       strcpy(subsys + len, "/subsystem");
+       res = readlink(subsys, subsys, sizeof(subsys)-1);
+       if (res > 0) {
+               subsys[res] = '\0';
                free(G.subsystem);
                if (G.subsys_env) {
                        bb_unsetenv_and_free(G.subsys_env);
                        G.subsys_env = NULL;
                }
-               G.subsystem = strrchr(fileName, '/');
+               /* Set G.subsystem and $SUBSYSTEM from symlink's last component */
+               G.subsystem = strrchr(subsys, '/');
                if (G.subsystem) {
                        G.subsystem = xstrdup(G.subsystem + 1);
                        G.subsys_env = xasprintf("%s=%s", "SUBSYSTEM", G.subsystem);
@@ -827,6 +848,17 @@ static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
                }
        }
 
+       make_device(/*DEVNAME:*/ NULL, path, OP_add);
+
+       return TRUE;
+}
+
+/* Directory callback for /sys/ traversal */
+static int FAST_FUNC dirAction(const char *fileName UNUSED_PARAM,
+               struct stat *statbuf UNUSED_PARAM,
+               void *userData UNUSED_PARAM,
+               int depth)
+{
        return (depth >= MAX_SYSFS_DEPTH ? SKIP : TRUE);
 }
 
@@ -847,8 +879,9 @@ static void load_firmware(const char *firmware, const char *sysfs_path)
        int firmware_fd, loading_fd;
 
        /* check for /lib/firmware/$FIRMWARE */
-       xchdir("/lib/firmware");
-       firmware_fd = open(firmware, O_RDONLY); /* can fail */
+       firmware_fd = -1;
+       if (chdir("/lib/firmware") == 0)
+               firmware_fd = open(firmware, O_RDONLY); /* can fail */
 
        /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */
        xchdir(sysfs_path);
@@ -900,7 +933,11 @@ static char *curtime(void)
 {
        struct timeval tv;
        gettimeofday(&tv, NULL);
-       sprintf(G.timestr, "%u.%06u", (unsigned)tv.tv_sec % 60, (unsigned)tv.tv_usec);
+       sprintf(
+               strftime_HHMMSS(G.timestr, sizeof(G.timestr), &tv.tv_sec),
+               ".%06u",
+               (unsigned)tv.tv_usec
+       );
        return G.timestr;
 }
 
@@ -920,7 +957,7 @@ static void open_mdev_log(const char *seq, unsigned my_pid)
  * Active mdev pokes us with SIGCHLD to check the new file.
  */
 static int
-wait_for_seqfile(const char *seq)
+wait_for_seqfile(unsigned expected_seq)
 {
        /* We time out after 2 sec */
        static const struct timespec ts = { 0, 32*1000*1000 };
@@ -935,12 +972,14 @@ wait_for_seqfile(const char *seq)
 
        for (;;) {
                int seqlen;
-               char seqbuf[sizeof(int)*3 + 2];
+               char seqbuf[sizeof(long)*3 + 2];
+               unsigned seqbufnum;
 
                if (seq_fd < 0) {
                        seq_fd = open("mdev.seq", O_RDWR);
                        if (seq_fd < 0)
                                break;
+                       close_on_exec_on(seq_fd);
                }
                seqlen = pread(seq_fd, seqbuf, sizeof(seqbuf) - 1, 0);
                if (seqlen < 0) {
@@ -949,19 +988,27 @@ wait_for_seqfile(const char *seq)
                        break;
                }
                seqbuf[seqlen] = '\0';
-               if (seqbuf[0] == '\n') {
+               if (seqbuf[0] == '\n' || seqbuf[0] == '\0') {
                        /* seed file: write out seq ASAP */
-                       xwrite_str(seq_fd, seq);
+                       xwrite_str(seq_fd, utoa(expected_seq));
                        xlseek(seq_fd, 0, SEEK_SET);
                        dbg2("first seq written");
                        break;
                }
-               if (strcmp(seq, seqbuf) == 0) {
+               seqbufnum = atoll(seqbuf);
+               if (seqbufnum == expected_seq) {
                        /* correct idx */
                        break;
                }
+               if (seqbufnum > expected_seq) {
+                       /* a later mdev runs already (this was seen by users to happen) */
+                       /* do not overwrite seqfile on exit */
+                       close(seq_fd);
+                       seq_fd = -1;
+                       break;
+               }
                if (do_once) {
-                       dbg2("%s waiting for '%s'", curtime(), seqbuf);
+                       dbg2("%s mdev.seq='%s', need '%u'", curtime(), seqbuf, expected_seq);
                        do_once = 0;
                }
                if (sigtimedwait(&set_CHLD, NULL, &ts) >= 0) {
@@ -969,7 +1016,7 @@ wait_for_seqfile(const char *seq)
                        continue; /* don't decrement timeout! */
                }
                if (--timeout == 0) {
-                       dbg1("%s waiting for '%s'", "timed out", seqbuf);
+                       dbg1("%s mdev.seq='%s'", "timed out", seqbuf);
                        break;
                }
        }
@@ -1026,25 +1073,10 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
 
                putenv((char*)"ACTION=add");
 
-               /* ACTION_FOLLOWLINKS is needed since in newer kernels
-                * /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)
-                */
-               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);
+               /* Create all devices from /sys/dev hierarchy */
+               recursive_action("/sys/dev",
+                                ACTION_RECURSE | ACTION_FOLLOWLINKS,
+                                fileAction, dirAction, temp, 0);
        } else {
                char *fw;
                char *seq;
@@ -1052,6 +1084,7 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
                char *env_devname;
                char *env_devpath;
                unsigned my_pid;
+               unsigned seqnum = seqnum; /* for compiler */
                int seq_fd;
                smalluint op;
 
@@ -1060,20 +1093,24 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
                 * ACTION can be "add", "remove", "change"
                 * DEVPATH is like "/block/sda" or "/class/input/mice"
                 */
-               action = getenv("ACTION");
-               op = index_in_strings(keywords, action);
                env_devname = getenv("DEVNAME"); /* can be NULL */
-               env_devpath = getenv("DEVPATH");
                G.subsystem = getenv("SUBSYSTEM");
+               action = getenv("ACTION");
+               env_devpath = getenv("DEVPATH");
                if (!action || !env_devpath /*|| !G.subsystem*/)
                        bb_show_usage();
                fw = getenv("FIRMWARE");
                seq = getenv("SEQNUM");
+               op = index_in_strings(keywords, action);
 
                my_pid = getpid();
                open_mdev_log(seq, my_pid);
 
-               seq_fd = seq ? wait_for_seqfile(seq) : -1;
+               seq_fd = -1;
+               if (seq) {
+                       seqnum = atoll(seq);
+                       seq_fd = wait_for_seqfile(seqnum);
+               }
 
                dbg1("%s "
                        "ACTION:%s SUBSYSTEM:%s DEVNAME:%s DEVPATH:%s"
@@ -1101,7 +1138,7 @@ int mdev_main(int argc UNUSED_PARAM, char **argv)
 
                dbg1("%s exiting", curtime());
                if (seq_fd >= 0) {
-                       xwrite_str(seq_fd, utoa(xatou(seq) + 1));
+                       xwrite_str(seq_fd, utoa(seqnum + 1));
                        signal_mdevs(my_pid);
                }
        }