X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fudev%2Fudevd.c;h=e72c5b231e004aec76e9b16461acb8af6dfea094;hp=6100d7d57a3d7e0409eb3eb4a64958ed9b33f863;hb=ad6e5b348fa88f44d6cbfe7aabda7612a1d0463f;hpb=f3a740a5dae792fb6b2d411022ce8c29ced1c3f1 diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 6100d7d57..e72c5b231 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -56,8 +57,7 @@ static bool debug; void udev_main_log(struct udev *udev, int priority, const char *file, int line, const char *fn, - const char *format, va_list args) -{ + const char *format, va_list args) { log_metav(priority, file, line, fn, format, args); } @@ -73,6 +73,7 @@ static bool reload; static int children; static int children_max; static int exec_delay; +static usec_t event_timeout_usec = 60 * USEC_PER_SEC; static sigset_t sigmask_orig; static UDEV_LIST(event_list); static UDEV_LIST(worker_list); @@ -99,13 +100,9 @@ struct event { dev_t devnum; int ifindex; bool is_block; -#ifdef HAVE_FIRMWARE - bool nodelay; -#endif }; -static inline struct event *node_to_event(struct udev_list_node *node) -{ +static inline struct event *node_to_event(struct udev_list_node *node) { return container_of(node, struct event, node); } @@ -135,34 +132,29 @@ struct worker_message { int exitcode; }; -static inline struct worker *node_to_worker(struct udev_list_node *node) -{ +static inline struct worker *node_to_worker(struct udev_list_node *node) { return container_of(node, struct worker, node); } -static void event_queue_delete(struct event *event) -{ +static void event_queue_delete(struct event *event) { udev_list_node_remove(&event->node); udev_device_unref(event->dev); free(event); } -static struct worker *worker_ref(struct worker *worker) -{ +static struct worker *worker_ref(struct worker *worker) { worker->refcount++; return worker; } -static void worker_cleanup(struct worker *worker) -{ +static void worker_cleanup(struct worker *worker) { udev_list_node_remove(&worker->node); udev_monitor_unref(worker->monitor); children--; free(worker); } -static void worker_unref(struct worker *worker) -{ +static void worker_unref(struct worker *worker) { worker->refcount--; if (worker->refcount > 0) return; @@ -170,8 +162,7 @@ static void worker_unref(struct worker *worker) worker_cleanup(worker); } -static void worker_list_cleanup(struct udev *udev) -{ +static void worker_list_cleanup(struct udev *udev) { struct udev_list_node *loop, *tmp; udev_list_node_foreach_safe(loop, tmp, &worker_list) { @@ -181,8 +172,7 @@ static void worker_list_cleanup(struct udev *udev) } } -static void worker_new(struct event *event) -{ +static void worker_new(struct event *event) { struct udev *udev = event->udev; struct worker *worker; struct udev_monitor *worker_monitor; @@ -284,26 +274,17 @@ static void worker_new(struct event *event) udev_event->exec_delay = exec_delay; /* - * Take a "read lock" on the device node; this establishes + * Take a shared lock on the device node; this establishes * a concept of device "ownership" to serialize device - * access. External processes holding a "write lock" will + * access. External processes holding an exclusive lock will * cause udev to skip the event handling; in the case udev - * acquired the lock, the external process will block until + * acquired the lock, the external process can block until * udev has finished its event handling. */ - - /* - * since we make check - device seems unused - we try - * ioctl to deactivate - and device is found to be opened - * sure, you try to take a write lock - * if you get it udev is out - * if you can't get it, udev is busy - * we cannot deactivate openned device (as it is in-use) - * maybe we should just exclude dm from that thing entirely - * IMHO this sounds like a good plan for this moment - */ - if (streq_ptr("block", udev_device_get_subsystem(dev)) && - !startswith("dm-", udev_device_get_sysname(dev))) { + if (!streq_ptr(udev_device_get_action(dev), "remove") && + streq_ptr("block", udev_device_get_subsystem(dev)) && + !startswith(udev_device_get_sysname(dev), "dm-") && + !startswith(udev_device_get_sysname(dev), "md")) { struct udev_device *d = dev; if (streq_ptr("partition", udev_device_get_devtype(d))) @@ -321,9 +302,9 @@ static void worker_new(struct event *event) } /* apply rules, create node, symlinks */ - udev_event_execute_rules(udev_event, rules, &sigmask_orig); + udev_event_execute_rules(udev_event, event_timeout_usec, rules, &sigmask_orig); - udev_event_execute_run(udev_event, &sigmask_orig); + udev_event_execute_run(udev_event, event_timeout_usec, &sigmask_orig); /* apply/restore inotify watch */ if (udev_event->inotify_watch) { @@ -423,8 +404,7 @@ out: } } -static void event_run(struct event *event) -{ +static void event_run(struct event *event) { struct udev_list_node *loop; udev_list_node_foreach(loop, &worker_list) { @@ -459,8 +439,7 @@ static void event_run(struct event *event) worker_new(event); } -static int event_queue_insert(struct udev_device *dev) -{ +static int event_queue_insert(struct udev_device *dev) { struct event *event; event = new0(struct event, 1); @@ -476,10 +455,6 @@ static int event_queue_insert(struct udev_device *dev) event->devnum = udev_device_get_devnum(dev); event->is_block = streq("block", udev_device_get_subsystem(dev)); event->ifindex = udev_device_get_ifindex(dev); -#ifdef HAVE_FIRMWARE - if (streq(udev_device_get_subsystem(dev), "firmware")) - event->nodelay = true; -#endif log_debug("seq %llu queued, '%s' '%s'", udev_device_get_seqnum(dev), udev_device_get_action(dev), udev_device_get_subsystem(dev)); @@ -489,8 +464,7 @@ static int event_queue_insert(struct udev_device *dev) return 0; } -static void worker_kill(struct udev *udev) -{ +static void worker_kill(struct udev *udev) { struct udev_list_node *loop; udev_list_node_foreach(loop, &worker_list) { @@ -505,8 +479,7 @@ static void worker_kill(struct udev *udev) } /* lookup event for identical, parent, child device */ -static bool is_devpath_busy(struct event *event) -{ +static bool is_devpath_busy(struct event *event) { struct udev_list_node *loop; size_t common; @@ -558,12 +531,6 @@ static bool is_devpath_busy(struct event *event) return true; } -#ifdef HAVE_FIRMWARE - /* allow to bypass the dependency tracking */ - if (event->nodelay) - continue; -#endif - /* parent device event found */ if (event->devpath[common] == '/') { event->delaying_seqnum = loop_event->seqnum; @@ -583,8 +550,7 @@ static bool is_devpath_busy(struct event *event) return false; } -static void event_queue_start(struct udev *udev) -{ +static void event_queue_start(struct udev *udev) { struct udev_list_node *loop; udev_list_node_foreach(loop, &event_list) { @@ -601,8 +567,7 @@ static void event_queue_start(struct udev *udev) } } -static void event_queue_cleanup(struct udev *udev, enum event_state match_type) -{ +static void event_queue_cleanup(struct udev *udev, enum event_state match_type) { struct udev_list_node *loop, *tmp; udev_list_node_foreach_safe(loop, tmp, &event_list) { @@ -615,8 +580,7 @@ static void event_queue_cleanup(struct udev *udev, enum event_state match_type) } } -static void worker_returned(int fd_worker) -{ +static void worker_returned(int fd_worker) { for (;;) { struct worker_message msg; ssize_t size; @@ -648,8 +612,7 @@ static void worker_returned(int fd_worker) } /* receive the udevd message from userspace */ -static struct udev_ctrl_connection *handle_ctrl_msg(struct udev_ctrl *uctrl) -{ +static struct udev_ctrl_connection *handle_ctrl_msg(struct udev_ctrl *uctrl) { struct udev *udev = udev_ctrl_get_udev(uctrl); struct udev_ctrl_connection *ctrl_conn; struct udev_ctrl_msg *ctrl_msg = NULL; @@ -738,17 +701,34 @@ static int synthesize_change(struct udev_device *dev) { char filename[UTIL_PATH_SIZE]; int r; - log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev)); - strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); - write_string_file(filename, "change"); - - /* for disks devices, re-trigger all partitions too */ if (streq_ptr("block", udev_device_get_subsystem(dev)) && - streq_ptr("disk", udev_device_get_devtype(dev))) { + streq_ptr("disk", udev_device_get_devtype(dev)) && + !startswith(udev_device_get_sysname(dev), "dm-")) { + bool part_table_read = false; + bool has_partitions = false; + int fd; struct udev *udev = udev_device_get_udev(dev); _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; struct udev_list_entry *item; + /* + * Try to re-read the partition table. This only succeeds if + * none of the devices is busy. The kernel returns 0 if no + * partition table is found, and we will not get an event for + * the disk. + */ + fd = open(udev_device_get_devnode(dev), O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NONBLOCK); + if (fd >= 0) { + r = flock(fd, LOCK_EX|LOCK_NB); + if (r >= 0) + r = ioctl(fd, BLKRRPART, 0); + + close(fd); + if (r >= 0) + part_table_read = true; + } + + /* search for partitions */ e = udev_enumerate_new(udev); if (!e) return -ENOMEM; @@ -762,6 +742,39 @@ static int synthesize_change(struct udev_device *dev) { return r; r = udev_enumerate_scan_devices(e); + if (r < 0) + return r; + + udev_list_entry_foreach(item, udev_enumerate_get_list_entry(e)) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + continue; + + if (!streq_ptr("partition", udev_device_get_devtype(d))) + continue; + + has_partitions = true; + break; + } + + /* + * We have partitions and re-read the table, the kernel already sent + * out a "change" event for the disk, and "remove/add" for all + * partitions. + */ + if (part_table_read && has_partitions) + return 0; + + /* + * We have partitions but re-reading the partition table did not + * work, synthesize "change" for the disk and all partitions. + */ + log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev)); + strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); + write_string_file(filename, "change"); + udev_list_entry_foreach(item, udev_enumerate_get_list_entry(e)) { _cleanup_udev_device_unref_ struct udev_device *d = NULL; @@ -777,13 +790,18 @@ static int synthesize_change(struct udev_device *dev) { strscpyl(filename, sizeof(filename), udev_device_get_syspath(d), "/uevent", NULL); write_string_file(filename, "change"); } + + return 0; } + log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev)); + strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); + write_string_file(filename, "change"); + return 0; } -static int handle_inotify(struct udev *udev) -{ +static int handle_inotify(struct udev *udev) { int nbytes, pos; char *buf; struct inotify_event *ev; @@ -822,8 +840,7 @@ static int handle_inotify(struct udev *udev) return 0; } -static void handle_signal(struct udev *udev, int signo) -{ +static void handle_signal(struct udev *udev, int signo) { switch (signo) { case SIGINT: case SIGTERM: @@ -883,8 +900,7 @@ static void handle_signal(struct udev *udev, int signo) } } -static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink) -{ +static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink) { int ctrl = -1, netlink = -1; int fd, n; @@ -925,10 +941,9 @@ static int systemd_fds(struct udev *udev, int *rctrl, int *rnetlink) * udev.children-max= events are fully serialized if set to 1 * udev.exec-delay= delay execution of every executed program */ -static void kernel_cmdline_options(struct udev *udev) -{ +static void kernel_cmdline_options(struct udev *udev) { _cleanup_free_ char *line = NULL; - char *w, *state; + const char *word, *state; size_t l; int r; @@ -938,10 +953,10 @@ static void kernel_cmdline_options(struct udev *udev) if (r <= 0) return; - FOREACH_WORD_QUOTED(w, l, line, state) { + FOREACH_WORD_QUOTED(word, l, line, state) { char *s, *opt; - s = strndup(w, l); + s = strndup(word, l); if (!s) break; @@ -961,14 +976,15 @@ static void kernel_cmdline_options(struct udev *udev) children_max = strtoul(opt + 18, NULL, 0); } else if (startswith(opt, "udev.exec-delay=")) { exec_delay = strtoul(opt + 16, NULL, 0); + } else if (startswith(opt, "udev.event-timeout=")) { + event_timeout_usec = strtoul(opt + 16, NULL, 0) * USEC_PER_SEC; } free(s); } } -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { struct udev *udev; sigset_t mask; int daemonize = false; @@ -978,6 +994,7 @@ int main(int argc, char *argv[]) { "debug", no_argument, NULL, 'D' }, { "children-max", required_argument, NULL, 'c' }, { "exec-delay", required_argument, NULL, 'e' }, + { "event-timeout", required_argument, NULL, 't' }, { "resolve-names", required_argument, NULL, 'N' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, @@ -1021,6 +1038,9 @@ int main(int argc, char *argv[]) case 'e': exec_delay = strtoul(optarg, NULL, 0); break; + case 't': + event_timeout_usec = strtoul(optarg, NULL, 0) * USEC_PER_SEC; + break; case 'D': debug = true; log_set_max_level(LOG_DEBUG); @@ -1045,6 +1065,7 @@ int main(int argc, char *argv[]) " --debug\n" " --children-max=\n" " --exec-delay=\n" + " --event-timeout=\n" " --resolve-names=early|late|never\n" " --version\n" " --help\n" @@ -1358,20 +1379,17 @@ int main(int argc, char *argv[]) if (worker->state != WORKER_RUNNING) continue; - if ((now(CLOCK_MONOTONIC) - worker->event_start_usec) > 30 * USEC_PER_SEC) { - log_error("worker [%u] %s timeout; kill it", worker->pid, - worker->event ? worker->event->devpath : ""); + if ((now(CLOCK_MONOTONIC) - worker->event_start_usec) > event_timeout_usec) { + log_error("worker [%u] %s timeout; kill it", worker->pid, worker->event->devpath); kill(worker->pid, SIGKILL); worker->state = WORKER_KILLED; /* drop reference taken for state 'running' */ worker_unref(worker); - if (worker->event) { - log_error("seq %llu '%s' killed", udev_device_get_seqnum(worker->event->dev), worker->event->devpath); - worker->event->exitcode = -64; - event_queue_delete(worker->event); - worker->event = NULL; - } + log_error("seq %llu '%s' killed", udev_device_get_seqnum(worker->event->dev), worker->event->devpath); + worker->event->exitcode = -64; + event_queue_delete(worker->event); + worker->event = NULL; } }