uevent: new applet
No binary (build failed)
—
(log yok)
| @@ -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 |
| + | } |