1/*****************************************************************************\
2*  _  _       _          _              ___                                   *
3* | || | ___ | |_  _ __ | | _  _  __ _ |_  )                                  *
4* | __ |/ _ \|  _|| '_ \| || || |/ _` | / /                                   *
5* |_||_|\___/ \__|| .__/|_| \_,_|\__, |/___|                                  *
6*                 |_|            |___/                                        *
7\*****************************************************************************/
8
9#define _GNU_SOURCE
10
11#include <fcntl.h>
12#include <stdio.h>
13#include <unistd.h>
14#include <stdlib.h>
15#include <errno.h>
16#include <sys/socket.h>
17#include <sys/types.h>
18#include <sys/un.h>
19#include <sys/wait.h>
20#include <sys/stat.h>
21#include <sys/mman.h>
22#include <linux/types.h>
23#include <linux/netlink.h>
24
25#include "mem_utils.h"
26#include "hotplug2.h"
27#include "rules.h"
28#include "childlist.h"
29
30#define TERMCONDITION (persistent == 0 && \
31					coldplug_p == FORK_FINISHED && \
32					child == NULL && \
33					highest_seqnum == get_kernel_seqnum())
34
35event_seqnum_t highest_seqnum = 0;
36pid_t coldplug_p;
37int coldplug = 1;
38int persistent = 0;
39int max_child_c = 20;
40int dumb = 0;
41int terminate = 0;
42int child_c;
43struct hotplug2_child_t *child;
44int netlink_socket;
45
46char *modprobe_command = NULL;
47
48inline void free_hotplug2_event(struct hotplug2_event_t *event) {
49	int i;
50
51	for (i = 0; i < event->env_vars_c; i++) {
52		free(event->env_vars[i].key);
53		free(event->env_vars[i].value);
54	}
55	free(event->env_vars);
56	free(event->plain);
57	free(event);
58}
59
60inline int get_hotplug2_event_action(char *action) {
61	if (!strcmp(action, "add"))
62		return ACTION_ADD;
63
64	if (!strcmp(action, "remove"))
65		return ACTION_REMOVE;
66
67	return ACTION_UNKNOWN;
68}
69
70char *get_hotplug2_value_by_key(struct hotplug2_event_t *event, char *key) {
71	int i;
72
73	for (i = 0; i < event->env_vars_c; i++) {
74		if (!strcmp(event->env_vars[i].key, key))
75			return event->env_vars[i].value;
76	}
77
78	return NULL;
79}
80
81inline int add_hotplug2_event_env(struct hotplug2_event_t *event, char *item) {
82	char *ptr, *tmp;
83
84	ptr = strchr(item, '=');
85	if (ptr == NULL)
86		return -1;
87
88	*ptr='\0';
89
90	event->env_vars_c++;
91	event->env_vars = xrealloc(event->env_vars, sizeof(struct env_var_t) * event->env_vars_c);
92	event->env_vars[event->env_vars_c - 1].key = strdup(item);
93	event->env_vars[event->env_vars_c - 1].value = strdup(ptr + 1);
94
95	/*
96	 * Variables not generated by kernel but demanded nonetheless...
97	 */
98	if (!strcmp(item, "DEVPATH")) {
99		event->env_vars_c++;
100		event->env_vars = xrealloc(event->env_vars, sizeof(struct env_var_t) * event->env_vars_c);
101		event->env_vars[event->env_vars_c - 1].key = strdup("DEVICENAME");
102		tmp = strdup(ptr + 1);
103		event->env_vars[event->env_vars_c - 1].value = strdup(basename(tmp));
104		free(tmp);
105	}
106
107	*ptr='=';
108
109	return 0;
110}
111
112inline struct hotplug2_event_t *dup_hotplug2_event(struct hotplug2_event_t *src) {
113	struct hotplug2_event_t *dest;
114	int i;
115
116	dest = xmalloc(sizeof(struct hotplug2_event_t));
117	dest->action = src->action;
118	dest->env_vars_c = src->env_vars_c;
119	dest->env_vars = xmalloc(sizeof(struct env_var_t) * dest->env_vars_c);
120	dest->plain_s = src->plain_s;
121	dest->plain = xmalloc(dest->plain_s);
122	memcpy(dest->plain, src->plain, dest->plain_s);
123
124	for (i = 0; i < src->env_vars_c; i++) {
125		dest->env_vars[i].key = strdup(src->env_vars[i].key);
126		dest->env_vars[i].value = strdup(src->env_vars[i].value);
127	}
128
129	return dest;
130}
131
132inline struct hotplug2_event_t *get_hotplug2_event(char *event_str, int size) {
133	char *ptr;
134	struct hotplug2_event_t *event;
135	int skip;
136
137	ptr = strchr(event_str, '@');
138	if (ptr == NULL) {
139		return NULL;
140	}
141	*ptr='\0';
142
143	event = xmalloc(sizeof(struct hotplug2_event_t));
144	event->action = get_hotplug2_event_action(event_str);
145	event->env_vars_c = 0;
146	event->env_vars = NULL;
147	event->plain_s = size;
148	event->plain = xmalloc(size);
149	memcpy(event->plain, event_str, size);
150
151	skip = ++ptr - event_str;
152	size -= skip;
153
154	while (size > 0) {
155		add_hotplug2_event_env(event, ptr);
156		skip = strlen(ptr);
157		ptr += skip + 1;
158		size -= skip + 1;
159	}
160
161	return event;
162}
163
164inline event_seqnum_t get_kernel_seqnum() {
165	FILE *fp;
166
167	char filename[64];
168	char seqnum[64];
169
170	strcpy(filename, sysfs_seqnum_path);
171
172	fp = fopen(filename, "r");
173	if (fp == NULL)
174		return 0;
175
176	fread(seqnum, 1, 64, fp);
177	fclose(fp);
178
179	return strtoull(seqnum, NULL, 0);
180}
181
182inline int init_netlink_socket() {
183	int netlink_socket;
184	struct sockaddr_nl snl;
185	int buffersize = 16 * 1024 * 1024;
186
187	memset(&snl, 0x00, sizeof(struct sockaddr_nl));
188	snl.nl_family = AF_NETLINK;
189	snl.nl_pid = getpid();
190	snl.nl_groups = 1;
191	netlink_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
192	if (netlink_socket == -1) {
193		ERROR("opening netlink","Failed socket: %s.", strerror(errno));
194		return -1;
195	}
196
197	if (setsockopt(netlink_socket, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize))) {
198		ERROR("opening netlink","Failed setsockopt: %s. (non-critical)", strerror(errno));
199
200		/* Somewhat safe default. */
201		buffersize = 106496;
202
203		if (setsockopt(netlink_socket, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize))) {
204			ERROR("opening netlink","Failed setsockopt: %s. (critical)", strerror(errno));
205		}
206	}
207
208	if (bind(netlink_socket, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl))) {
209		ERROR("opening netlink","Failed bind: %s.", strerror(errno));
210		close(netlink_socket);
211		return -1;
212	}
213
214	return netlink_socket;
215}
216
217int get_bool_opt(char *argv, char *name, int *value) {
218	int rv = -1;
219
220	if (!strncmp(argv, "--no-", 5)) {
221		rv = 0;
222		argv+=5;
223	}
224
225	if (!strncmp(argv, "--", 2)) {
226		rv = 1;
227		argv+=2;
228	}
229
230	if (rv == -1)
231		return -1;
232
233	if (!strcmp(argv, name)) {
234		*value = rv;
235		return 0;
236	} else {
237		return -1;
238	}
239}
240
241void cleanup(void) {
242	pid_t p;
243
244	close(netlink_socket);
245
246	signal(SIGUSR1, SIG_DFL);
247	signal(SIGINT, SIG_DFL);
248	signal(SIGCHLD, SIG_DFL);
249
250	INFO("cleanup", "Waiting for children.");
251	/* Wait for our possible children... */
252	while ((p = wait(NULL)) != -1)
253		DBG("cleanup", "pid: %d.", p);
254	INFO("cleanup", "All children terminated.");
255}
256
257void sighandler(int sig) {
258	pid_t p;
259
260	switch (sig) {
261		/*
262		 * SIGINT simply tells that yes, we caught the signal, and
263		 * exits.
264		 */
265		case SIGINT:
266			INFO("sighandler", "Obtained SIGINT, quitting.");
267			cleanup();
268			exit(0);
269			break;
270
271		/*
272		 * SIGUSR1 is handled so that if we have turned off persistency
273		 * and have processed all events, we quit.
274		 */
275		case SIGUSR1:
276			persistent = !persistent;
277			INFO("sighandler", "Changed persistency to: %s", persistent ? "yes" : "no");
278			if (TERMCONDITION) {
279				INFO("sighandler", "No more events to be processed, quitting.");
280				cleanup();
281				exit(0);
282			}
283			break;
284
285		/*
286		 * SIGCHLD helps us to figure out when a child died and
287		 * what kind of child it was. It may also invoke termination.
288		 */
289		case SIGCHLD:
290			while (1) {
291				p = waitpid (WAIT_ANY, NULL, WNOHANG);
292				if (p <= 0)
293					break;
294
295				DBG("sighandler", "caught pid: %d.", p);
296				if (p == coldplug_p) {
297					DBG("sighandler", "coldplug_p: %d.", coldplug_p);
298					coldplug_p = FORK_FINISHED;
299				} else {
300					child = remove_child_by_pid(child, p, NULL, &child_c);
301				}
302
303				DBG("sighandler", "child_c: %d, child: %p, highest_seqnum: %lld, cur_seqnum: %lld, coldplug_p: %d.\n", child_c, child, highest_seqnum, get_kernel_seqnum(), coldplug_p);
304			}
305
306			if (TERMCONDITION) {
307				INFO("sighandler", "No more events to be processed, quitting.");
308				cleanup();
309				exit(0);
310			}
311			break;
312	}
313}
314
315#ifdef HAVE_RULES
316void perform_action(struct hotplug2_event_t *event, struct rules_t *rules) {
317	int i, rv;
318
319	for (i = 0; i < rules->rules_c; i++) {
320		rv = rule_execute(event, &rules->rules[i]);
321		if (rv == -1)
322			break;
323	}
324
325	free_hotplug2_event(event);
326}
327#endif
328
329void perform_dumb_action(struct hotplug2_event_t *event, char *modalias) {
330	free_hotplug2_event(event);
331	execl(modprobe_command, modprobe_command, "-q", modalias, NULL);
332}
333
334int get_modprobe_command() {
335	pid_t p;
336	int fds[2];
337	char buf[18];
338	FILE *fp;
339
340	pipe(fds);
341
342	p = fork();
343
344	switch (p) {
345		case -1:
346			ERROR("modprobe_command","Unable to fork.");
347			return -1;
348			break;
349
350		case 0:
351			close(fds[0]);
352			close(2);
353			dup2(fds[1], 1);
354
355			execlp("/sbin/modprobe", "/sbin/modprobe", "--version", NULL);
356			exit(1);
357			break;
358
359		default:
360			close(fds[1]);
361			fp = fdopen(fds[0], "r");
362			fread(buf, 1, 17, fp);
363			buf[17]='\0';
364
365			/*
366			 * module-init-tools can handle aliases.
367			 * If we do not have a match, we use hotplug2-depwrap,
368			 * even though our modprobe can do fnmatch aliases,
369			 * which is the case of eg. busybox.
370			 */
371			if (!strcmp(buf, "module-init-tools")) {
372				modprobe_command = "/sbin/modprobe";
373			} else {
374				modprobe_command = "/sbin/hotplug2-depwrap";
375			}
376			fclose(fp);
377			waitpid(p, NULL, 0);
378			break;
379	}
380	return 0;
381}
382
383int main(int argc, char *argv[]) {
384	static char buffer[UEVENT_BUFFER_SIZE+512];
385	struct hotplug2_event_t *tmpevent;
386	char *modalias, *seqnum;
387	event_seqnum_t cur_seqnum;
388	pid_t p;
389	int recv_errno;
390	int size;
391	int rv = 0;
392	int i;
393	char *coldplug_command = NULL;
394	sigset_t block_mask;
395
396	struct rules_t *rules = NULL;
397	struct stat statbuf;
398	void *filemap;
399	int rule_fd;
400
401	struct options_t bool_options[] = {
402		{"persistent", &persistent},
403		{"coldplug", &coldplug},
404		{"udevtrigger", &coldplug},	/* compatibility */
405#ifdef HAVE_RULES
406		{"dumb", &dumb},
407#endif
408		{NULL, NULL}
409	};
410
411	for (argc--; argc > 0; argc--) {
412		argv++;
413		for (i = 0; bool_options[i].name != NULL; i++) {
414			if (!get_bool_opt(*argv, bool_options[i].name, bool_options[i].value)) {
415				break;
416			} else {
417				if (!strcmp(*argv, "--max-children")) {
418					argv++;
419					argc--;
420					if (argc <= 0)
421						break;
422
423					max_child_c = strtol(*argv, NULL, 0);
424				} else if (!strcmp(*argv, "--set-coldplug-cmd")) {
425					argv++;
426					argc--;
427					if (argc <= 0)
428						break;
429
430					coldplug_command = *argv;
431				} else if (!strcmp(*argv, "--set-modprobe-cmd")) {
432					argv++;
433					argc--;
434					if (argc <= 0)
435						break;
436
437					modprobe_command = *argv;
438				}
439			}
440		}
441	}
442
443#ifdef HAVE_RULES
444	if (!dumb) {
445		filemap = MAP_FAILED;
446		rule_fd = open(HOTPLUG2_RULE_PATH, O_RDONLY | O_NOATIME);
447		if (rule_fd == -1) {
448			dumb = 1;
449			ERROR("rules parse","Unable to open rules file: %s.", strerror(errno));
450			goto end_rules;
451		}
452
453		if (fstat(rule_fd, &statbuf)) {
454			dumb = 1;
455			ERROR("rules parse","Unable to stat rules file: %s.", strerror(errno));
456			goto end_rules;
457		}
458
459		filemap = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, rule_fd, 0);
460		if (filemap == MAP_FAILED) {
461			dumb = 1;
462			ERROR("rules parse","Unable to mmap rules file: %s.", strerror(errno));
463			goto end_rules;
464		}
465
466		rules = rules_from_config((char*)filemap);
467		if (rules == NULL) {
468			ERROR("rules parse","Unable to parse rules file.");
469			dumb = 1;
470		}
471
472end_rules:
473		if (filemap != MAP_FAILED)
474			munmap(filemap, statbuf.st_size);
475		if (rule_fd != -1)
476			close(rule_fd);
477
478		if (dumb == 1)
479			ERROR("rules parse","Parsing rules failed, switching to dumb mode.");
480	} else if (!modprobe_command)
481#else
482	if (dumb && !modprobe_command)
483#endif
484	{
485		if (get_modprobe_command()) {
486			ERROR("modprobe_command","Unable to autodetect modprobe command.");
487			goto exit;
488		}
489		DBG("modprobe_command", "Using modprobe: `%s'.", modprobe_command);
490	}
491
492	netlink_socket = init_netlink_socket();
493
494	if (netlink_socket == -1) {
495		ERROR("netlink init","Unable to open netlink socket.");
496		goto exit;
497	}
498
499	child = NULL;
500	child_c = 0;
501
502	signal(SIGUSR1, sighandler);
503	signal(SIGINT, sighandler);
504	signal(SIGCHLD, sighandler);
505
506	if (coldplug) {
507		if (coldplug_command == NULL)
508			coldplug_command = UDEVTRIGGER_COMMAND;
509		coldplug_p = fork();
510		switch (coldplug_p) {
511			case FORK_ERROR:
512				ERROR("coldplug","Coldplug fork failed: %s.", strerror(errno));
513				perror("coldplug fork failed");
514				goto exit;
515				break;
516			case 0:
517				execlp(coldplug_command, coldplug_command, NULL);
518				ERROR("coldplug","Coldplug exec ('%s') failed: %s.", coldplug_command, strerror(errno));
519				goto exit;
520				break;
521		}
522	} else {
523		coldplug_p = FORK_FINISHED;
524	}
525
526	while (!terminate) {
527		size = recv(netlink_socket, &buffer, sizeof(buffer), 0);
528		recv_errno = errno;
529
530		tmpevent = get_hotplug2_event(buffer, size);
531
532		if (tmpevent == NULL) {
533			ERROR("reading events", "Malformed event read (missing action prefix).");
534			continue;
535		}
536
537		modalias = get_hotplug2_value_by_key(tmpevent, "MODALIAS");
538		seqnum = get_hotplug2_value_by_key(tmpevent, "SEQNUM");
539
540		if (seqnum == NULL) {
541			free_hotplug2_event(tmpevent);
542			ERROR("reading events", "Malformed event read (missing SEQNUM).");
543			continue;
544		}
545
546		cur_seqnum = strtoull(seqnum, NULL, 0);
547		if (cur_seqnum > highest_seqnum)
548			highest_seqnum = cur_seqnum;
549
550		if (tmpevent->action == ACTION_ADD && (!dumb || modalias != NULL)) {
551			/*
552			 * We have more children than we want. Wait until SIGCHLD handler reduces
553			 * their numbers.
554			 */
555			while (child_c >= max_child_c) {
556				usleep(HOTPLUG2_THROTTLE_INTERVAL);
557			}
558
559			sigemptyset(&block_mask);
560			sigaddset(&block_mask, SIGCHLD);
561			sigprocmask(SIG_BLOCK, &block_mask, 0);
562			p = fork();
563			switch (p) {
564				case -1:
565					ERROR("event","fork failed: %s.", strerror(errno));
566					break;
567				case 0:
568					sigprocmask(SIG_UNBLOCK, &block_mask, 0);
569					signal(SIGCHLD, SIG_DFL);
570					signal(SIGUSR1, SIG_DFL);
571#ifdef HAVE_RULES
572					if (!dumb)
573						perform_action(dup_hotplug2_event(tmpevent), rules);
574					else
575#endif
576						perform_dumb_action(dup_hotplug2_event(tmpevent), modalias);
577					exit(0);
578					break;
579				default:
580					DBG("spawn", "spawning: %d.", p);
581					child = add_child(child, p, cur_seqnum);
582					child_c++;
583					break;
584			}
585			sigprocmask(SIG_UNBLOCK, &block_mask, 0);
586		}
587
588		free_hotplug2_event(tmpevent);
589	}
590
591exit:
592	signal(SIGUSR1, SIG_DFL);
593	signal(SIGINT, SIG_DFL);
594	signal(SIGCHLD, SIG_DFL);
595
596#ifdef HAVE_RULES
597	if (!dumb) {
598		rules_free(rules);
599		free(rules);
600	}
601#endif
602
603	cleanup();
604
605	return rv;
606}
607