summaryrefslogtreecommitdiffhomepage
path: root/util-linux/uevent.c
blob: 252e8fb6411159e15daf99d0fad5e2938f099d56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
 * Copyright 2015 Denys Vlasenko
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
//config:config UEVENT
//config:	bool "uevent (3.2 kb)"
//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 "common_bufsiz.h"
#include <linux/netlink.h>

#define BUFFER_SIZE 16*1024

#define env ((char **)bb_common_bufsiz1)
#define INIT_G() do { setup_common_bufsiz(); } while (0)
enum {
	MAX_ENV = COMMON_BUFSIZE / sizeof(env[0]) - 1,
};

#ifndef SO_RCVBUFFORCE
#define SO_RCVBUFFORCE 33
#endif
enum { 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;

	INIT_G();

	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_SOL_SOCKET_int(fd, SO_RCVBUF,      RCVBUF);
	setsockopt_SOL_SOCKET_int(fd, SO_RCVBUFFORCE, 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
}