X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=udev%2Fudevd.c;h=60edea98e1144774ef5a5145d077aefc9d8c92b9;hp=244780ab57a02f7b93ab5edeb7ff9828ff48ff5f;hb=1f5a5100f3bdfdaf9ef71b29931574b678ff0d82;hpb=02bf3e13e00157c23f77a89a35cb3e51543d336e diff --git a/udev/udevd.c b/udev/udevd.c index 244780ab5..60edea98e 100644 --- a/udev/udevd.c +++ b/udev/udevd.c @@ -55,13 +55,12 @@ static void log_fn(struct udev *udev, int priority, { if (debug) { char buf[1024]; - struct timeval tv; - struct timezone tz; + struct timespec ts; vsnprintf(buf, sizeof(buf), format, args); - gettimeofday(&tv, &tz); - fprintf(stderr, "%llu.%06u [%u] %s: %s", - (unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec, + clock_gettime(CLOCK_MONOTONIC, &ts); + fprintf(stderr, "[%llu.%06u] [%u] %s: %s", + (unsigned long long) ts.tv_sec, (unsigned int) ts.tv_nsec/1000, (int) getpid(), fn, buf); } else { vsyslog(priority, format, args); @@ -81,11 +80,10 @@ static bool reload_config; static int children; static int children_max; static int exec_delay; -static sigset_t orig_sigmask; +static sigset_t sigmask_orig; static UDEV_LIST(event_list); static UDEV_LIST(worker_list); static bool udev_exit; -static volatile sig_atomic_t worker_exit; enum event_state { EVENT_UNDEF, @@ -157,29 +155,13 @@ static void event_queue_delete(struct event *event, bool export) udev_list_node_remove(&event->node); if (export) { - /* mark as failed, if "add" event returns non-zero */ - if (event->exitcode != 0 && strcmp(udev_device_get_action(event->dev), "remove") != 0) - udev_queue_export_device_failed(udev_queue_export, event->dev); - else - udev_queue_export_device_finished(udev_queue_export, event->dev); + udev_queue_export_device_finished(udev_queue_export, event->dev); info(event->udev, "seq %llu done with %i\n", udev_device_get_seqnum(event->dev), event->exitcode); } udev_device_unref(event->dev); free(event); } -static void event_sig_handler(int signum) -{ - switch (signum) { - case SIGALRM: - _exit(1); - break; - case SIGTERM: - worker_exit = true; - break; - } -} - static struct worker *worker_ref(struct worker *worker) { worker->refcount++; @@ -220,7 +202,6 @@ static void worker_new(struct event *event) struct worker *worker; struct udev_monitor *worker_monitor; pid_t pid; - struct sigaction act; /* listen for new events */ worker_monitor = udev_monitor_new_from_netlink(udev, NULL); @@ -242,12 +223,11 @@ static void worker_new(struct event *event) pid = fork(); switch (pid) { case 0: { - sigset_t sigmask; - struct udev_device *dev; - struct pollfd pmon = { - .fd = udev_monitor_get_fd(worker_monitor), - .events = POLLIN, - }; + struct udev_device *dev = NULL; + int fd_monitor; + struct epoll_event ep_signal, ep_monitor; + sigset_t mask; + int rc = EXIT_SUCCESS; /* move initial device from queue */ dev = event->dev; @@ -262,55 +242,64 @@ static void worker_new(struct event *event) close(fd_signal); close(fd_ep); close(worker_watch[READ_END]); - udev_log_close(); - udev_log_init("udevd-work"); - - /* 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); - - /* unblock SIGALRM */ - sigfillset(&sigmask); - sigdelset(&sigmask, SIGALRM); - sigprocmask(SIG_SETMASK, &sigmask, NULL); - /* SIGTERM is unblocked in ppoll() */ - sigdelset(&sigmask, SIGTERM); + + sigfillset(&mask); + fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (fd_signal < 0) { + err(udev, "error creating signalfd %m\n"); + rc = 2; + goto out; + } + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + err(udev, "error creating epoll fd: %m\n"); + rc = 3; + goto out; + } + + memset(&ep_signal, 0, sizeof(struct epoll_event)); + ep_signal.events = EPOLLIN; + ep_signal.data.fd = fd_signal; + + fd_monitor = udev_monitor_get_fd(worker_monitor); + memset(&ep_monitor, 0, sizeof(struct epoll_event)); + ep_monitor.events = EPOLLIN; + ep_monitor.data.fd = fd_monitor; + + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_monitor, &ep_monitor) < 0) { + err(udev, "fail to add fds to epoll: %m\n"); + rc = 4; + goto out; + } /* request TERM signal if parent exits */ prctl(PR_SET_PDEATHSIG, SIGTERM); - do { + for (;;) { struct udev_event *udev_event; - struct worker_message msg = {}; + struct worker_message msg; int err; - int failed = 0; info(udev, "seq %llu running\n", udev_device_get_seqnum(dev)); udev_event = udev_event_new(dev); - if (udev_event == NULL) - _exit(3); + if (udev_event == NULL) { + rc = 5; + goto out; + } - /* set timeout to prevent hanging processes */ - alarm(UDEV_EVENT_TIMEOUT); + /* needed for SIGCHLD/SIGTERM in spawn() */ + udev_event->fd_signal = fd_signal; if (exec_delay > 0) udev_event->exec_delay = exec_delay; /* 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)); + err = udev_event_execute_rules(udev_event, rules, &sigmask_orig); if (err == 0) - failed = udev_event_execute_run(udev_event, &orig_sigmask); - - alarm(0); + udev_event_execute_run(udev_event, &sigmask_orig); /* apply/restore inotify watch */ if (err == 0 && udev_event->inotify_watch) { @@ -322,41 +311,71 @@ static void worker_new(struct event *event) udev_monitor_send_device(worker_monitor, NULL, dev); /* send udevd the result of the event execution */ + memset(&msg, 0, sizeof(struct worker_message)); if (err != 0) msg.exitcode = err; - else if (failed != 0) - msg.exitcode = failed; msg.pid = getpid(); send(worker_watch[WRITE_END], &msg, sizeof(struct worker_message), 0); info(udev, "seq %llu processed with %i\n", udev_device_get_seqnum(dev), err); - udev_event_unref(udev_event); + udev_device_unref(dev); dev = NULL; - /* wait for more device messages or signal from udevd */ - while (!worker_exit) { - int fdcount; + if (udev_event->sigterm) { + udev_event_unref(udev_event); + goto out; + } - fdcount = ppoll(&pmon, 1, NULL, &sigmask); - if (fdcount < 0) - continue; + udev_event_unref(udev_event); + + /* wait for more device messages from main udevd, or term signal */ + while (dev == NULL) { + struct epoll_event ev[4]; + int fdcount; + int i; + + fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), -1); + if (fdcount < 0) { + if (errno == EINTR) + continue; + err = -errno; + err(udev, "failed to poll: %m\n"); + goto out; + } - if (pmon.revents & POLLIN) { - dev = udev_monitor_receive_device(worker_monitor); - if (dev != NULL) + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_monitor && ev[i].events & EPOLLIN) { + dev = udev_monitor_receive_device(worker_monitor); break; + } else if (ev[i].data.fd == fd_signal && ev[i].events & EPOLLIN) { + struct signalfd_siginfo fdsi; + ssize_t size; + + size = read(fd_signal, &fdsi, sizeof(struct signalfd_siginfo)); + if (size != sizeof(struct signalfd_siginfo)) + continue; + switch (fdsi.ssi_signo) { + case SIGTERM: + goto out; + } + } } } - } while (dev != NULL); - + } +out: + udev_device_unref(dev); + if (fd_signal >= 0) + close(fd_signal); + if (fd_ep >= 0) + close(fd_ep); close(fd_inotify); close(worker_watch[WRITE_END]); udev_rules_unref(rules); udev_monitor_unref(worker_monitor); udev_unref(udev); udev_log_close(); - exit(EXIT_SUCCESS); + exit(rc); } case -1: udev_monitor_unref(worker_monitor); @@ -780,15 +799,29 @@ static void handle_signal(struct udev *udev, int signo) if (worker->pid != pid) continue; - info(udev, "worker [%u] exit\n", pid); + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) + err(udev, "worker [%u] exit with return code %i\n", pid, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + err(udev, "worker [%u] terminated by signal %i (%s)\n", + pid, WTERMSIG(status), strsignal(WTERMSIG(status))); + } else if (WIFSTOPPED(status)) { + err(udev, "worker [%u] stopped\n", pid); + } else if (WIFCONTINUED(status)) { + err(udev, "worker [%u] continued\n", pid); + } else { + err(udev, "worker [%u] exit with status 0x%04x\n", pid, status); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - err(udev, "worker [%u] unexpectedly returned with status 0x%04x\n", pid, status); if (worker->event != NULL) { - err(udev, "worker [%u] failed while handling '%s'\n", pid, worker->event->devpath); + err(udev, "worker [%u] failed while handling '%s'\n", + pid, worker->event->devpath); worker->event->exitcode = -32; event_queue_delete(worker->event, true); - /* drop reference from running event */ + /* drop reference taken for state 'running' */ worker_unref(worker); } } @@ -850,10 +883,11 @@ static void static_dev_create_from_modules(struct udev *udev) if (sscanf(devno, "%c%u:%u", &type, &maj, &min) != 3) continue; + /* set sticky bit, so we do not remove the node on module unload */ if (type == 'c') - mode = 0600 | S_IFCHR; + mode = 01600|S_IFCHR; else if (type == 'b') - mode = 0600 | S_IFBLK; + mode = 01600|S_IFBLK; else continue; @@ -1030,7 +1064,7 @@ static int convert_db(struct udev *udev) f = fopen("/dev/kmsg", "w"); if (f != NULL) { - fprintf(f, "<30>udev[%u]: converting old udev database\n", getpid()); + fprintf(f, "<30>udevd[%u]: converting old udev database\n", getpid()); fclose(f); } @@ -1099,10 +1133,45 @@ static int convert_db(struct udev *udev) return 0; } +static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink) +{ + int ctrl = -1, netlink = -1; + int fd, n; + + n = sd_listen_fds(true); + if (n <= 0) + return -1; + + for (fd = SD_LISTEN_FDS_START; fd < n + SD_LISTEN_FDS_START; fd++) { + if (sd_is_socket(fd, AF_LOCAL, SOCK_SEQPACKET, -1)) { + if (ctrl >= 0) + return -1; + ctrl = fd; + continue; + } + + if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1)) { + if (netlink >= 0) + return -1; + netlink = fd; + continue; + } + + return -1; + } + + if (ctrl < 0 || netlink < 0) + return -1; + + info(udev, "ctrl=%i netlink=%i\n", ctrl, netlink); + *rctrl = ctrl; + *rnetlink = netlink; + return 0; +} + int main(int argc, char *argv[]) { struct udev *udev; - int fd; FILE *f; sigset_t mask; int daemonize = false; @@ -1261,47 +1330,85 @@ int main(int argc, char *argv[]) static_dev_create_from_modules(udev); /* before opening new files, make sure std{in,out,err} fds are in a sane state */ - fd = open("/dev/null", O_RDWR); - if (fd < 0) { - fprintf(stderr, "cannot open /dev/null\n"); - err(udev, "cannot open /dev/null\n"); + if (daemonize) { + int fd; + + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + if (write(STDOUT_FILENO, 0, 0) < 0) + dup2(fd, STDOUT_FILENO); + if (write(STDERR_FILENO, 0, 0) < 0) + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) + close(fd); + } else { + fprintf(stderr, "cannot open /dev/null\n"); + err(udev, "cannot open /dev/null\n"); + } } - if (write(STDOUT_FILENO, 0, 0) < 0) - dup2(fd, STDOUT_FILENO); - if (write(STDERR_FILENO, 0, 0) < 0) - dup2(fd, STDERR_FILENO); - - /* udevadm control socket */ - if (sd_listen_fds(true) == 1 && sd_is_socket(SD_LISTEN_FDS_START, AF_LOCAL, SOCK_SEQPACKET, -1)) - udev_ctrl = udev_ctrl_new_from_fd(udev, SD_LISTEN_FDS_START); - else - udev_ctrl = udev_ctrl_new_from_socket(udev, UDEV_CTRL_SOCK_PATH); - if (udev_ctrl == NULL) { - fprintf(stderr, "error initializing udev control socket"); - err(udev, "error initializing udev control socket"); - rc = 1; + + if (systemd_fds(udev, &fd_ctrl, &fd_netlink) >= 0) { + /* get control and netlink socket from from systemd */ + udev_ctrl = udev_ctrl_new_from_fd(udev, fd_ctrl); + if (udev_ctrl == NULL) { + err(udev, "error taking over udev control socket"); + rc = 1; + goto exit; + } + + monitor = udev_monitor_new_from_netlink_fd(udev, "kernel", fd_netlink); + if (monitor == NULL) { + err(udev, "error taking over netlink socket\n"); + rc = 3; + goto exit; + } + } else { + /* open control and netlink socket */ + udev_ctrl = udev_ctrl_new(udev); + if (udev_ctrl == NULL) { + fprintf(stderr, "error initializing udev control socket"); + err(udev, "error initializing udev control socket"); + rc = 1; + goto exit; + } + fd_ctrl = udev_ctrl_get_fd(udev_ctrl); + + monitor = udev_monitor_new_from_netlink(udev, "kernel"); + if (monitor == NULL) { + fprintf(stderr, "error initializing netlink socket\n"); + err(udev, "error initializing netlink socket\n"); + rc = 3; + goto exit; + } + fd_netlink = udev_monitor_get_fd(monitor); + } + + if (udev_monitor_enable_receiving(monitor) < 0) { + fprintf(stderr, "error binding netlink socket\n"); + err(udev, "error binding netlink socket\n"); + rc = 3; goto exit; } + if (udev_ctrl_enable_receiving(udev_ctrl) < 0) { fprintf(stderr, "error binding udev control socket\n"); err(udev, "error binding udev control socket\n"); rc = 1; goto exit; } - fd_ctrl = udev_ctrl_get_fd(udev_ctrl); - 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; + udev_monitor_set_receive_buffer_size(monitor, 128*1024*1024); + + /* create queue file before signalling 'ready', to make sure we block 'settle' */ + udev_queue_export = udev_queue_export_new(udev); + if (udev_queue_export == NULL) { + err(udev, "error creating queue file\n"); goto exit; } - udev_monitor_set_receive_buffer_size(monitor, 128*1024*1024); - fd_netlink = udev_monitor_get_fd(monitor); if (daemonize) { pid_t pid; + int fd; pid = fork(); switch (pid) { @@ -1312,13 +1419,49 @@ int main(int argc, char *argv[]) rc = 4; goto exit; default: - rc = 0; - goto exit; + rc = EXIT_SUCCESS; + goto exit_daemonize; + } + + setsid(); + + fd = open("/proc/self/oom_score_adj", O_RDWR); + if (fd < 0) { + /* Fallback to old interface */ + fd = open("/proc/self/oom_adj", O_RDWR); + if (fd < 0) { + err(udev, "error disabling OOM: %m\n"); + } else { + /* OOM_DISABLE == -17 */ + write(fd, "-17", 3); + close(fd); + } + } else { + write(fd, "-1000", 5); + close(fd); } } else { sd_notify(1, "READY=1"); } + f = fopen("/dev/kmsg", "w"); + if (f != NULL) { + fprintf(f, "<30>udevd[%u]: starting version " VERSION "\n", getpid()); + fclose(f); + } + + if (!debug) { + int fd; + + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + } + fd_inotify = udev_watch_init(udev); if (fd_inotify < 0) { fprintf(stderr, "error initializing inotify\n"); @@ -1352,19 +1495,19 @@ int main(int argc, char *argv[]) /* block and listen to all signals on signalfd */ sigfillset(&mask); - sigprocmask(SIG_SETMASK, &mask, &orig_sigmask); - fd_signal = signalfd(-1, &mask, SFD_CLOEXEC); + sigprocmask(SIG_SETMASK, &mask, &sigmask_orig); + fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); if (fd_signal < 0) { - fprintf(stderr, "error getting signalfd\n"); - err(udev, "error getting signalfd\n"); + fprintf(stderr, "error creating signalfd\n"); + err(udev, "error creating signalfd\n"); rc = 5; goto exit; } /* unnamed socket from workers to the main daemon */ if (socketpair(AF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0, worker_watch) < 0) { - fprintf(stderr, "error getting socketpair\n"); - err(udev, "error getting socketpair\n"); + fprintf(stderr, "error creating socketpair\n"); + err(udev, "error creating socketpair\n"); rc = 6; goto exit; } @@ -1376,27 +1519,26 @@ int main(int argc, char *argv[]) goto exit; } - udev_queue_export = udev_queue_export_new(udev); - if (udev_queue_export == NULL) { - err(udev, "error creating queue file\n"); - goto exit; - } - memset(&ep_ctrl, 0, sizeof(struct epoll_event)); ep_ctrl.events = EPOLLIN; ep_ctrl.data.fd = fd_ctrl; + memset(&ep_inotify, 0, sizeof(struct epoll_event)); ep_inotify.events = EPOLLIN; ep_inotify.data.fd = fd_inotify; + memset(&ep_signal, 0, sizeof(struct epoll_event)); ep_signal.events = EPOLLIN; ep_signal.data.fd = fd_signal; + memset(&ep_netlink, 0, sizeof(struct epoll_event)); ep_netlink.events = EPOLLIN; ep_netlink.data.fd = fd_netlink; + memset(&ep_worker, 0, sizeof(struct epoll_event)); ep_worker.events = EPOLLIN; ep_worker.data.fd = fd_worker; + fd_ep = epoll_create1(EPOLL_CLOEXEC); if (fd_ep < 0) { err(udev, "error creating epoll fd: %m\n"); @@ -1414,38 +1556,6 @@ int main(int argc, char *argv[]) /* if needed, convert old database from earlier udev version */ convert_db(udev); - if (!debug) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - } - if (fd > STDERR_FILENO) - close(fd); - - setsid(); - - f = fopen("/dev/kmsg", "w"); - if (f != NULL) { - fprintf(f, "<30>udev[%u]: starting version " VERSION "\n", getpid()); - fclose(f); - } - - fd = open("/proc/self/oom_score_adj", O_RDWR); - if (fd < 0) { - /* Fallback to old interface */ - fd = open("/proc/self/oom_adj", O_RDWR); - if (fd < 0) { - err(udev, "error disabling OOM: %m\n"); - } else { - /* OOM_DISABLE == -17 */ - write(fd, "-17", 3); - close(fd); - } - } else { - write(fd, "-1000", 5); - close(fd); - } - if (children_max <= 0) { int memsize = mem_size_mb(); @@ -1459,8 +1569,8 @@ int main(int argc, char *argv[]) udev_rules_apply_static_dev_perms(rules); - udev_list_init(&event_list); - udev_list_init(&worker_list); + udev_list_node_init(&event_list); + udev_list_node_init(&worker_list); for (;;) { struct epoll_event ev[8]; @@ -1491,12 +1601,12 @@ int main(int argc, char *argv[]) worker_kill(udev, 0); /* exit after all has cleaned up */ - if (udev_list_is_empty(&event_list) && udev_list_is_empty(&worker_list)) + if (udev_list_node_is_empty(&event_list) && udev_list_node_is_empty(&worker_list)) break; /* timeout at exit for workers to finish */ timeout = 60 * 1000; - } else if (udev_list_is_empty(&event_list) && children > 2) { + } else if (udev_list_node_is_empty(&event_list) && children > 2) { /* set timeout to kill idle workers */ timeout = 3 * 1000; } else { @@ -1539,12 +1649,13 @@ int main(int argc, char *argv[]) dev = udev_monitor_receive_device(monitor); if (dev != NULL) + udev_device_set_usec_initialized(dev, now_usec()); if (event_queue_insert(dev) < 0) udev_device_unref(dev); } /* start new events */ - if (!udev_list_is_empty(&event_list) && !udev_exit && !stop_exec_queue) + if (!udev_list_node_is_empty(&event_list) && !udev_exit && !stop_exec_queue) event_queue_start(udev); if (is_signal) { @@ -1590,9 +1701,11 @@ int main(int argc, char *argv[]) } } - udev_queue_export_cleanup(udev_queue_export); - rc = 0; + rc = EXIT_SUCCESS; exit: + udev_queue_export_cleanup(udev_queue_export); + udev_ctrl_cleanup(udev_ctrl); +exit_daemonize: if (fd_ep >= 0) close(fd_ep); worker_list_cleanup(udev);