//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"
#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
#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 };
}
}
-static NOINLINE void process_action(char *temp, unsigned my_pid)
+static void process_action(char *temp, unsigned my_pid)
{
char *fw;
char *seq;
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);
}
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();
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());