diff -Naur busybox.orig/include/applets.h busybox/include/applets.h --- busybox.orig/include/applets.h 2010-05-25 20:48:33.000000000 +0400 +++ busybox/include/applets.h 2010-05-27 22:16:09.126989091 +0400 @@ -287,6 +287,7 @@ IF_MV(APPLET(mv, _BB_DIR_BIN, _BB_SUID_DROP)) IF_NAMEIF(APPLET(nameif, _BB_DIR_SBIN, _BB_SUID_DROP)) IF_NC(APPLET(nc, _BB_DIR_USR_BIN, _BB_SUID_DROP)) +IF_NDEV(APPLET(ndev, _BB_DIR_USR_SBIN, _BB_SUID_DROP)) IF_NETSTAT(APPLET(netstat, _BB_DIR_BIN, _BB_SUID_DROP)) IF_NICE(APPLET(nice, _BB_DIR_BIN, _BB_SUID_DROP)) IF_NMETER(APPLET(nmeter, _BB_DIR_USR_BIN, _BB_SUID_DROP)) diff -Naur busybox.orig/include/usage.h busybox/include/usage.h --- busybox.orig/include/usage.h 2010-05-25 20:48:33.000000000 +0400 +++ busybox/include/usage.h 2010-05-27 22:16:09.134965314 +0400 @@ -3241,6 +3241,13 @@ #endif +#define ndev_trivial_usage \ + "[-s]" +#define ndev_full_usage "\n\n" \ + " -s Scan /sys and populate /dev during system boot\n" \ + "\n" \ + "It can be run as netlink daemon: \"ndev &\"" \ + #define netstat_trivial_usage \ "[-laentuwxr"IF_FEATURE_NETSTAT_WIDE("W")IF_FEATURE_NETSTAT_PRG("p")"]" #define netstat_full_usage "\n\n" \ diff -Naur busybox.orig/util-linux/Config.in busybox/util-linux/Config.in --- busybox.orig/util-linux/Config.in 2010-05-25 20:48:33.000000000 +0400 +++ busybox/util-linux/Config.in 2010-05-27 22:16:09.134965314 +0400 @@ -372,6 +372,13 @@ This version uses sysfs (/sys/bus/usb/devices) only. +config NDEV + bool "ndev" + default n + help + ndev is a mini-udev implementation for dynamically creating device + nodes in the /dev directory using netlink kernel messages. + config MDEV bool "mdev" default n diff -Naur busybox.orig/util-linux/Kbuild busybox/util-linux/Kbuild --- busybox.orig/util-linux/Kbuild 2010-05-25 20:48:33.000000000 +0400 +++ busybox/util-linux/Kbuild 2010-05-27 22:16:09.134965314 +0400 @@ -32,6 +32,7 @@ lib-$(CONFIG_MKSWAP) += mkswap.o lib-$(CONFIG_MORE) += more.o lib-$(CONFIG_MOUNT) += mount.o +lib-$(CONFIG_NDEV) += ndev.o lib-$(CONFIG_PIVOT_ROOT) += pivot_root.o lib-$(CONFIG_RDATE) += rdate.o lib-$(CONFIG_RDEV) += rdev.o diff -Naur busybox.orig/util-linux/ndev.c busybox/util-linux/ndev.c --- busybox.orig/util-linux/ndev.c 1970-01-01 03:00:00.000000000 +0300 +++ busybox/util-linux/ndev.c 2010-05-27 22:16:09.138965264 +0400 @@ -0,0 +1,529 @@ +/* vi: set sw=4 ts=4: */ +/* + * mdev - Mini udev for busybox + * + * Copyright 2005 Rob Landley + * Copyright 2005 Frank Sorenson + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ +#include "libbb.h" +#include "xregex.h" +#include + +/* "mdev -s" scans /sys/class/xxx, looking for directories which have dev + * file (it is of the form "M:m\n"). Example: /sys/class/tty/tty0/dev + * contains "4:0\n". Directory name is taken as device name, path component + * directly after /sys/class/ as subsystem. In this example, "tty0" and "tty". + * Then mdev creates the /dev/device_name node. + * If /sys/class/.../dev file does not exist, mdev still may act + * on this device: see "@|$|*command args..." parameter in config file. + * + * mdev w/o parameters is called as hotplug helper. It takes device + * and subsystem names from $DEVPATH and $SUBSYSTEM, extracts + * maj,min from "/sys/$DEVPATH/dev" and also examines + * $ACTION ("add"/"delete") and $FIRMWARE. + * + * If action is "add", mdev creates /dev/device_name similarly to mdev -s. + * (todo: explain "delete" and $FIRMWARE) + * + * If /etc/mdev.conf exists, it may modify /dev/device_name's properties. + * /etc/mdev.conf file format: + * + * [-][subsystem/]device user:grp mode [>|=path] [@|$|*command args...] + * [-]@maj,min[-min2] user:grp mode [>|=path] [@|$|*command args...] + * [-]$envvar=val user:grp mode [>|=path] [@|$|*command args...] + * + * Leading minus in 1st field means "don't stop on this line", otherwise + * search is stopped after the matching line is encountered. + * + * The device name or "subsystem/device" combo is matched against 1st field + * (which is a regex), or maj,min is matched against 1st field, + * or specified environment variable (as regex) is matched against 1st field. + * + * $envvar=val format is useful for loading modules for hot-plugged devices + * which do not have driver loaded yet. In this case /sys/class/.../dev + * does not exist, but $MODALIAS is set to needed module's name + * (actually, an alias to it) by kernel. This rule instructs mdev + * to load the module and exit: + * $MODALIAS=.* 0:0 660 @modprobe "$MODALIAS" + * The kernel will generate another hotplug event when /sys/class/.../dev + * file appears. + * + * When line matches, the device node is created, chmod'ed and chown'ed, + * moved to path, and if >path, a symlink to moved node is created, + * all this if /sys/class/.../dev exists. + * Examples: + * =loop/ - moves to /dev/loop + * >disk/sda%1 - moves to /dev/disk/sdaN, makes /dev/sdaN a symlink + * + * Then "command args..." is executed (via sh -c 'command args...'). + * @:execute on creation, $:on deletion, *:on both. + * This happens regardless of /sys/class/.../dev existence. + */ + +/* We use additional 64+ bytes in make_device() */ +#define SCRATCH_SIZE 80 + +/* Builds an alias path. + * This function potentionally reallocates the alias parameter. + */ +static char *build_alias(char *alias, const char *device_name) +{ + char *dest; + + /* ">bar/": rename to bar/device_name */ + /* ">bar[/]baz": rename to bar[/]baz */ + dest = strrchr(alias, '/'); + if (dest) { /* ">bar/[baz]" ? */ + *dest = '\0'; /* mkdir bar */ + bb_make_directory(alias, 0755, FILEUTILS_RECUR); + *dest = '/'; + if (dest[1] == '\0') { /* ">bar/" => ">bar/device_name" */ + dest = alias; + alias = concat_path_file(alias, device_name); + free(dest); + } + } + + return alias; +} + +/* 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. + * NB2: "mdev -s" may call us many times, do not leak memory/fds! + */ +static void hotplug(int delete) +{ + const char *devpath, *subsystem; + char *device_name, path[PATH_MAX]; + int major, minor, type; + mode_t mode; + parser_t *parser = NULL; + +/* for (char **env = environ; env && *env; env++) { + bb_info_msg("%s", *env); + }*/ + + devpath = getenv("DEVPATH"); + subsystem = getenv("SUBSYSTEM"); + { + char *s; + s = getenv("MAJOR"); major = s ? bb_strtou(s, NULL, 10) : -1; + s = getenv("MINOR"); minor = s ? bb_strtou(s, NULL, 10) : -1; + } + + // device name is the last component of devpath + device_name = (char*) bb_basename(devpath); + type = (0 == strcmp(subsystem, "block")) ? S_IFBLK : S_IFCHR; + + // make path point to "subsystem/device_name" + sprintf(path, "%s/%s", subsystem, device_name); + + /* If we want and have config file, look up user settings */ + if (!(option_mask32 & 1)) + parser = config_open2("/etc/mdev.conf", fopen_for_read); + + do { + int keep_matching; + struct bb_uidgid_t ugid; + char *tokens[4]; + char *command = NULL; + char *alias = NULL; + char aliaslink = aliaslink; /* for compiler */ + + // defaults in case we won't match any line + ugid.uid = ugid.gid = 0; // root:root + keep_matching = 0; + mode = 0660; + + if (parser && config_read(parser, tokens, 4, 3, "# \t", PARSE_NORMAL)) { + char *val; + char *str_to_match; + regmatch_t off[1 + 9]; + + val = tokens[0]; + keep_matching = ('-' == val[0]); + val += keep_matching; /* swallow leading dash */ + + /* Match against either "subsystem/device_name" + * or "device_name" alone */ + str_to_match = strchr(val, '/') ? path : device_name; + + /* Fields: regex uid:gid mode [alias] [cmd] */ + + if (val[0] == '$') { + /* regex to match an environment variable */ + char *eq = strchr(++val, '='); + if (!eq) + continue; + *eq = '\0'; + str_to_match = getenv(val); + if (!str_to_match) + continue; + str_to_match -= strlen(val) + 1; + *eq = '='; + } + /* else: regex to match [subsystem/]device_name */ + + { + regex_t match; + int result; + + xregcomp(&match, val, REG_EXTENDED); + result = regexec(&match, str_to_match, ARRAY_SIZE(off), off, 0); + regfree(&match); + //bb_error_msg("matches:"); + //for (int i = 0; i < ARRAY_SIZE(off); i++) { + // if (off[i].rm_so < 0) continue; + // bb_error_msg("match %d: '%.*s'\n", i, + // (int)(off[i].rm_eo - off[i].rm_so), + // device_name + off[i].rm_so); + //} + + /* If no match, skip rest of line */ + /* (regexec returns whole pattern as "range" 0) */ + if (result || off[0].rm_so + || ((int)off[0].rm_eo != (int)strlen(str_to_match)) + ) { + continue; /* this line doesn't match */ + } + } + + /* This line matches. Stop parsing after parsing + * the rest the line unless keep_matching == 1 */ + + /* 2nd field: uid:gid - device ownership */ + if (!LONE_DASH(tokens[1]) && 0 == get_uidgid(&ugid, tokens[1], 1)) + bb_error_msg("unknown user/group %s", tokens[1]); + + /* 3rd field: mode - device permissions */ + if (!LONE_DASH(tokens[2])) + bb_parse_mode(tokens[2], &mode); + + val = tokens[3]; + /* 4th field (opt): >|=alias */ + + if (val) { + aliaslink = val[0]; + if (aliaslink == '>' || aliaslink == '=') { + char *a, *s, *st; + char *p; + unsigned i, n; + + 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 (1) { + /* substitute %1..9 with off[1..9], if any */ + n = 0; + s = a; + while (*s) + if (*s++ == '%') + n++; + + p = alias = xzalloc(strlen(a) + n * strlen(str_to_match)); + s = a + 1; + while (*s) { + *p = *s; + if ('%' == *s) { + i = (s[1] - '0'); + if (i <= 9 && off[i].rm_so >= 0) { + n = off[i].rm_eo - off[i].rm_so; + strncpy(p, str_to_match + off[i].rm_so, n); + p += n - 1; + s++; + } + } + p++; + s++; + } + } else { + alias = xstrdup(a + 1); + } + } + } + + if (val) { + const char *s = "$@*"; + const char *s2 = strchr(s, val[0]); + + if (!s2) { + bb_error_msg("bad line %u", parser->lineno); + if (1) + free(alias); + continue; + } + + /* Are we running this command now? + * Run $cmd on delete, @cmd on create, *cmd on both + */ + if (s2-s != delete) + command = xstrdup(val + 1); + } + } + + /* End of field parsing */ + + /* "Execute" the line we found */ + { + const char *node_name; + + node_name = device_name; + if (alias) + node_name = alias = build_alias(alias, device_name); + + if (!delete && major >= 0) { + unlink(node_name); /* Ensure no device is present */ + if (mknod(node_name, mode | type, makedev(major, minor)) && errno != EEXIST) { + bb_perror_msg("can't create %s", node_name); + continue; + } + if (parser) { + chmod(node_name, mode); + chown(node_name, ugid.uid, ugid.gid); + } + if (alias) { + if (aliaslink == '>') + symlink(node_name, device_name); + } + } + + if (command) { + if (system(command) == -1) + bb_perror_msg("can't run '%s'", command); + free(command); + } + + if (delete) { + if (alias) { + if (aliaslink == '>') + unlink(device_name); + } + unlink(node_name); + } + + if (1) + free(alias); + } + + /* We found matching line. + * Stop unless it was prefixed with '-' */ + if (parser && !keep_matching) + break; + + /* end of "while line is read from /etc/mdev.conf" */ + } while (parser); + + if (parser) + config_close(parser); +} + +/* Directory callback for /sys/ traversal */ +static int FAST_FUNC dirAction(const char *fileName, + struct stat *statbuf UNUSED_PARAM, + void *temp, + int depth) +{ + // we must fetch device path, subsystem +#define len depth +#define buf ((char *) temp) + // fetch device subsystem + char s[PATH_MAX]; + strcpy(buf, fileName); strcat(buf, "/subsystem"); + len = readlink(buf, s, sizeof(s)); + // subsystem must not be empty + if (len > 0) { + s[len] = '\0'; // s now contains the value of subsystem link: the last path component is subsystem itself + // read uevent environment + strcpy(buf + strlen(fileName), "/uevent"); + len = open_read_close(buf, buf, PATH_MAX + SCRATCH_SIZE); + // uevent environment must not be empty + if (len > 0) { + char *env, *ptr; + buf[len] = '\0'; + // reset current environment + clearenv(); + // put environment read from uevent + setenv("DEVPATH", fileName + (sizeof("/sys")-1), 0); + setenv("SUBSYSTEM", bb_basename(s), 0); + setenv("ACTION", "add", 0); + for (env = strtok_r(buf, "\n", &ptr); env; env = strtok_r(NULL, "\n", &ptr)) { + putenv(env); + } + // simulate hotplug add event + hotplug(0); + } + } +#undef buf +#undef len + return TRUE; +} + +/* For the full gory details, see linux/Documentation/firmware_class/README + * + * Firmware loading works like this: + * - kernel sets FIRMWARE env var + * - userspace checks /lib/firmware/$FIRMWARE + * - userspace waits for /sys/$DEVPATH/loading to appear + * - userspace writes "1" to /sys/$DEVPATH/loading + * - userspace copies /lib/firmware/$FIRMWARE into /sys/$DEVPATH/data + * - userspace writes "0" (worked) or "-1" (failed) to /sys/$DEVPATH/loading + * - kernel loads firmware into device + */ +static void load_firmware(const char *firmware, const char *devpath) +{ + int cnt; + int firmware_fd, loading_fd, data_fd; + + /* check for /lib/firmware/$FIRMWARE */ + xchdir("/lib/firmware"); + firmware_fd = xopen(firmware, O_RDONLY); + + /* in case we goto out ... */ + data_fd = -1; + + /* check for /sys/$DEVPATH/loading ... give 30 seconds to appear */ + xchdir("/sys"); xchdir(devpath + 1); // +1 to skip leading '/' + for (cnt = 0; cnt < 30; ++cnt) { + loading_fd = open("loading", O_WRONLY); + if (loading_fd != -1) + goto loading; + sleep(1); + } + goto out; + + loading: + /* tell kernel we're loading by "echo 1 > /sys/$DEVPATH/loading" */ + if (full_write(loading_fd, "1", 1) != 1) + goto out; + + /* 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" */ + if (cnt > 0) + full_write(loading_fd, "0", 1); + else + full_write(loading_fd, "-1", 2); + + out: + if (ENABLE_FEATURE_CLEAN_UP) { + close(firmware_fd); + close(loading_fd); + close(data_fd); + } +} + +int ndev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ndev_main(int argc UNUSED_PARAM, char **argv) +{ + RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); + + // force the configuration file settings exactly + umask(0); + + xchdir("/dev"); + + // parse options + getopt32(argv, "csS"); + + // coldplug branch + if (option_mask32 & 6/*-s or -S*/) { + recursive_action("/sys/block", + ACTION_RECURSE, + NULL, dirAction, temp, 0); + recursive_action("/sys/class", + ACTION_RECURSE, + NULL, dirAction, temp, 0); + if (option_mask32 & 4/*-S*/) { + recursive_action("/sys/devices", + ACTION_RECURSE, + NULL, dirAction, temp, 0); + } + // uevent listener + } else { + + struct sockaddr_nl sa; + struct pollfd pfd; + + // subscribe for UEVENT kernel messages + close(STDIN_FILENO); + xsocket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); // will return STDIN_FILENO + + sa.nl_family = AF_NETLINK; + sa.nl_pad = 0; + sa.nl_pid = getpid(); + sa.nl_groups = 1 << 0; + xbind(STDIN_FILENO, (struct sockaddr *) &sa, sizeof(sa)); + + // setup signals + bb_signals(0 + + (1 << SIGHUP) + + (1 << SIGINT) + + (1 << SIGTERM) + , record_signo); + + // dump any string chunks we read from stdin + pfd.fd = STDIN_FILENO; + pfd.events = POLLIN; + + while (!bb_got_signal && poll(&pfd, 1, -1) > 0) { + char *s = temp; + ssize_t len = recv(STDIN_FILENO, temp, PATH_MAX + SCRATCH_SIZE, MSG_DONTWAIT); + if (len < 0) { + // N.B. really don't know what to do: bail out, or just warn? + bb_perror_msg("recv"); + continue; + } + + while (len && *s) { + int i = strlen(s) + 1; + //puts(s); + if (0 == strncmp(s, "add@/", 5) || 0 == strncmp(s, "remove@/", 8) || 0 == strncmp(s, "change@/", 8)) { + clearenv(); + } else if (strchr(s, '=')) { + putenv(s); + if (0 == strncmp(s, "SEQNUM=", 7)) { + char *fw = getenv("FIRMWARE"); + char *action = getenv("ACTION"); + char *devpath = getenv("DEVPATH"); + char *subsystem = getenv("SUBSYSTEM"); + if (action && devpath && subsystem) { + if (strcmp(action, "remove") == 0) { + /* Ignoring "remove firmware". It was reported + * to happen and to cause erroneous deletion + * of device nodes. */ + if (!fw) + hotplug(1); + } else if (strcmp(action, "add") == 0) { + hotplug(0); + if (fw) + load_firmware(fw, devpath); + } else { + // TODO: change event: what to do? + } + } + } + } + s += i; + len -= i; + } + } + } + + if (ENABLE_FEATURE_CLEAN_UP) + RELEASE_CONFIG_BUFFER(temp); + + return bb_got_signal; +}