chiark / gitweb /
update README
[elogind.git] / udev / udevd.c
index f730cab4849d45f190f630c170bc7edb65ba049a..63b4fd7ad26c33a28b17c097cc819461aa692d43 100644 (file)
 
 static bool debug;
 
-static void log_fn(struct udev *udev, int priority,
+void udev_main_log(struct udev *udev, int priority,
                   const char *file, int line, const char *fn,
                   const char *format, va_list args)
 {
        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);
@@ -77,11 +76,11 @@ static int fd_signal = -1;
 static int fd_ep = -1;
 static int fd_inotify = -1;
 static bool stop_exec_queue;
-static bool reload_config;
+static bool reload;
 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;
@@ -156,11 +155,7 @@ 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);
@@ -247,8 +242,6 @@ 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");
 
                sigfillset(&mask);
                fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
@@ -286,9 +279,8 @@ static void worker_new(struct event *event)
 
                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);
@@ -297,23 +289,17 @@ static void worker_new(struct event *event)
                                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) {
@@ -325,18 +311,24 @@ 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;
 
+                       if (udev_event->sigterm) {
+                               udev_event_unref(udev_event);
+                               goto out;
+                       }
+
+                       udev_event_unref(udev_event);
+
                        /* wait for more device messages from main udevd, or term signal */
                        while (dev == NULL) {
                                struct epoll_event ev[4];
@@ -344,12 +336,18 @@ static void worker_new(struct event *event)
                                int i;
 
                                fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), -1);
-                               if (fdcount < 0)
-                                       continue;
+                               if (fdcount < 0) {
+                                       if (errno == EINTR)
+                                               continue;
+                                       err = -errno;
+                                       err(udev, "failed to poll: %m\n");
+                                       goto out;
+                               }
 
                                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;
@@ -360,9 +358,6 @@ static void worker_new(struct event *event)
                                                switch (fdsi.ssi_signo) {
                                                case SIGTERM:
                                                        goto out;
-                                               case SIGALRM:
-                                                       rc = EXIT_FAILURE;
-                                                       goto out;
                                                }
                                        }
                                }
@@ -403,7 +398,7 @@ out:
        }
 }
 
-static void event_run(struct event *event, bool force)
+static void event_run(struct event *event)
 {
        struct udev_list_node *loop;
 
@@ -428,7 +423,7 @@ static void event_run(struct event *event, bool force)
                return;
        }
 
-       if (!force && children >= children_max) {
+       if (children >= children_max) {
                if (children_max > 1)
                        info(event->udev, "maximum number (%i) of children reached\n", children);
                return;
@@ -462,13 +457,6 @@ static int event_queue_insert(struct udev_device *dev)
 
        event->state = EVENT_QUEUED;
        udev_list_node_append(&event->node, &event_list);
-
-       /* run all events with a timeout set immediately */
-       if (udev_device_get_timeout(dev) > 0) {
-               event_run(event, true);
-               return 0;
-       }
-
        return 0;
 }
 
@@ -585,7 +573,7 @@ static void event_queue_start(struct udev *udev)
                        continue;
                }
 
-               event_run(event, false);
+               event_run(event);
        }
 }
 
@@ -667,9 +655,9 @@ static struct udev_ctrl_connection *handle_ctrl_msg(struct udev_ctrl *uctrl)
                stop_exec_queue = false;
        }
 
-       if (udev_ctrl_get_reload_rules(ctrl_msg) > 0) {
-               info(udev, "udevd message (RELOAD_RULES) received\n");
-               reload_config = true;
+       if (udev_ctrl_get_reload(ctrl_msg) > 0) {
+               info(udev, "udevd message (RELOAD) received\n");
+               reload = true;
        }
 
        str = udev_ctrl_get_set_env(ctrl_msg);
@@ -741,19 +729,6 @@ static int handle_inotify(struct udev *udev)
                struct udev_device *dev;
 
                ev = (struct inotify_event *)(buf + pos);
-               if (ev->len) {
-                       const char *s;
-
-                       info(udev, "inotify event: %x for %s\n", ev->mask, ev->name);
-                       s = strstr(ev->name, ".rules");
-                       if (s == NULL)
-                               continue;
-                       if (strlen(s) != strlen(".rules"))
-                               continue;
-                       reload_config = true;
-                       continue;
-               }
-
                dev = udev_watch_lookup(udev, ev->wd);
                if (dev != NULL) {
                        info(udev, "inotify event: %x for %s\n", ev->mask, udev_device_get_devnode(dev));
@@ -804,15 +779,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);
                                        }
                                }
@@ -822,7 +811,7 @@ static void handle_signal(struct udev *udev, int signo)
                }
                break;
        case SIGHUP:
-               reload_config = true;
+               reload = true;
                break;
        }
 }
@@ -874,10 +863,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;
 
@@ -1054,7 +1044,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);
        }
 
@@ -1159,6 +1149,36 @@ static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink)
        return 0;
 }
 
+static bool check_rules_timestamp(struct udev *udev)
+{
+       char **p;
+       unsigned long long *stamp_usec;
+       int i, n;
+       bool changed = false;
+
+       n = udev_get_rules_path(udev, &p, &stamp_usec);
+       for (i = 0; i < n; i++) {
+               struct stat stats;
+
+               if (stat(p[i], &stats) < 0)
+                       continue;
+
+               if (stamp_usec[i] == ts_usec(&stats.st_mtim))
+                       continue;
+
+               /* first check */
+               if (stamp_usec[i] != 0) {
+                       info(udev, "reload - timestamp of '%s' changed\n", p[i]);
+                       changed = true;
+               }
+
+               /* update timestamp */
+               stamp_usec[i] = ts_usec(&stats.st_mtim);
+       }
+
+       return changed;
+}
+
 int main(int argc, char *argv[])
 {
        struct udev *udev;
@@ -1181,6 +1201,7 @@ int main(int argc, char *argv[])
        int fd_worker = -1;
        struct epoll_event ep_ctrl, ep_inotify, ep_signal, ep_netlink, ep_worker;
        struct udev_ctrl_connection *ctrl_conn = NULL;
+       char **s;
        int rc = 1;
 
        udev = udev_new();
@@ -1188,35 +1209,10 @@ int main(int argc, char *argv[])
                goto exit;
 
        udev_log_init("udevd");
-       udev_set_log_fn(udev, log_fn);
+       udev_set_log_fn(udev, udev_main_log);
        info(udev, "version %s\n", VERSION);
        udev_selinux_init(udev);
 
-       /* make sure, that our runtime dir exists and is writable */
-       if (utimensat(AT_FDCWD, udev_get_run_config_path(udev), NULL, 0) < 0) {
-               /* try to create our own subdirectory, do not create parent directories */
-               mkdir(udev_get_run_config_path(udev), 0755);
-
-               if (utimensat(AT_FDCWD, udev_get_run_config_path(udev), NULL, 0) >= 0) {
-                       /* directory seems writable now */
-                       udev_set_run_path(udev, udev_get_run_config_path(udev));
-               } else {
-                       /* fall back to /dev/.udev */
-                       char filename[UTIL_PATH_SIZE];
-
-                       util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev", NULL);
-                       if (udev_set_run_path(udev, filename) == NULL)
-                               goto exit;
-                       mkdir(udev_get_run_path(udev), 0755);
-                       err(udev, "error: runtime directory '%s' not writable, for now falling back to '%s'",
-                           udev_get_run_config_path(udev), udev_get_run_path(udev));
-               }
-       }
-       /* relabel runtime dir only if it resides below /dev */
-       if (strncmp(udev_get_run_path(udev), udev_get_dev_path(udev), strlen(udev_get_dev_path(udev))) == 0)
-               udev_selinux_lsetfilecon(udev, udev_get_run_path(udev), 0755);
-       info(udev, "runtime dir '%s'\n", udev_get_run_path(udev));
-
        for (;;) {
                int option;
 
@@ -1315,6 +1311,9 @@ int main(int argc, char *argv[])
        chdir("/");
        umask(022);
 
+       /* /run/udev */
+       mkdir(udev_get_run_path(udev), 0755);
+
        /* create standard links, copy static nodes, create nodes from modules */
        static_dev_create(udev);
        static_dev_create_from_modules(udev);
@@ -1339,7 +1338,7 @@ int main(int argc, char *argv[])
 
        if (systemd_fds(udev, &fd_ctrl, &fd_netlink) >= 0) {
                /* get control and netlink socket from from systemd */
-               udev_ctrl = udev_ctrl_new_from_socket_fd(udev, UDEV_CTRL_SOCK_PATH, fd_ctrl);
+               udev_ctrl = udev_ctrl_new_from_fd(udev, fd_ctrl);
                if (udev_ctrl == NULL) {
                        err(udev, "error taking over udev control socket");
                        rc = 1;
@@ -1354,7 +1353,7 @@ int main(int argc, char *argv[])
                }
        } else {
                /* open control and netlink socket */
-               udev_ctrl = udev_ctrl_new_from_socket(udev, UDEV_CTRL_SOCK_PATH);
+               udev_ctrl = udev_ctrl_new(udev);
                if (udev_ctrl == NULL) {
                        fprintf(stderr, "error initializing udev control socket");
                        err(udev, "error initializing udev control socket");
@@ -1389,6 +1388,13 @@ int main(int argc, char *argv[])
 
        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;
+       }
+
        if (daemonize) {
                pid_t pid;
                int fd;
@@ -1402,8 +1408,8 @@ int main(int argc, char *argv[])
                        rc = 4;
                        goto exit;
                default:
-                       rc = 0;
-                       goto exit;
+                       rc = EXIT_SUCCESS;
+                       goto exit_daemonize;
                }
 
                setsid();
@@ -1429,7 +1435,7 @@ int main(int argc, char *argv[])
 
        f = fopen("/dev/kmsg", "w");
        if (f != NULL) {
-               fprintf(f, "<30>udev[%u]: starting version " VERSION "\n", getpid());
+               fprintf(f, "<30>udevd[%u]: starting version " VERSION "\n", getpid());
                fclose(f);
        }
 
@@ -1452,33 +1458,11 @@ int main(int argc, char *argv[])
                rc = 4;
                goto exit;
        }
-
-       if (udev_get_rules_path(udev) != NULL) {
-               inotify_add_watch(fd_inotify, udev_get_rules_path(udev),
-                                 IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-       } else {
-               char filename[UTIL_PATH_SIZE];
-               struct stat statbuf;
-
-               inotify_add_watch(fd_inotify, LIBEXECDIR "/rules.d",
-                                 IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-               inotify_add_watch(fd_inotify, SYSCONFDIR "/udev/rules.d",
-                                 IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-
-               /* watch dynamic rules directory */
-               util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/rules.d", NULL);
-               if (stat(filename, &statbuf) != 0) {
-                       util_create_path(udev, filename);
-                       mkdir(filename, 0755);
-               }
-               inotify_add_watch(fd_inotify, filename,
-                                 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, &orig_sigmask);
+       sigprocmask(SIG_SETMASK, &mask, &sigmask_orig);
        fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
        if (fd_signal < 0) {
                fprintf(stderr, "error creating signalfd\n");
@@ -1496,18 +1480,14 @@ int main(int argc, char *argv[])
        }
        fd_worker = worker_watch[READ_END];
 
+       udev_builtin_init(udev);
+
        rules = udev_rules_new(udev, resolve_names);
        if (rules == NULL) {
                err(udev, "error reading rules\n");
                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;
@@ -1558,10 +1538,11 @@ 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 (;;) {
+               static unsigned long long last_usec;
                struct epoll_event ev[8];
                int fdcount;
                int timeout;
@@ -1590,12 +1571,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 {
@@ -1629,6 +1610,24 @@ int main(int argc, char *argv[])
                                is_ctrl = true;
                }
 
+               /* check for changed config, every 3 seconds at most */
+               if ((now_usec() - last_usec) > 3 * 1000 * 1000) {
+                       if (check_rules_timestamp(udev))
+                               reload = true;
+                       if (udev_builtin_validate(udev))
+                               reload = true;
+
+                       last_usec = now_usec();
+               }
+
+               /* reload requested, HUP signal received, rules changed, builtin changed */
+               if (reload) {
+                       worker_kill(udev, 0);
+                       rules = udev_rules_unref(rules);
+                       udev_builtin_exit(udev);
+                       reload = 0;
+               }
+
                /* event has finished */
                if (is_worker)
                        worker_returned(fd_worker);
@@ -1637,14 +1636,20 @@ int main(int argc, char *argv[])
                        struct udev_device *dev;
 
                        dev = udev_monitor_receive_device(monitor);
-                       if (dev != NULL)
+                       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)
-                       event_queue_start(udev);
+               if (!udev_list_node_is_empty(&event_list) && !udev_exit && !stop_exec_queue) {
+                       if (rules == NULL)
+                               rules = udev_rules_new(udev, resolve_names);
+                       if (rules != NULL)
+                               event_queue_start(udev);
+               }
 
                if (is_signal) {
                        struct signalfd_siginfo fdsi;
@@ -1659,7 +1664,7 @@ int main(int argc, char *argv[])
                if (udev_exit)
                        continue;
 
-               /* device node and rules directory inotify watch */
+               /* device node watch */
                if (is_inotify)
                        handle_inotify(udev);
 
@@ -1674,29 +1679,19 @@ int main(int argc, char *argv[])
                 */
                if (is_ctrl)
                        ctrl_conn = handle_ctrl_msg(udev_ctrl);
-
-               /* rules changed, set by inotify or a HUP signal */
-               if (reload_config) {
-                       struct udev_rules *rules_new;
-
-                       worker_kill(udev, 0);
-                       rules_new = udev_rules_new(udev, resolve_names);
-                       if (rules_new != NULL) {
-                               udev_rules_unref(rules);
-                               rules = rules_new;
-                       }
-                       reload_config = 0;
-               }
        }
 
-       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);
        event_queue_cleanup(udev, EVENT_UNDEF);
        udev_rules_unref(rules);
+       udev_builtin_exit(udev);
        if (fd_signal >= 0)
                close(fd_signal);
        if (worker_watch[READ_END] >= 0)