Hotplug2: process rules on "remove" events
[tomato.git] / release / src / router / hotplug2 / hotplug2.c
blobb78c27cfacc7cc025bf1c76ea8e594fb5f61ad86
1 /*****************************************************************************\
2 * _ _ _ _ ___ *
3 * | || | ___ | |_ _ __ | | _ _ __ _ |_ ) *
4 * | __ |/ _ \| _|| '_ \| || || |/ _` | / / *
5 * |_||_|\___/ \__|| .__/|_| \_,_|\__, |/___| *
6 * |_| |___/ *
7 \*****************************************************************************/
9 #define _GNU_SOURCE
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>
25 #include "mem_utils.h"
26 #include "hotplug2.h"
27 #include "rules.h"
28 #include "childlist.h"
30 #define TERMCONDITION (persistent == 0 && \
31 coldplug_p == FORK_FINISHED && \
32 child == NULL && \
33 highest_seqnum == get_kernel_seqnum())
35 event_seqnum_t highest_seqnum = 0;
36 pid_t coldplug_p;
37 int coldplug = 1;
38 int persistent = 0;
39 int max_child_c = 20;
40 int dumb = 0;
41 int terminate = 0;
42 int child_c;
43 struct hotplug2_child_t *child;
44 int netlink_socket;
46 char *modprobe_command = NULL;
48 inline void free_hotplug2_event(struct hotplug2_event_t *event) {
49 int i;
51 for (i = 0; i < event->env_vars_c; i++) {
52 free(event->env_vars[i].key);
53 free(event->env_vars[i].value);
55 free(event->env_vars);
56 free(event->plain);
57 free(event);
60 inline int get_hotplug2_event_action(char *action) {
61 if (!strcmp(action, "add"))
62 return ACTION_ADD;
64 if (!strcmp(action, "remove"))
65 return ACTION_REMOVE;
67 return ACTION_UNKNOWN;
70 char *get_hotplug2_value_by_key(struct hotplug2_event_t *event, char *key) {
71 int i;
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;
78 return NULL;
81 inline int add_hotplug2_event_env(struct hotplug2_event_t *event, char *item) {
82 char *ptr, *tmp;
84 ptr = strchr(item, '=');
85 if (ptr == NULL)
86 return -1;
88 *ptr='\0';
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);
96 * Variables not generated by kernel but demanded nonetheless...
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);
107 *ptr='=';
109 return 0;
112 inline struct hotplug2_event_t *dup_hotplug2_event(struct hotplug2_event_t *src) {
113 struct hotplug2_event_t *dest;
114 int i;
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);
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);
129 return dest;
132 inline struct hotplug2_event_t *get_hotplug2_event(char *event_str, int size) {
133 char *ptr;
134 struct hotplug2_event_t *event;
135 int skip;
137 ptr = strchr(event_str, '@');
138 if (ptr == NULL) {
139 return NULL;
141 *ptr='\0';
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);
151 skip = ++ptr - event_str;
152 size -= skip;
154 while (size > 0) {
155 add_hotplug2_event_env(event, ptr);
156 skip = strlen(ptr);
157 ptr += skip + 1;
158 size -= skip + 1;
161 return event;
164 inline event_seqnum_t get_kernel_seqnum() {
165 FILE *fp;
167 char filename[64];
168 char seqnum[64];
170 strcpy(filename, sysfs_seqnum_path);
172 fp = fopen(filename, "r");
173 if (fp == NULL)
174 return 0;
176 fread(seqnum, 1, 64, fp);
177 fclose(fp);
179 return strtoull(seqnum, NULL, 0);
182 inline int init_netlink_socket() {
183 int netlink_socket;
184 struct sockaddr_nl snl;
185 int buffersize = 16 * 1024 * 1024;
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;
197 if (setsockopt(netlink_socket, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize))) {
198 ERROR("opening netlink","Failed setsockopt: %s. (non-critical)", strerror(errno));
200 /* Somewhat safe default. */
201 buffersize = 106496;
203 if (setsockopt(netlink_socket, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize))) {
204 ERROR("opening netlink","Failed setsockopt: %s. (critical)", strerror(errno));
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;
214 return netlink_socket;
217 int get_bool_opt(char *argv, char *name, int *value) {
218 int rv = -1;
220 if (!strncmp(argv, "--no-", 5)) {
221 rv = 0;
222 argv+=5;
225 if (!strncmp(argv, "--", 2)) {
226 rv = 1;
227 argv+=2;
230 if (rv == -1)
231 return -1;
233 if (!strcmp(argv, name)) {
234 *value = rv;
235 return 0;
236 } else {
237 return -1;
241 void cleanup(void) {
242 pid_t p;
244 close(netlink_socket);
246 signal(SIGUSR1, SIG_DFL);
247 signal(SIGINT, SIG_DFL);
248 signal(SIGCHLD, SIG_DFL);
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.");
257 void sighandler(int sig) {
258 pid_t p;
260 switch (sig) {
262 * SIGINT simply tells that yes, we caught the signal, and
263 * exits.
265 case SIGINT:
266 INFO("sighandler", "Obtained SIGINT, quitting.");
267 cleanup();
268 exit(0);
269 break;
272 * SIGUSR1 is handled so that if we have turned off persistency
273 * and have processed all events, we quit.
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);
283 break;
286 * SIGCHLD helps us to figure out when a child died and
287 * what kind of child it was. It may also invoke termination.
289 case SIGCHLD:
290 while (1) {
291 p = waitpid (WAIT_ANY, NULL, WNOHANG);
292 if (p <= 0)
293 break;
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);
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);
306 if (TERMCONDITION) {
307 INFO("sighandler", "No more events to be processed, quitting.");
308 cleanup();
309 exit(0);
311 break;
315 #ifdef HAVE_RULES
316 void perform_action(struct hotplug2_event_t *event, struct rules_t *rules) {
317 int i, rv;
319 for (i = 0; i < rules->rules_c; i++) {
320 rv = rule_execute(event, &rules->rules[i]);
321 if (rv == -1)
322 break;
325 free_hotplug2_event(event);
327 #endif
329 void perform_dumb_action(struct hotplug2_event_t *event, char *modalias) {
330 free_hotplug2_event(event);
331 execl(modprobe_command, modprobe_command, "-q", modalias, NULL);
334 int get_modprobe_command() {
335 pid_t p;
336 int fds[2];
337 char buf[18];
338 FILE *fp;
340 pipe(fds);
342 p = fork();
344 switch (p) {
345 case -1:
346 ERROR("modprobe_command","Unable to fork.");
347 return -1;
348 break;
350 case 0:
351 close(fds[0]);
352 close(2);
353 dup2(fds[1], 1);
355 execlp("/sbin/modprobe", "/sbin/modprobe", "--version", NULL);
356 exit(1);
357 break;
359 default:
360 close(fds[1]);
361 fp = fdopen(fds[0], "r");
362 fread(buf, 1, 17, fp);
363 buf[17]='\0';
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.
371 if (!strcmp(buf, "module-init-tools")) {
372 modprobe_command = "/sbin/modprobe";
373 } else {
374 modprobe_command = "/sbin/hotplug2-depwrap";
376 fclose(fp);
377 waitpid(p, NULL, 0);
378 break;
380 return 0;
383 int 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;
396 struct rules_t *rules = NULL;
397 struct stat statbuf;
398 void *filemap;
399 int rule_fd;
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}
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;
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;
430 coldplug_command = *argv;
431 } else if (!strcmp(*argv, "--set-modprobe-cmd")) {
432 argv++;
433 argc--;
434 if (argc <= 0)
435 break;
437 modprobe_command = *argv;
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;
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;
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;
466 rules = rules_from_config((char*)filemap);
467 if (rules == NULL) {
468 ERROR("rules parse","Unable to parse rules file.");
469 dumb = 1;
472 end_rules:
473 if (filemap != MAP_FAILED)
474 munmap(filemap, statbuf.st_size);
475 if (rule_fd != -1)
476 close(rule_fd);
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
485 if (get_modprobe_command()) {
486 ERROR("modprobe_command","Unable to autodetect modprobe command.");
487 goto exit;
489 DBG("modprobe_command", "Using modprobe: `%s'.", modprobe_command);
492 netlink_socket = init_netlink_socket();
494 if (netlink_socket == -1) {
495 ERROR("netlink init","Unable to open netlink socket.");
496 goto exit;
499 child = NULL;
500 child_c = 0;
502 signal(SIGUSR1, sighandler);
503 signal(SIGINT, sighandler);
504 signal(SIGCHLD, sighandler);
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;
522 } else {
523 coldplug_p = FORK_FINISHED;
526 while (!terminate) {
527 size = recv(netlink_socket, &buffer, sizeof(buffer), 0);
528 recv_errno = errno;
529 if (size < 0)
530 continue;
532 tmpevent = get_hotplug2_event(buffer, size);
534 if (tmpevent == NULL) {
535 ERROR("reading events", "Malformed event read (missing action prefix).");
536 continue;
539 modalias = get_hotplug2_value_by_key(tmpevent, "MODALIAS");
540 seqnum = get_hotplug2_value_by_key(tmpevent, "SEQNUM");
542 if (seqnum == NULL) {
543 free_hotplug2_event(tmpevent);
544 ERROR("reading events", "Malformed event read (missing SEQNUM).");
545 continue;
548 cur_seqnum = strtoull(seqnum, NULL, 0);
549 if (cur_seqnum > highest_seqnum)
550 highest_seqnum = cur_seqnum;
552 if (tmpevent->action == ACTION_REMOVE || (!dumb || modalias != NULL)) {
554 * We have more children than we want. Wait until SIGCHLD handler reduces
555 * their numbers.
557 while (child_c >= max_child_c) {
558 usleep(HOTPLUG2_THROTTLE_INTERVAL);
561 sigemptyset(&block_mask);
562 sigaddset(&block_mask, SIGCHLD);
563 sigprocmask(SIG_BLOCK, &block_mask, 0);
564 p = fork();
565 switch (p) {
566 case -1:
567 ERROR("event","fork failed: %s.", strerror(errno));
568 break;
569 case 0:
570 sigprocmask(SIG_UNBLOCK, &block_mask, 0);
571 signal(SIGCHLD, SIG_DFL);
572 signal(SIGUSR1, SIG_DFL);
573 #ifdef HAVE_RULES
574 if (!dumb)
575 perform_action(dup_hotplug2_event(tmpevent), rules);
576 else if (tmpevent->action == ACTION_ADD)
577 #endif
578 perform_dumb_action(dup_hotplug2_event(tmpevent), modalias);
579 exit(0);
580 break;
581 default:
582 DBG("spawn", "spawning: %d.", p);
583 child = add_child(child, p, cur_seqnum);
584 child_c++;
585 break;
587 sigprocmask(SIG_UNBLOCK, &block_mask, 0);
590 free_hotplug2_event(tmpevent);
593 exit:
594 signal(SIGUSR1, SIG_DFL);
595 signal(SIGINT, SIG_DFL);
596 signal(SIGCHLD, SIG_DFL);
598 #ifdef HAVE_RULES
599 if (!dumb) {
600 rules_free(rules);
601 free(rules);
603 #endif
605 cleanup();
607 return rv;