[git commit] mdev: add support to run as daemon

Denys Vlasenko vda.linux at googlemail.com
Mon Jun 3 11:24:49 UTC 2019


commit: https://git.busybox.net/busybox/commit/?id=498cec202adbf69a7a72af5e204260682d614183
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

Adds the -d option to run mdev in daemon mode handling hotplug events
from the kernel like udev. If the system generates many hotplug events
this mode of operation will consume less resources than registering
mdev as hotplug helper or using the uevent applet.

function                                             old     new   delta
daemon_loop                                            -     152    +152
initial_scan                                           -     127    +127
open_mdev_log                                          -      85     +85
mdev_main                                            255     314     +59
packed_usage                                       33284   33316     +32
process_action                                      1051     992     -59
------------------------------------------------------------------------------
(add/remove: 3/0 grow/shrink: 2/1 up/down: 455/-59)           Total: 396 bytes

Signed-off-by: Jan Klötzke <jan at kloetzke.net>
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 util-linux/mdev.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file 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());


More information about the busybox-cvs mailing list