diff options
Diffstat (limited to 'util-linux')
-rw-r--r-- | util-linux/uevent.c | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/util-linux/uevent.c b/util-linux/uevent.c new file mode 100644 index 000000000..fb98b4845 --- /dev/null +++ b/util-linux/uevent.c @@ -0,0 +1,127 @@ +/* + * Copyright 2015 Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this source tree. + */ + +//config:config UEVENT +//config: bool "uevent" +//config: default y +//config: select PLATFORM_LINUX +//config: help +//config: uevent is a netlink listener for kernel uevent notifications +//config: sent via netlink. It is usually used for dynamic device creation. + +//applet:IF_UEVENT(APPLET(uevent, BB_DIR_SBIN, BB_SUID_DROP)) + +//kbuild:lib-$(CONFIG_UEVENT) += uevent.o + +//usage:#define uevent_trivial_usage +//usage: "[PROG [ARGS]]" +//usage:#define uevent_full_usage "\n\n" +//usage: "uevent runs PROG for every netlink notification." +//usage: "\n""PROG's environment contains data passed from the kernel." +//usage: "\n""Typical usage (daemon for dynamic device node creation):" +//usage: "\n"" # uevent mdev & mdev -s" + +#include "libbb.h" +#include <linux/netlink.h> + +#define BUFFER_SIZE 16*1024 + +#define env ((char **)&bb_common_bufsiz1) +enum { + MAX_ENV = COMMON_BUFSIZE / sizeof(env[0]) - 1, +}; + +#ifndef SO_RCVBUFFORCE +#define SO_RCVBUFFORCE 33 +#endif +static const int RCVBUF = 2 * 1024 * 1024; + +int uevent_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int uevent_main(int argc UNUSED_PARAM, char **argv) +{ + struct sockaddr_nl sa; + int fd; + + argv++; + + // 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: + // uevent mdev & + // find /sys -name uevent -exec sh -c 'echo add >"{}"' ';' + // + // SO_RCVBUFFORCE (root only) can go above net.core.rmem_max sysctl + setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &RCVBUF, sizeof(RCVBUF)); + setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &RCVBUF, sizeof(RCVBUF)); + if (0) { + int z; + socklen_t zl = sizeof(z); + getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &z, &zl); + bb_error_msg("SO_RCVBUF:%d", z); + } + + for (;;) { + char *netbuf; + char *s, *end; + ssize_t len; + int idx; + + // In many cases, a system sits for *days* waiting + // for a new uevent notification to come in. + // We use a fresh mmap so that buffer is not allocated + // until kernel actually starts filling it. + netbuf = mmap(NULL, BUFFER_SIZE, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + /* ignored: */ -1, 0); + if (netbuf == MAP_FAILED) + bb_perror_msg_and_die("mmap"); + + // Here we block, possibly for a very long time + len = safe_read(fd, netbuf, BUFFER_SIZE - 1); + if (len < 0) + bb_perror_msg_and_die("read"); + end = netbuf + len; + *end = '\0'; + + // Each netlink message starts with "ACTION@/path" + // (which we currently ignore), + // followed by environment variables. + if (!argv[0]) + putchar('\n'); + idx = 0; + s = netbuf; + while (s < end) { + if (!argv[0]) + puts(s); + if (strchr(s, '=') && idx < MAX_ENV) + env[idx++] = s; + s += strlen(s) + 1; + } + env[idx] = NULL; + + idx = 0; + while (env[idx]) + putenv(env[idx++]); + if (argv[0]) + spawn_and_wait(argv); + idx = 0; + while (env[idx]) + bb_unsetenv(env[idx++]); + munmap(netbuf, BUFFER_SIZE); + } + + return 0; // not reached +} |