On Mon, Jun 1, 2009 at 21:39, Kay Sievers <kay.sievers@xxxxxxxx> wrote: > On Mon, Jun 1, 2009 at 18:24, Alan Jenkins <alan-jenkins@xxxxxxxxxxxxxx> wrote: > >> I don't think the signal handler should be doing list traversal, in case it >> interrupts list manipulation in the main loop. Or perhaps the main loop >> should only allow signals during ppoll(). > > Make sense. we can block them during list mangling, or just go for > signalfd, and convert all the signal handling to it. Here is a version that uses signalfd(), and a socketpair() from the workers to pass data back. Same numbers so far, but the code looks a lot simpler. Kay
diff --git a/configure.ac b/configure.ac index f1d008e..f126146 100644 --- a/configure.ac +++ b/configure.ac @@ -23,10 +23,6 @@ AC_SUBST(LIBUDEV_LT_AGE) AC_PATH_PROG([XSLTPROC], [xsltproc]) -AC_CHECK_LIB(c, inotify_init, - [AC_DEFINE([HAVE_INOTIFY], 1, [inotify available])], - [AC_MSG_WARN([inotify support disabled])]) - AC_ARG_WITH(udev-prefix, AS_HELP_STRING([--with-udev-prefix=DIR], [add prefix to internal udev path names]), [], [with_udev_prefix='${exec_prefix}']) diff --git a/udev/Makefile.am b/udev/Makefile.am index 6cd2f23..94989e6 100644 --- a/udev/Makefile.am +++ b/udev/Makefile.am @@ -14,7 +14,6 @@ common_ldadd = common_files = \ udev.h \ - udev-sysdeps.h \ udev-event.c \ udev-watch.c \ udev-node.c \ diff --git a/udev/lib/libudev-monitor.c b/udev/lib/libudev-monitor.c index 395a4d2..54c9576 100644 --- a/udev/lib/libudev-monitor.c +++ b/udev/lib/libudev-monitor.c @@ -32,7 +32,6 @@ struct udev_monitor { int refcount; int sock; struct sockaddr_nl snl; - struct sockaddr_nl snl_peer; struct sockaddr_un sun; socklen_t addrlen; struct udev_list_node filter_subsystem_list; @@ -171,8 +170,8 @@ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char return NULL; if (name == NULL) - return NULL; - if (strcmp(name, "kernel") == 0) + group = 0; + else if (strcmp(name, "kernel") == 0) group = UDEV_MONITOR_KERNEL; else if (strcmp(name, "udev") == 0) group = UDEV_MONITOR_UDEV; @@ -193,8 +192,6 @@ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char udev_monitor->snl.nl_family = AF_NETLINK; udev_monitor->snl.nl_groups = group; - udev_monitor->snl_peer.nl_family = AF_NETLINK; - udev_monitor->snl_peer.nl_groups = UDEV_MONITOR_UDEV; dbg(udev, "monitor %p created with NETLINK_KOBJECT_UEVENT (%u)\n", udev_monitor, group); return udev_monitor; @@ -434,7 +431,6 @@ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monito struct iovec iov; char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; struct cmsghdr *cmsg; - struct sockaddr_nl snl; struct ucred *cred; char buf[8192]; ssize_t buflen; @@ -459,11 +455,6 @@ retry: smsg.msg_control = cred_msg; smsg.msg_controllen = sizeof(cred_msg); - if (udev_monitor->snl.nl_family != 0) { - smsg.msg_name = &snl; - smsg.msg_namelen = sizeof(snl); - } - buflen = recvmsg(udev_monitor->sock, &smsg, 0); if (buflen < 0) { if (errno != EINTR) @@ -476,20 +467,6 @@ retry: return NULL; } - if (udev_monitor->snl.nl_family != 0) { - if (snl.nl_groups == 0) { - info(udev_monitor->udev, "unicast netlink message ignored\n"); - return NULL; - } - if (snl.nl_groups == UDEV_MONITOR_KERNEL) { - if (snl.nl_pid > 0) { - info(udev_monitor->udev, "multicast kernel netlink message from pid %d ignored\n", snl.nl_pid); - return NULL; - } - is_kernel = 1; - } - } - cmsg = CMSG_FIRSTHDR(&smsg); if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { info(udev_monitor->udev, "no sender credentials received, message ignored\n"); @@ -621,7 +598,7 @@ retry: return udev_device; } -int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_device *udev_device) +int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_device *udev_device, pid_t pid) { struct msghdr smsg; struct iovec iov[2]; @@ -660,6 +637,7 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_devi } else if (udev_monitor->snl.nl_family != 0) { const char *val; struct udev_monitor_netlink_header nlh; + struct sockaddr_nl snl_peer; /* add versioned header */ @@ -680,11 +658,18 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_devi iov[1].iov_base = (char *)buf; iov[1].iov_len = blen; + /* we will always get ECONNREFUSED when sending to the muticast group */ + memset(&snl_peer, 0x00, sizeof(struct sockaddr_nl)); + snl_peer.nl_family = AF_NETLINK; + if (pid > 0) + snl_peer.nl_pid = pid; + else + snl_peer.nl_groups = UDEV_MONITOR_UDEV; + memset(&smsg, 0x00, sizeof(struct msghdr)); smsg.msg_iov = iov; smsg.msg_iovlen = 2; - /* no destination besides the muticast group, we will always get ECONNREFUSED */ - smsg.msg_name = &udev_monitor->snl_peer; + smsg.msg_name = &snl_peer; smsg.msg_namelen = sizeof(struct sockaddr_nl); } else { return -1; diff --git a/udev/lib/libudev-private.h b/udev/lib/libudev-private.h index dc02a84..e600802 100644 --- a/udev/lib/libudev-private.h +++ b/udev/lib/libudev-private.h @@ -86,7 +86,7 @@ int udev_device_delete_db(struct udev_device *udev_device); int udev_device_rename_db(struct udev_device *udev_device, const char *devpath); /* libudev-monitor - netlink/unix socket communication */ -int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_device *udev_device); +int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_device *udev_device, pid_t pid); int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size); /* libudev-ctrl - daemon runtime setup */ diff --git a/udev/udev-event.c b/udev/udev-event.c index d521251..8ab262a 100644 --- a/udev/udev-event.c +++ b/udev/udev-event.c @@ -734,18 +734,13 @@ int udev_event_execute_run(struct udev_event *event) monitor = udev_monitor_new_from_socket(event->udev, &cmd[strlen("socket:")]); if (monitor == NULL) continue; - udev_monitor_send_device(monitor, event->dev); + udev_monitor_send_device(monitor, event->dev, 0); udev_monitor_unref(monitor); } else { char program[UTIL_PATH_SIZE]; char **envp; udev_event_apply_format(event, cmd, program, sizeof(program)); - if (event->trace) - fprintf(stderr, "run %s (%llu) '%s'\n", - udev_device_get_syspath(event->dev), - udev_device_get_seqnum(event->dev), - program); envp = udev_device_get_properties_envp(event->dev); if (util_run_program(event->udev, program, envp, NULL, 0, NULL) != 0) { if (!udev_list_entry_get_flag(list_entry)) diff --git a/udev/udev-sysdeps.h b/udev/udev-sysdeps.h deleted file mode 100644 index 35671ba..0000000 --- a/udev/udev-sysdeps.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * wrapping of libc features and kernel interfaces - * - * Copyright (C) 2005-2008 Kay Sievers <kay.sievers@xxxxxxxx> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef _UDEV_SYSDEPS_H_ -#define _UDEV_SYSDEPS_H_ - -#include <stdint.h> -#include <errno.h> - -#ifndef HAVE_INOTIFY -static inline int inotify_init(void) -{ - errno = ENOSYS; - return -1; -} - -static inline int inotify_add_watch(int fd, const char *name, uint32_t mask) -{ - return -1; -} - -#define IN_CREATE 0 -#define IN_DELETE 0 -#define IN_MOVE 0 -#define IN_CLOSE_WRITE 0 - -#endif /* HAVE_INOTIFY */ -#endif diff --git a/udev/udev-watch.c b/udev/udev-watch.c index 53492e5..5a49c96 100644 --- a/udev/udev-watch.c +++ b/udev/udev-watch.c @@ -26,27 +26,24 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> -#ifdef HAVE_INOTIFY #include <sys/inotify.h> -#endif #include "udev.h" -int inotify_fd = -1; +static int inotify_fd = -1; /* inotify descriptor, will be shared with rules directory; * set to cloexec since we need our children to be able to add * watches for us */ -void udev_watch_init(struct udev *udev) +int udev_watch_init(struct udev *udev) { inotify_fd = inotify_init(); if (inotify_fd >= 0) util_set_fd_cloexec(inotify_fd); - else if (errno == ENOSYS) - info(udev, "unable to use inotify, udevd will not monitor rule files changes\n"); else err(udev, "inotify_init failed: %m\n"); + return inotify_fd; } /* move any old watches directory out of the way, and then restore diff --git a/udev/udev.h b/udev/udev.h index 8f2c1c6..7187975 100644 --- a/udev/udev.h +++ b/udev/udev.h @@ -22,7 +22,6 @@ #include <sys/types.h> #include <sys/param.h> -#include "udev-sysdeps.h" #include "lib/libudev.h" #include "lib/libudev-private.h" @@ -53,7 +52,6 @@ static inline void logging_close(void) } struct udev_event { - struct udev_list_node node; struct udev *udev; struct udev_device *dev; struct udev_device *dev_parent; @@ -64,10 +62,6 @@ struct udev_event { uid_t uid; gid_t gid; struct udev_list_node run_list; - pid_t pid; - int exitstatus; - time_t queue_time; - unsigned long long int delaying_seqnum; unsigned int group_final:1; unsigned int owner_final:1; unsigned int mode_final:1; @@ -76,7 +70,6 @@ struct udev_event { unsigned int run_final:1; unsigned int ignore_device:1; unsigned int inotify_watch:1; - unsigned int trace:1; }; struct udev_watch { @@ -101,8 +94,7 @@ int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string, char *result, size_t maxsize, int read_value); /* udev-watch.c */ -extern int inotify_fd; -void udev_watch_init(struct udev *udev); +int udev_watch_init(struct udev *udev); void udev_watch_restore(struct udev *udev); void udev_watch_begin(struct udev *udev, struct udev_device *dev); void udev_watch_end(struct udev *udev, struct udev_device *dev); diff --git a/udev/udevd.c b/udev/udevd.c index 37b547a..7e6d244 100644 --- a/udev/udevd.c +++ b/udev/udevd.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@xxxxxxxx> + * Copyright (C) 2004-2009 Kay Sievers <kay.sievers@xxxxxxxx> * Copyright (C) 2004 Chris Friesen <chris_friesen@xxxxxxxxxxxx> * Copyright (C) 2009 Canonical Ltd. * Copyright (C) 2009 Scott James Remnant <scott@xxxxxxxxxxxx> @@ -30,23 +30,21 @@ #include <time.h> #include <getopt.h> #include <dirent.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/signalfd.h> #include <sys/select.h> #include <sys/poll.h> #include <sys/wait.h> #include <sys/stat.h> #include <sys/ioctl.h> -#ifdef HAVE_INOTIFY #include <sys/inotify.h> -#endif #include "udev.h" #define UDEVD_PRIORITY -4 #define UDEV_PRIORITY -2 -/* maximum limit of forked childs */ -#define UDEVD_MAX_CHILDS 256 - static int debug; static void log_fn(struct udev *udev, int priority, @@ -61,163 +59,311 @@ static void log_fn(struct udev *udev, int priority, } } -static void reap_sigchilds(void); - static int debug_trace; static struct udev_rules *rules; static struct udev_queue_export *udev_queue_export; static struct udev_ctrl *udev_ctrl; -static struct udev_monitor *kernel_monitor; -static volatile sig_atomic_t sigchilds_waiting; -static volatile sig_atomic_t udev_exit; -static volatile sig_atomic_t reload_config; -static volatile sig_atomic_t signal_received; -static volatile pid_t settle_pid; -static int run_exec_q; -static int stop_exec_q; +static struct udev_monitor *monitor; +static int worker_socket[2]; +static pid_t settle_pid; +static int stop_exec_queue; +static int reload_config; static int max_childs; static int childs; static struct udev_list_node event_list; +static struct udev_list_node worker_list; +static int udev_exit; +static volatile sig_atomic_t worker_exit; + +enum poll_fd { + FD_CONTROL, + FD_NETLINK, + FD_INOTIFY, + FD_SIGNAL, + FD_WORKER, +}; + +static struct pollfd pfd[] = { + [FD_NETLINK] = { .events = POLLIN }, + [FD_WORKER] = { .events = POLLIN }, + [FD_SIGNAL] = { .events = POLLIN }, + [FD_INOTIFY] = { .events = POLLIN }, + [FD_CONTROL] = { .events = POLLIN }, +}; + +enum event_state { + EVENT_UNDEF, + EVENT_QUEUED, + EVENT_RUNNING, +}; + +struct event { + struct udev_list_node node; + struct udev *udev; + struct udev_device *dev; + enum event_state state; + int exitcode; + unsigned long long int delaying_seqnum; +}; -static struct udev_event *node_to_event(struct udev_list_node *node) +static struct event *node_to_event(struct udev_list_node *node) { char *event; event = (char *)node; - event -= offsetof(struct udev_event, node); - return (struct udev_event *)event; + event -= offsetof(struct event, node); + return (struct event *)event; +} + +enum worker_state { + WORKER_UNDEF, + WORKER_RUNNING, + WORKER_IDLE, + WORKER_KILLED, +}; + +struct worker { + struct udev_list_node node; + pid_t pid; + enum worker_state state; + struct event *event; +}; + +/* passed from worker to main process */ +struct worker_message { + pid_t pid; + int exitcode; +}; + +static struct worker *node_to_worker(struct udev_list_node *node) +{ + char *worker; + + worker = (char *)node; + worker -= offsetof(struct worker, node); + return (struct worker *)worker; } -static void event_queue_delete(struct udev_event *event) +static void event_queue_delete(struct event *event) { udev_list_node_remove(&event->node); /* mark as failed, if "add" event returns non-zero */ - if (event->exitstatus && strcmp(udev_device_get_action(event->dev), "add") == 0) + if (event->exitcode && strcmp(udev_device_get_action(event->dev), "add") == 0) udev_queue_export_device_failed(udev_queue_export, event->dev); else udev_queue_export_device_finished(udev_queue_export, event->dev); udev_device_unref(event->dev); - udev_event_unref(event); + free(event); } static void event_sig_handler(int signum) { - if (signum == SIGALRM) + switch (signum) { + case SIGALRM: _exit(1); + break; + case SIGTERM: + worker_exit = 1; + break; + } } -static void event_fork(struct udev_event *event) +static void worker_new(struct event *event) { + struct worker *worker; pid_t pid; struct sigaction act; - int err; - -#if 0 - /* single process, no forking, just for testing/profiling */ - err = udev_event_execute_rules(event, rules); - if (err == 0 && !event->ignore_device && udev_get_run(event->udev)) - udev_event_execute_run(event); - info(event->udev, "seq %llu exit with %i\n", udev_device_get_seqnum(event->dev), err); - event_queue_delete(event); - return; -#endif - if (debug_trace) { - event->trace = 1; - fprintf(stderr, "fork %s (%llu)\n", - udev_device_get_syspath(event->dev), - udev_device_get_seqnum(event->dev)); - } + worker = calloc(1, sizeof(struct worker)); + if (worker == NULL) + return; pid = fork(); switch (pid) { - case 0: - /* child */ + case 0: { + sigset_t mask; + struct udev_device *dev; + udev_queue_export_unref(udev_queue_export); udev_ctrl_unref(udev_ctrl); + udev_monitor_unref(monitor); + close(pfd[FD_SIGNAL].fd); + close(worker_socket[READ_END]); logging_close(); - logging_init("udevd-event"); + logging_init("udevd-work"); setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY); + /* re-open socket to listen to udevd only, and send back libudev events */ + monitor = udev_monitor_new_from_netlink(event->udev, NULL); + if (monitor == NULL) + _exit(2); + udev_monitor_enable_receiving(monitor); + /* set signal handlers */ memset(&act, 0x00, sizeof(act)); act.sa_handler = event_sig_handler; sigemptyset (&act.sa_mask); act.sa_flags = 0; + sigaction(SIGTERM, &act, NULL); sigaction(SIGALRM, &act, NULL); - /* reset to default */ - act.sa_handler = SIG_DFL; - sigaction(SIGINT, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigaction(SIGCHLD, &act, NULL); - sigaction(SIGHUP, &act, NULL); + /* unblock signals */ + sigfillset(&mask); + sigdelset(&mask, SIGTERM); + sigdelset(&mask, SIGALRM); + sigprocmask(SIG_SETMASK, &mask, NULL); - /* set timeout to prevent hanging processes */ - alarm(UDEV_EVENT_TIMEOUT); + /* request TERM signal if main daemon exits */ + prctl(PR_SET_PDEATHSIG, SIGTERM); - /* apply rules, create node, symlinks */ - err = udev_event_execute_rules(event, rules); + /* initial device */ + dev = event->dev; - /* rules may change/disable the timeout */ - if (udev_device_get_event_timeout(event->dev) >= 0) - alarm(udev_device_get_event_timeout(event->dev)); + while (!worker_exit) { + struct udev_event *udev_event; + struct worker_message msg; + int err; - /* execute RUN= */ - if (err == 0 && !event->ignore_device && udev_get_run(event->udev)) - udev_event_execute_run(event); + udev_event = udev_event_new(dev); + if (udev_event == NULL) + _exit(3); - /* apply/restore inotify watch */ - if (err == 0 && event->inotify_watch) { - udev_watch_begin(event->udev, event->dev); - udev_device_update_db(event->dev); - } + /* set timeout to prevent hanging processes */ + alarm(UDEV_EVENT_TIMEOUT); + + /* apply rules, create node, symlinks */ + err = udev_event_execute_rules(udev_event, rules); + + /* rules may change/disable the timeout */ + if (udev_device_get_event_timeout(dev) >= 0) + alarm(udev_device_get_event_timeout(dev)); + + /* execute RUN= */ + if (err == 0 && !udev_event->ignore_device && udev_get_run(udev_event->udev)) + udev_event_execute_run(udev_event); + + /* reset alarm */ + alarm(0); + + /* apply/restore inotify watch */ + if (err == 0 && udev_event->inotify_watch) { + udev_watch_begin(udev_event->udev, dev); + udev_device_update_db(dev); + } + + /* send processed event back to libudev listeners */ + udev_monitor_send_device(monitor, dev, 0); + + info(event->udev, "seq %llu finished with %i\n", udev_device_get_seqnum(dev), err); + udev_device_unref(dev); + udev_event_unref(udev_event); + + /* send back the result of the event execution */ + msg.exitcode = err; + msg.pid = getpid(); + send(worker_socket[WRITE_END], &msg, sizeof(struct worker_message), 0); - /* send processed event back to the kernel netlink socket */ - udev_monitor_send_device(kernel_monitor, event->dev); + /* wait for more device messages from udevd */ + do + dev = udev_monitor_receive_device(monitor); + while (!worker_exit && dev == NULL); + } - info(event->udev, "seq %llu exit with %i\n", udev_device_get_seqnum(event->dev), err); + udev_monitor_unref(monitor); logging_close(); - if (err != 0) - exit(1); exit(0); + } case -1: + event->state = EVENT_QUEUED; + free(worker); err(event->udev, "fork of child failed: %m\n"); - event_queue_delete(event); break; default: - /* get SIGCHLD in main loop */ - info(event->udev, "seq %llu forked, pid [%d], '%s' '%s', %ld seconds old\n", - udev_device_get_seqnum(event->dev), - pid, - udev_device_get_action(event->dev), - udev_device_get_subsystem(event->dev), - time(NULL) - event->queue_time); - event->pid = pid; + event->state = EVENT_RUNNING; + worker->event = event; + worker->pid = pid; + worker->state = WORKER_RUNNING; + udev_list_node_append(&worker->node, &worker_list); childs++; + break; } } -static void event_queue_insert(struct udev_event *event) +static void event_run(struct event *event) { - event->queue_time = time(NULL); + struct udev_list_node *loop; - udev_queue_export_device_queued(udev_queue_export, event->dev); - info(event->udev, "seq %llu queued, '%s' '%s'\n", udev_device_get_seqnum(event->dev), - udev_device_get_action(event->dev), udev_device_get_subsystem(event->dev)); + udev_list_node_foreach(loop, &worker_list) { + struct worker *worker = node_to_worker(loop); + ssize_t count; + if (worker->state != WORKER_IDLE) + continue; + + worker->event = event; + worker->state = WORKER_RUNNING; + event->state = EVENT_RUNNING; + count = udev_monitor_send_device(monitor, event->dev, worker->pid); + if (count < 0) { + err(event->udev, "worker [%u] did not accept message, kill it\n", worker->pid); + event->state = EVENT_QUEUED; + worker->state = WORKER_KILLED; + kill(worker->pid, SIGKILL); + continue; + } + return; + } + + if (childs >= max_childs) { + info(event->udev, "maximum number (%i) of childs reached\n", childs); + return; + } + + /* start new worker and pass initial device */ + worker_new(event); +} + +static void event_queue_insert(struct udev_device *dev) +{ + struct event *event; + + event = calloc(1, sizeof(struct event)); + if (event == NULL) + return; + + event->udev = udev_device_get_udev(dev); + event->dev = dev; + udev_queue_export_device_queued(udev_queue_export, dev); + info(event->udev, "seq %llu queued, '%s' '%s'\n", udev_device_get_seqnum(dev), + udev_device_get_action(dev), udev_device_get_subsystem(dev)); + + event->state = EVENT_QUEUED; udev_list_node_append(&event->node, &event_list); - run_exec_q = 1; /* run all events with a timeout set immediately */ - if (udev_device_get_timeout(event->dev) > 0) { - event_fork(event); + if (udev_device_get_timeout(dev) > 0) { + worker_new(event); return; } } +static void worker_kill_idle(void) +{ + struct udev_list_node *loop; + + udev_list_node_foreach(loop, &worker_list) { + struct worker *worker = node_to_worker(loop); + + if (worker->state != WORKER_IDLE) + continue; + + worker->state = WORKER_KILLED; + kill(worker->pid, SIGTERM); + } +} + static int mem_size_mb(void) { FILE *f; @@ -265,13 +411,13 @@ static int compare_devpath(const char *running, const char *waiting) } /* lookup event for identical, parent, child device */ -static int devpath_busy(struct udev_event *event) +static int devpath_busy(struct event *event) { struct udev_list_node *loop; /* check if queue contains events we depend on */ udev_list_node_foreach(loop, &event_list) { - struct udev_event *loop_event = node_to_event(loop); + struct event *loop_event = node_to_event(loop); /* we already found a later event, earlier can not block us, no need to check again */ if (udev_device_get_seqnum(loop_event->dev) < event->delaying_seqnum) @@ -305,52 +451,6 @@ static int devpath_busy(struct udev_event *event) return 0; } -/* serializes events for the identical and parent and child devices */ -static void event_queue_manager(struct udev *udev) -{ - struct udev_list_node *loop; - struct udev_list_node *tmp; - -start_over: - if (udev_list_is_empty(&event_list)) { - if (childs > 0) { - err(udev, "event list empty, but childs count is %i", childs); - childs = 0; - } - return; - } - - udev_list_node_foreach_safe(loop, tmp, &event_list) { - struct udev_event *loop_event = node_to_event(loop); - - if (childs >= max_childs) { - info(udev, "maximum number (%i) of childs reached\n", childs); - break; - } - - if (loop_event->pid != 0) - continue; - - /* do not start event if parent or child event is still running */ - if (devpath_busy(loop_event) != 0) { - dbg(udev, "delay seq %llu (%s)\n", - udev_device_get_seqnum(loop_event->dev), - udev_device_get_devpath(loop_event->dev)); - continue; - } - - event_fork(loop_event); - dbg(udev, "moved seq %llu to running list\n", udev_device_get_seqnum(loop_event->dev)); - - /* retry if events finished in the meantime */ - if (sigchilds_waiting) { - sigchilds_waiting = 0; - reap_sigchilds(); - goto start_over; - } - } -} - /* receive the udevd message from userspace */ static void handle_ctrl_msg(struct udev_ctrl *uctrl) { @@ -371,13 +471,12 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl) if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) { info(udev, "udevd message (STOP_EXEC_QUEUE) received\n"); - stop_exec_q = 1; + stop_exec_queue = 1; } if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) { info(udev, "udevd message (START_EXEC_QUEUE) received\n"); - stop_exec_q = 0; - event_queue_manager(udev); + stop_exec_queue = 0; } if (udev_ctrl_get_reload_rules(ctrl_msg) > 0) { @@ -420,6 +519,8 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl) settle_pid = udev_ctrl_get_settle(ctrl_msg); if (settle_pid > 0) { info(udev, "udevd message (SETTLE) received\n"); + kill(settle_pid, SIGUSR1); + settle_pid = 0; } udev_ctrl_msg_unref(ctrl_msg); } @@ -427,22 +528,20 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl) /* read inotify messages */ static int handle_inotify(struct udev *udev) { - int nbytes, pos; + ssize_t nbytes, pos; char *buf; struct inotify_event *ev; - if ((ioctl(inotify_fd, FIONREAD, &nbytes) < 0) || (nbytes <= 0)) + if ((ioctl(pfd[FD_INOTIFY].fd, FIONREAD, &nbytes) < 0) || (nbytes <= 0)) return 0; buf = malloc(nbytes); if (buf == NULL) { err(udev, "error getting buffer for inotify, disable watching\n"); - close(inotify_fd); - inotify_fd = -1; - return 0; + return -1; } - read(inotify_fd, buf, nbytes); + nbytes = read(pfd[FD_INOTIFY].fd, buf, nbytes); for (pos = 0; pos < nbytes; pos += sizeof(struct inotify_event) + ev->len) { struct udev_device *dev; @@ -480,71 +579,6 @@ static int handle_inotify(struct udev *udev) return 0; } -static void sig_handler(int signum) -{ - switch (signum) { - case SIGINT: - case SIGTERM: - udev_exit = 1; - break; - case SIGCHLD: - /* set flag, then write to pipe if needed */ - sigchilds_waiting = 1; - break; - case SIGHUP: - reload_config = 1; - break; - } - - signal_received = 1; -} - -static void udev_done(int pid, int exitstatus) -{ - struct udev_list_node *loop; - - /* find event associated with pid and delete it */ - udev_list_node_foreach(loop, &event_list) { - struct udev_event *loop_event = node_to_event(loop); - - if (loop_event->pid == pid) { - info(loop_event->udev, "seq %llu cleanup, pid [%d], status %i, %ld seconds old\n", - udev_device_get_seqnum(loop_event->dev), loop_event->pid, - exitstatus, time(NULL) - loop_event->queue_time); - loop_event->exitstatus = exitstatus; - if (debug_trace) - fprintf(stderr, "exit %s (%llu)\n", - udev_device_get_syspath(loop_event->dev), - udev_device_get_seqnum(loop_event->dev)); - event_queue_delete(loop_event); - childs--; - - /* there may be dependent events waiting */ - run_exec_q = 1; - return; - } - } -} - -static void reap_sigchilds(void) -{ - pid_t pid; - int status; - - while (1) { - pid = waitpid(-1, &status, WNOHANG); - if (pid <= 0) - break; - if (WIFEXITED(status)) - status = WEXITSTATUS(status); - else if (WIFSIGNALED(status)) - status = WTERMSIG(status) + 128; - else - status = 0; - udev_done(pid, status); - } -} - static void startup_log(struct udev *udev) { FILE *f; @@ -576,7 +610,7 @@ int main(int argc, char *argv[]) { struct udev *udev; int fd; - struct sigaction act; + sigset_t mask; const char *value; int daemonize = 0; int resolve_names = 1; @@ -669,29 +703,76 @@ int main(int argc, char *argv[]) rc = 1; goto exit; } - if (udev_ctrl_enable_receiving(udev_ctrl) < 0) { fprintf(stderr, "error binding control socket, seems udevd is already running\n"); err(udev, "error binding control socket, seems udevd is already running\n"); rc = 1; goto exit; } + pfd[FD_CONTROL].fd = udev_ctrl_get_fd(udev_ctrl); - kernel_monitor = udev_monitor_new_from_netlink(udev, "kernel"); - if (kernel_monitor == NULL || udev_monitor_enable_receiving(kernel_monitor) < 0) { + monitor = udev_monitor_new_from_netlink(udev, "kernel"); + if (monitor == NULL || udev_monitor_enable_receiving(monitor) < 0) { fprintf(stderr, "error initializing netlink socket\n"); err(udev, "error initializing netlink socket\n"); rc = 3; goto exit; } - udev_monitor_set_receive_buffer_size(kernel_monitor, 128*1024*1024); + udev_monitor_set_receive_buffer_size(monitor, 128*1024*1024); + pfd[FD_NETLINK].fd = udev_monitor_get_fd(monitor); + + pfd[FD_INOTIFY].fd = udev_watch_init(udev); + if (pfd[FD_INOTIFY].fd < 0) { + fprintf(stderr, "error initializing inotify\n"); + err(udev, "error initializing inotify\n"); + rc = 4; + goto exit; + } + + if (udev_get_rules_path(udev) != NULL) { + inotify_add_watch(pfd[FD_NETLINK].fd, udev_get_rules_path(udev), + IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); + } else { + char filename[UTIL_PATH_SIZE]; + + inotify_add_watch(pfd[FD_NETLINK].fd, UDEV_PREFIX "/lib/udev/rules.d", + IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); + inotify_add_watch(pfd[FD_NETLINK].fd, SYSCONFDIR "/udev/rules.d", + IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); + + /* watch dynamic rules directory */ + util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/rules.d", NULL); + inotify_add_watch(pfd[FD_NETLINK].fd, filename, + IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); + } + udev_watch_restore(udev); + + /* block and listen to all signals on signalfd */ + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + pfd[FD_SIGNAL].fd = signalfd(-1, &mask, 0); + if (pfd[FD_SIGNAL].fd < 0) { + fprintf(stderr, "error getting signalfd\n"); + err(udev, "error getting signalfd\n"); + rc = 5; + goto exit; + } + + /* unnamed socket from workers to the main daemon */ + if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, worker_socket) < 0) { + fprintf(stderr, "error getting socketpair\n"); + err(udev, "error getting socketpair\n"); + rc = 6; + goto exit; + } + pfd[FD_WORKER].fd = worker_socket[READ_END]; rules = udev_rules_new(udev, resolve_names); if (rules == NULL) { err(udev, "error reading rules\n"); goto exit; } - udev_list_init(&event_list); + udev_queue_export = udev_queue_export_new(udev); if (udev_queue_export == NULL) { err(udev, "error creating queue file\n"); @@ -704,19 +785,19 @@ int main(int argc, char *argv[]) pid = fork(); switch (pid) { case 0: - dbg(udev, "daemonized fork running\n"); break; case -1: err(udev, "fork of daemon failed: %m\n"); rc = 4; goto exit; default: - dbg(udev, "child [%u] running, parent exits\n", pid); rc = 0; goto exit; } } + startup_log(udev); + /* redirect std{out,err} */ if (!debug && !debug_trace) { dup2(fd, STDIN_FILENO); @@ -742,159 +823,189 @@ int main(int argc, char *argv[]) close(fd); } - startup_log(udev); - - /* set signal handlers */ - memset(&act, 0x00, sizeof(struct sigaction)); - act.sa_handler = sig_handler; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_RESTART; - sigaction(SIGINT, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigaction(SIGCHLD, &act, NULL); - sigaction(SIGHUP, &act, NULL); - - /* watch rules directory */ - udev_watch_init(udev); - if (inotify_fd >= 0) { - if (udev_get_rules_path(udev) != NULL) { - inotify_add_watch(inotify_fd, udev_get_rules_path(udev), - IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); - } else { - char filename[UTIL_PATH_SIZE]; - - inotify_add_watch(inotify_fd, UDEV_PREFIX "/lib/udev/rules.d", - IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); - inotify_add_watch(inotify_fd, SYSCONFDIR "/udev/rules.d", - IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); - - /* watch dynamic rules directory */ - util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/rules.d", NULL); - inotify_add_watch(inotify_fd, filename, - IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); - } - - udev_watch_restore(udev); - } - /* in trace mode run one event after the other */ if (debug_trace) { max_childs = 1; } else { int memsize = mem_size_mb(); + if (memsize > 0) max_childs = 128 + (memsize / 4); else - max_childs = UDEVD_MAX_CHILDS; + max_childs = 256; } + /* possibly overwrite maximum limit of executed events */ value = getenv("UDEVD_MAX_CHILDS"); if (value) max_childs = strtoul(value, NULL, 10); info(udev, "initialize max_childs to %u\n", max_childs); + udev_list_init(&event_list); + udev_list_init(&worker_list); + while (!udev_exit) { - sigset_t blocked_mask, orig_mask; - struct pollfd pfd[4]; - struct pollfd *ctrl_poll, *monitor_poll, *inotify_poll = NULL; - int nfds = 0; int fdcount; + int timeout; - sigfillset(&blocked_mask); - sigprocmask(SIG_SETMASK, &blocked_mask, &orig_mask); - if (signal_received) { - sigprocmask(SIG_SETMASK, &orig_mask, NULL); - goto handle_signals; - } - - ctrl_poll = &pfd[nfds++]; - ctrl_poll->fd = udev_ctrl_get_fd(udev_ctrl); - ctrl_poll->events = POLLIN; - - monitor_poll = &pfd[nfds++]; - monitor_poll->fd = udev_monitor_get_fd(kernel_monitor); - monitor_poll->events = POLLIN; + /* set timeout to kill idle workers */ + if (udev_list_is_empty(&event_list) && !udev_list_is_empty(&worker_list)) + timeout = 10 * 1000; + else + timeout = -1; + /* wait for events */ + fdcount = poll(pfd, ARRAY_SIZE(pfd), timeout); + if (fdcount < 0) + continue; - if (inotify_fd >= 0) { - inotify_poll = &pfd[nfds++]; - inotify_poll->fd = inotify_fd; - inotify_poll->events = POLLIN; + /* timeout - kill idle workers */ + if (fdcount == 0) + worker_kill_idle(); + + /* event has finished */ + if (pfd[FD_WORKER].revents & POLLIN) { + while (1) { + struct worker_message msg; + ssize_t size; + struct udev_list_node *loop; + + size = recv(pfd[FD_WORKER].fd, &msg, sizeof(struct worker_message), MSG_DONTWAIT); + if (size != sizeof(struct worker_message)) + break; + + /* lookup worker who sent the signal */ + udev_list_node_foreach(loop, &worker_list) { + struct worker *worker = node_to_worker(loop); + + if (worker->pid != msg.pid) + continue; + + /* worker returned */ + worker->event->exitcode = msg.exitcode; + event_queue_delete(worker->event); + worker->event = NULL; + worker->state = WORKER_IDLE; + break; + } + } } - fdcount = ppoll(pfd, nfds, NULL, &orig_mask); - sigprocmask(SIG_SETMASK, &orig_mask, NULL); - if (fdcount < 0) { - if (errno == EINTR) - goto handle_signals; - err(udev, "error in select: %m\n"); - continue; + /* get kernel uevent */ + if (pfd[FD_NETLINK].revents & POLLIN) { + struct udev_device *dev; + + dev = udev_monitor_receive_device(monitor); + if (dev != NULL) + event_queue_insert(dev); + else + udev_device_unref(dev); } /* get control message */ - if (ctrl_poll->revents & POLLIN) + if (pfd[FD_CONTROL].revents & POLLIN) handle_ctrl_msg(udev_ctrl); - /* get kernel uevent */ - if (monitor_poll->revents & POLLIN) { - struct udev_device *dev; + /* start new events */ + if (!udev_list_is_empty(&event_list) && !stop_exec_queue) { + struct udev_list_node *loop; - dev = udev_monitor_receive_device(kernel_monitor); - if (dev != NULL) { - struct udev_event *event; + udev_list_node_foreach(loop, &event_list) { + struct event *event = node_to_event(loop); + + /* skip running events */ + if (event->state != EVENT_QUEUED) + continue; + + /* do not start event if parent or child event is still running */ + if (devpath_busy(event) != 0) { + dbg(udev, "delay seq %llu (%s)\n", + udev_device_get_seqnum(event->dev), + udev_device_get_devpath(event->dev)); + continue; + } - event = udev_event_new(dev); - if (event != NULL) - event_queue_insert(event); - else - udev_device_unref(dev); + event_run(event); } } - /* rules directory inotify watch */ - if (inotify_poll && (inotify_poll->revents & POLLIN)) - handle_inotify(udev); + /* get signal */ + if (pfd[FD_SIGNAL].revents & POLLIN) { + struct signalfd_siginfo fdsi; + ssize_t size; + + size = read(pfd[FD_SIGNAL].fd, &fdsi, sizeof(struct signalfd_siginfo)); + if (size != sizeof(struct signalfd_siginfo)) + continue; + switch (fdsi.ssi_signo) { + case SIGINT: + case SIGTERM: + udev_exit = 1; + break; + case SIGCHLD: + while (1) { + pid_t pid; + struct udev_list_node *loop, *tmp; + + pid = waitpid(-1, NULL, WNOHANG); + if (pid <= 0) + break; + + udev_list_node_foreach_safe(loop, tmp, &worker_list) { + struct worker *worker = node_to_worker(loop); + + if (worker->pid != pid) + continue; + + /* fail event, if worker died unexpectedly */ + if (worker->event != NULL) { + worker->event->exitcode = 127; + event_queue_delete(worker->event); + } + + udev_list_node_remove(&worker->node); + free(worker); + childs--; + break; + } + } + break; + case SIGHUP: + reload_config = 1; + break; + } + } -handle_signals: - signal_received = 0; + /* device node and rules directory inotify watch */ + if (pfd[FD_INOTIFY].revents & POLLIN) + handle_inotify(udev); /* rules changed, set by inotify or a HUP signal */ if (reload_config) { struct udev_rules *rules_new; - reload_config = 0; + worker_kill_idle(); rules_new = udev_rules_new(udev, resolve_names); if (rules_new != NULL) { udev_rules_unref(rules); rules = rules_new; } - } - - if (sigchilds_waiting) { - sigchilds_waiting = 0; - reap_sigchilds(); - } - - if (run_exec_q) { - run_exec_q = 0; - if (!stop_exec_q) - event_queue_manager(udev); - } - - if (settle_pid > 0) { - kill(settle_pid, SIGUSR1); - settle_pid = 0; + reload_config = 0; } } + udev_queue_export_cleanup(udev_queue_export); rc = 0; exit: - udev_queue_export_unref(udev_queue_export); udev_rules_unref(rules); udev_ctrl_unref(udev_ctrl); - if (inotify_fd >= 0) - close(inotify_fd); - udev_monitor_unref(kernel_monitor); + if (pfd[FD_SIGNAL].fd >= 0) + close(pfd[FD_SIGNAL].fd); + if (worker_socket[READ_END] >= 0) + close(worker_socket[READ_END]); + if (worker_socket[WRITE_END] >= 0) + close(worker_socket[WRITE_END]); + udev_monitor_unref(monitor); udev_selinux_exit(udev); udev_unref(udev); logging_close();