mdev: add support to run as daemon
No binary (build failed)
—
(log yok)
| @@ -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()); | |