diff options
Diffstat (limited to 'util-linux')
-rw-r--r-- | util-linux/mdev.c | 137 |
1 files changed, 120 insertions, 17 deletions
diff --git a/util-linux/mdev.c b/util-linux/mdev.c index e730548f7..88c82b6fb 100644 --- a/util-linux/mdev.c +++ b/util-linux/mdev.c @@ -64,15 +64,30 @@ //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: +//config:config FEATURE_MDEV_DAEMON +//config: bool "Support daemon mode" +//config: default y +//config: depends on MDEV +//config: help +//config: Adds the -d option to run mdev in daemon mode handling hotplug +//config: events from the kernel like udev. If the system generates many +//config: hotplug events this mode of operation will consume less +//config: resources than registering mdev as hotplug helper or using the +//config: uevent applet. //applet:IF_MDEV(APPLET(mdev, BB_DIR_SBIN, BB_SUID_DROP)) //kbuild:lib-$(CONFIG_MDEV) += mdev.o //usage:#define mdev_trivial_usage -//usage: "[-s]" +//usage: "[-s]" IF_FEATURE_MDEV_DAEMON(" | [-df]") //usage:#define mdev_full_usage "\n\n" //usage: "mdev -s is to be run during boot to scan /sys and populate /dev.\n" +//usage: IF_FEATURE_MDEV_DAEMON( +//usage: "mdev -d[f]: daemon, listen on netlink.\n" +//usage: " -f: stay in foreground.\n" +//usage: ) //usage: "\n" //usage: "Bare mdev is a kernel hotplug helper. To activate it:\n" //usage: " echo /sbin/mdev >/proc/sys/kernel/hotplug\n" @@ -98,6 +113,7 @@ #include "libbb.h" #include "common_bufsiz.h" #include "xregex.h" +#include <linux/netlink.h> /* "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 @@ -249,10 +265,9 @@ #endif -enum { - MDEV_OPT_SCAN = 1 << 0, -}; - +#ifndef SO_RCVBUFFORCE +#define SO_RCVBUFFORCE 33 +#endif static const char keywords[] ALIGN1 = "add\0remove\0"; // "change\0" enum { OP_add, OP_remove }; @@ -1051,7 +1066,7 @@ static void signal_mdevs(unsigned my_pid) } } -static NOINLINE void process_action(char *temp, unsigned my_pid) +static void process_action(char *temp, unsigned my_pid) { char *fw; char *seq; @@ -1077,10 +1092,11 @@ static NOINLINE void process_action(char *temp, unsigned my_pid) seq = getenv("SEQNUM"); op = index_in_strings(keywords, action); - open_mdev_log(seq, my_pid); + if (my_pid) + open_mdev_log(seq, my_pid); seq_fd = -1; - if (seq) { + if (my_pid && seq) { seqnum = atoll(seq); seq_fd = wait_for_seqfile(seqnum); } @@ -1131,18 +1147,60 @@ static void initial_scan(char *temp) fileAction, dirAction, temp, 0); } +#if ENABLE_FEATURE_MDEV_DAEMON + +/* uevent applet uses 16k buffer, and mmaps it before every read */ +# define BUFFER_SIZE (2 * 1024) +# define RCVBUF (2 * 1024 * 1024) +# define MAX_ENV 32 + +static void daemon_loop(char *temp, int fd) +{ + for (;;) { + char netbuf[BUFFER_SIZE]; + char *env[MAX_ENV]; + char *s, *end; + ssize_t len; + int idx; + + len = safe_read(fd, netbuf, sizeof(netbuf) - 1); + if (len < 0) { + bb_perror_msg_and_die("read"); + } + end = netbuf + len; + *end = '\0'; + + idx = 0; + s = netbuf; + while (s < end && idx < MAX_ENV) { + if (endofname(s)[0] == '=') { + env[idx++] = s; + putenv(s); + } + s += strlen(s) + 1; + } + + process_action(temp, 0); + + while (idx) + bb_unsetenv(env[--idx]); + } +} +#endif + int mdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int mdev_main(int argc UNUSED_PARAM, char **argv) { + enum { + MDEV_OPT_SCAN = 1 << 0, + MDEV_OPT_DAEMON = 1 << 1, + MDEV_OPT_FOREGROUND = 1 << 2, + }; int opt; RESERVE_CONFIG_BUFFER(temp, PATH_MAX + SCRATCH_SIZE); INIT_G(); -#if ENABLE_FEATURE_MDEV_CONF - G.filename = "/etc/mdev.conf"; -#endif - /* We can be called as hotplug helper */ /* Kernel cannot provide suitable stdio fds for us, do it ourself */ bb_sanitize_stdio(); @@ -1152,17 +1210,62 @@ int mdev_main(int argc UNUSED_PARAM, char **argv) xchdir("/dev"); - opt = getopt32(argv, "s"); + opt = getopt32(argv, "s" IF_FEATURE_MDEV_DAEMON("df")); - if (opt & MDEV_OPT_SCAN) { - /* - * Scan: mdev -s - */ #if ENABLE_FEATURE_MDEV_CONF + G.filename = "/etc/mdev.conf"; + if (opt & (MDEV_OPT_SCAN|MDEV_OPT_DAEMON)) { /* Same as xrealloc_vector(NULL, 4, 0): */ G.rule_vec = xzalloc((1 << 4) * sizeof(*G.rule_vec)); + } #endif +#if ENABLE_FEATURE_MDEV_DAEMON + if (opt & MDEV_OPT_DAEMON) { + /* + * Daemon mode listening on uevent netlink socket. + */ + struct sockaddr_nl sa; + int fd; + +//TODO: reuse same code in uevent + // Subscribe for UEVENT kernel messages + sa.nl_family = AF_NETLINK; + sa.nl_pad = 0; + sa.nl_pid = getpid(); + sa.nl_groups = 1 << 0; + fd = xsocket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + xbind(fd, (struct sockaddr *) &sa, sizeof(sa)); + close_on_exec_on(fd); + + // Without a sufficiently big RCVBUF, a ton of simultaneous events + // can trigger ENOBUFS on read, which is unrecoverable. + // Reproducer: + // mdev -d + // find /sys -name uevent -exec sh -c 'echo add >"{}"' ';' + // + // SO_RCVBUFFORCE (root only) can go above net.core.rmem_max sysctl + setsockopt_SOL_SOCKET_int(fd, SO_RCVBUF, RCVBUF); + setsockopt_SOL_SOCKET_int(fd, SO_RCVBUFFORCE, RCVBUF); + + /* + * Make inital scan after the uevent socket is alive and + * _before_ we fork away. + */ + initial_scan(temp); + + if (!(opt & MDEV_OPT_FOREGROUND)) + bb_daemonize_or_rexec(0, argv); + + open_mdev_log(NULL, getpid()); + + daemon_loop(temp, fd); + } +#endif + if (opt & MDEV_OPT_SCAN) { + /* + * Scan: mdev -s + */ initial_scan(temp); } else { process_action(temp, getpid()); |