diff options
Diffstat (limited to 'udev')
-rw-r--r-- | udev/test-udev.c | 61 | ||||
-rw-r--r-- | udev/udev-event.c | 422 | ||||
-rw-r--r-- | udev/udev-node.c | 2 | ||||
-rw-r--r-- | udev/udev-rules.c | 199 | ||||
-rw-r--r-- | udev/udev.h | 17 | ||||
-rw-r--r-- | udev/udevadm-control.c | 59 | ||||
-rw-r--r-- | udev/udevadm-info.c | 82 | ||||
-rw-r--r-- | udev/udevadm-monitor.c | 109 | ||||
-rw-r--r-- | udev/udevadm-settle.c | 122 | ||||
-rw-r--r-- | udev/udevadm-test.c | 67 | ||||
-rw-r--r-- | udev/udevadm.c | 8 | ||||
-rw-r--r-- | udev/udevadm.xml | 20 | ||||
-rw-r--r-- | udev/udevd.c | 773 |
13 files changed, 1361 insertions, 580 deletions
diff --git a/udev/test-udev.c b/udev/test-udev.c index 28c833a9e5..07716897b4 100644 --- a/udev/test-udev.c +++ b/udev/test-udev.c @@ -23,36 +23,24 @@ #include <fcntl.h> #include <ctype.h> #include <errno.h> -#include <signal.h> #include <unistd.h> #include <syslog.h> #include <grp.h> +#include <sys/signalfd.h> #include "udev.h" -static void sig_handler(int signum) -{ - switch (signum) { - case SIGALRM: - _exit(1); - case SIGINT: - case SIGTERM: - _exit(20 + signum); - } -} - int main(int argc, char *argv[]) { struct udev *udev; - struct udev_event *event; - struct udev_device *dev; - struct udev_rules *rules; + struct udev_event *event = NULL; + struct udev_device *dev = NULL; + struct udev_rules *rules = NULL; char syspath[UTIL_PATH_SIZE]; const char *devpath; const char *action; const char *subsystem; - struct sigaction act; - sigset_t mask; + sigset_t mask, sigmask_orig; int err = -EINVAL; udev = udev_new(); @@ -61,22 +49,7 @@ int main(int argc, char *argv[]) info(udev, "version %s\n", VERSION); udev_selinux_init(udev); - /* set signal handlers */ - memset(&act, 0x00, sizeof(act)); - act.sa_handler = sig_handler; - sigemptyset (&act.sa_mask); - act.sa_flags = 0; - sigaction(SIGALRM, &act, NULL); - sigaction(SIGINT, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigemptyset(&mask); - sigaddset(&mask, SIGALRM); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - sigprocmask(SIG_UNBLOCK, &mask, NULL); - - /* trigger timeout to prevent hanging processes */ - alarm(UDEV_EVENT_TIMEOUT); + sigprocmask(SIG_SETMASK, NULL, &sigmask_orig); action = getenv("ACTION"); devpath = getenv("DEVPATH"); @@ -84,7 +57,7 @@ int main(int argc, char *argv[]) if (action == NULL || subsystem == NULL || devpath == NULL) { err(udev, "action, subsystem or devpath missing\n"); - goto exit; + goto out; } rules = udev_rules_new(udev, 1); @@ -93,7 +66,7 @@ int main(int argc, char *argv[]) dev = udev_device_new_from_syspath(udev, syspath); if (dev == NULL) { info(udev, "unknown device '%s'\n", devpath); - goto fail; + goto out; } /* skip reading of db, but read kernel parameters */ @@ -102,20 +75,24 @@ int main(int argc, char *argv[]) udev_device_set_action(dev, action); event = udev_event_new(dev); - err = udev_event_execute_rules(event, rules); - /* rules may change/disable the timeout */ - if (udev_device_get_event_timeout(dev) >= 0) - alarm(udev_device_get_event_timeout(dev)); + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, &sigmask_orig); + event->fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (event->fd_signal < 0) { + fprintf(stderr, "error creating signalfd\n"); + goto out; + } + err = udev_event_execute_rules(event, rules, &sigmask_orig); if (err == 0) udev_event_execute_run(event, NULL); - +out: + if (event != NULL && event->fd_signal >= 0) + close(event->fd_signal); udev_event_unref(event); udev_device_unref(dev); -fail: udev_rules_unref(rules); -exit: udev_selinux_exit(udev); udev_unref(udev); if (err != 0) diff --git a/udev/udev-event.c b/udev/udev-event.c index 63a8b3aa83..832abbbfce 100644 --- a/udev/udev-event.c +++ b/udev/udev-event.c @@ -26,7 +26,12 @@ #include <time.h> #include <net/if.h> #include <sys/ioctl.h> +#include <sys/prctl.h> +#include <sys/poll.h> +#include <sys/epoll.h> +#include <sys/wait.h> #include <sys/socket.h> +#include <sys/signalfd.h> #include <linux/sockios.h> #include "udev.h" @@ -38,10 +43,12 @@ struct udev_event *udev_event_new(struct udev_device *dev) event = calloc(1, sizeof(struct udev_event)); if (event == NULL) return NULL; - event->mode = 0600; event->dev = dev; event->udev = udev_device_get_udev(dev); udev_list_init(&event->run_list); + event->fd_signal = -1; + event->birth_usec = now_usec(); + event->timeout_usec = 60 * 1000 * 1000; dbg(event->udev, "allocated event %p\n", event); return event; } @@ -439,6 +446,383 @@ out: return l; } +static int spawn_exec(struct udev_event *event, + const char *cmd, char *const argv[], char **envp, const sigset_t *sigmask, + int fd_stdout, int fd_stderr) +{ + struct udev *udev = event->udev; + int err; + int fd; + + /* discard child output or connect to pipe */ + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, STDIN_FILENO); + if (fd_stdout < 0) + dup2(fd, STDOUT_FILENO); + if (fd_stderr < 0) + dup2(fd, STDERR_FILENO); + close(fd); + } else { + err(udev, "open /dev/null failed: %m\n"); + } + + /* connect pipes to std{out,err} */ + if (fd_stdout >= 0) { + dup2(fd_stdout, STDOUT_FILENO); + close(fd_stdout); + } + if (fd_stderr >= 0) { + dup2(fd_stderr, STDERR_FILENO); + close(fd_stderr); + } + + /* terminate child in case parent goes away */ + prctl(PR_SET_PDEATHSIG, SIGTERM); + + /* restore original udev sigmask before exec */ + if (sigmask) + sigprocmask(SIG_SETMASK, sigmask, NULL); + + execve(argv[0], argv, envp); + + /* exec failed */ + err = -errno; + err(udev, "failed to execute '%s' '%s': %m\n", argv[0], cmd); + return err; +} + +static int spawn_read(struct udev_event *event, + const char *cmd, + int fd_stdout, int fd_stderr, + char *result, size_t ressize) +{ + struct udev *udev = event->udev; + size_t respos = 0; + int fd_ep = -1; + struct epoll_event ep_outpipe, ep_errpipe; + int err = 0; + + /* read from child if requested */ + if (fd_stdout < 0 && fd_stderr < 0) + return 0; + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + err = -errno; + err(udev, "error creating epoll fd: %m\n"); + goto out; + } + + if (fd_stdout >= 0) { + memset(&ep_outpipe, 0, sizeof(struct epoll_event)); + ep_outpipe.events = EPOLLIN; + ep_outpipe.data.ptr = &fd_stdout; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stdout, &ep_outpipe) < 0) { + err(udev, "fail to add fd to epoll: %m\n"); + goto out; + } + } + + if (fd_stderr >= 0) { + memset(&ep_errpipe, 0, sizeof(struct epoll_event)); + ep_errpipe.events = EPOLLIN; + ep_errpipe.data.ptr = &fd_stderr; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_stderr, &ep_errpipe) < 0) { + err(udev, "fail to add fd to epoll: %m\n"); + goto out; + } + } + + /* read child output */ + while (fd_stdout >= 0 || fd_stderr >= 0) { + int timeout; + int fdcount; + struct epoll_event ev[4]; + int i; + + if (event->timeout_usec > 0) { + unsigned long long age_usec; + + age_usec = now_usec() - event->birth_usec; + if (age_usec >= event->timeout_usec) { + err = -ETIMEDOUT; + err(udev, "timeout '%s'\n", cmd); + goto out; + } + timeout = ((event->timeout_usec - age_usec) / 1000) + 1000; + } else { + timeout = -1; + } + + fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), timeout); + if (fdcount < 0) { + if (errno == EINTR) + continue; + err = -errno; + err(udev, "failed to poll: %m\n"); + goto out; + } + if (fdcount == 0) { + err = -ETIMEDOUT; + err(udev, "timeout '%s'\n", cmd); + goto out; + } + + for (i = 0; i < fdcount; i++) { + int *fd = (int *)ev[i].data.ptr; + + if (ev[i].events & EPOLLIN) { + ssize_t count; + char buf[4096]; + + count = read(*fd, buf, sizeof(buf)-1); + if (count <= 0) + continue; + buf[count] = '\0'; + + /* store stdout result */ + if (result != NULL && *fd == fd_stdout) { + if (respos + count < ressize) { + memcpy(&result[respos], buf, count); + respos += count; + } else { + err(udev, "'%s' ressize %zd too short\n", cmd, ressize); + err = -ENOBUFS; + } + } + + /* log debug output only if we watch stderr */ + if (fd_stderr >= 0) { + char *pos; + char *line; + + pos = buf; + while ((line = strsep(&pos, "\n"))) { + if (pos != NULL || line[0] != '\0') + info(udev, "'%s'(%s) '%s'\n", cmd, *fd == fd_stdout ? "out" : "err" , line); + } + } + } else if (ev[i].events & EPOLLHUP) { + if (epoll_ctl(fd_ep, EPOLL_CTL_DEL, *fd, NULL) < 0) { + err = -errno; + err(udev, "failed to remove fd from epoll: %m\n"); + goto out; + } + *fd = -1; + } + } + } + + /* return the child's stdout string */ + if (result != NULL) { + result[respos] = '\0'; + dbg(udev, "result='%s'\n", result); + } +out: + if (fd_ep >= 0) + close(fd_ep); + return err; +} + +static int spawn_wait(struct udev_event *event, const char *cmd, pid_t pid) +{ + struct udev *udev = event->udev; + struct pollfd pfd[1]; + int err = 0; + + pfd[0].events = POLLIN; + pfd[0].fd = event->fd_signal; + + while (pid > 0) { + int timeout; + int fdcount; + + if (event->timeout_usec > 0) { + unsigned long long age_usec; + + age_usec = now_usec() - event->birth_usec; + if (age_usec >= event->timeout_usec) + timeout = 1000; + else + timeout = ((event->timeout_usec - age_usec) / 1000) + 1000; + } else { + timeout = -1; + } + + fdcount = poll(pfd, 1, timeout); + if (fdcount < 0) { + if (errno == EINTR) + continue; + err = -errno; + err(udev, "failed to poll: %m\n"); + goto out; + } + if (fdcount == 0) { + err(udev, "timeout: killing '%s' [%u]\n", cmd, pid); + kill(pid, SIGKILL); + } + + if (pfd[0].revents & POLLIN) { + struct signalfd_siginfo fdsi; + int status; + ssize_t size; + + size = read(event->fd_signal, &fdsi, sizeof(struct signalfd_siginfo)); + if (size != sizeof(struct signalfd_siginfo)) + continue; + + switch (fdsi.ssi_signo) { + case SIGTERM: + event->sigterm = true; + break; + case SIGCHLD: + if (waitpid(pid, &status, WNOHANG) < 0) + break; + if (WIFEXITED(status)) { + info(udev, "'%s' [%u] exit with return code %i\n", cmd, pid, WEXITSTATUS(status)); + if (WEXITSTATUS(status) != 0) + err = -1; + } else if (WIFSIGNALED(status)) { + err(udev, "'%s' [%u] terminated by signal %i (%s)\n", cmd, pid, WTERMSIG(status), strsignal(WTERMSIG(status))); + err = -1; + } else if (WIFSTOPPED(status)) { + err(udev, "'%s' [%u] stopped\n", cmd, pid); + err = -1; + } else if (WIFCONTINUED(status)) { + err(udev, "'%s' [%u] continued\n", cmd, pid); + err = -1; + } else { + err(udev, "'%s' [%u] exit with status 0x%04x\n", cmd, pid, status); + err = -1; + } + pid = 0; + break; + } + } + } +out: + return err; +} + +int udev_event_spawn(struct udev_event *event, + const char *cmd, char **envp, const sigset_t *sigmask, + char *result, size_t ressize) +{ + struct udev *udev = event->udev; + int outpipe[2] = {-1, -1}; + int errpipe[2] = {-1, -1}; + pid_t pid; + char arg[UTIL_PATH_SIZE]; + char program[UTIL_PATH_SIZE]; + char *argv[((sizeof(arg) + 1) / 2) + 1]; + int i; + int err = 0; + + /* build argv from command */ + util_strscpy(arg, sizeof(arg), cmd); + i = 0; + if (strchr(arg, ' ') != NULL) { + char *pos = arg; + + while (pos != NULL && pos[0] != '\0') { + if (pos[0] == '\'') { + /* do not separate quotes */ + pos++; + argv[i] = strsep(&pos, "\'"); + if (pos != NULL) + while (pos[0] == ' ') + pos++; + } else { + argv[i] = strsep(&pos, " "); + if (pos != NULL) + while (pos[0] == ' ') + pos++; + } + dbg(udev, "arg[%i] '%s'\n", i, argv[i]); + i++; + } + argv[i] = NULL; + } else { + argv[0] = arg; + argv[1] = NULL; + } + + /* pipes from child to parent */ + if (result != NULL || udev_get_log_priority(udev) >= LOG_INFO) { + if (pipe2(outpipe, O_NONBLOCK) != 0) { + err = -errno; + err(udev, "pipe failed: %m\n"); + goto out; + } + } + if (udev_get_log_priority(udev) >= LOG_INFO) { + if (pipe2(errpipe, O_NONBLOCK) != 0) { + err = -errno; + err(udev, "pipe failed: %m\n"); + goto out; + } + } + + /* allow programs in /lib/udev/ to be called without the path */ + if (argv[0][0] != '/') { + util_strscpyl(program, sizeof(program), LIBEXECDIR "/", argv[0], NULL); + argv[0] = program; + } + + pid = fork(); + switch(pid) { + case 0: + /* child closes parent's ends of pipes */ + if (outpipe[READ_END] >= 0) { + close(outpipe[READ_END]); + outpipe[READ_END] = -1; + } + if (errpipe[READ_END] >= 0) { + close(errpipe[READ_END]); + errpipe[READ_END] = -1; + } + + info(udev, "starting '%s'\n", cmd); + + err = spawn_exec(event, cmd, argv, envp, sigmask, + outpipe[WRITE_END], errpipe[WRITE_END]); + + _exit(2 ); + case -1: + err(udev, "fork of '%s' failed: %m\n", cmd); + err = -1; + goto out; + default: + /* parent closed child's ends of pipes */ + if (outpipe[WRITE_END] >= 0) { + close(outpipe[WRITE_END]); + outpipe[WRITE_END] = -1; + } + if (errpipe[WRITE_END] >= 0) { + close(errpipe[WRITE_END]); + errpipe[WRITE_END] = -1; + } + + err = spawn_read(event, cmd, + outpipe[READ_END], errpipe[READ_END], + result, ressize); + + err = spawn_wait(event, cmd, pid); + } + +out: + if (outpipe[READ_END] >= 0) + close(outpipe[READ_END]); + if (outpipe[WRITE_END] >= 0) + close(outpipe[WRITE_END]); + if (errpipe[READ_END] >= 0) + close(errpipe[READ_END]); + if (errpipe[WRITE_END] >= 0) + close(errpipe[WRITE_END]); + return err; +} + static void rename_netif_kernel_log(struct ifreq ifr) { int klog; @@ -454,7 +838,7 @@ static void rename_netif_kernel_log(struct ifreq ifr) return; } - fprintf(f, "<30>udev[%u]: renamed network interface %s to %s\n", + fprintf(f, "<30>udevd[%u]: renamed network interface %s to %s\n", getpid(), ifr.ifr_name, ifr.ifr_newname); fclose(f); } @@ -492,7 +876,7 @@ static int rename_netif(struct udev_event *event) goto out; /* free our own name, another process may wait for us */ - util_strscpyl(ifr.ifr_newname, IFNAMSIZ, udev_device_get_sysname(dev), "-", event->name, NULL); + snprintf(ifr.ifr_newname, IFNAMSIZ, "rename%u", udev_device_get_ifindex(dev)); err = ioctl(sk, SIOCSIFNAME, &ifr); if (err < 0) { err = -errno; @@ -530,7 +914,7 @@ out: return err; } -int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules) +int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules, const sigset_t *sigmask) { struct udev_device *dev = event->dev; int err = 0; @@ -546,7 +930,7 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules) if (major(udev_device_get_devnum(dev)) != 0) udev_watch_end(event->udev, dev); - udev_rules_apply_to_event(rules, event); + udev_rules_apply_to_event(rules, event, sigmask); if (major(udev_device_get_devnum(dev)) != 0) err = udev_node_remove(dev); @@ -561,7 +945,7 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules) udev_watch_end(event->udev, event->dev_db); } - udev_rules_apply_to_event(rules, event); + udev_rules_apply_to_event(rules, event, sigmask); /* rename a new network interface, if needed */ if (udev_device_get_ifindex(dev) > 0 && strcmp(udev_device_get_action(dev), "add") == 0 && @@ -636,9 +1020,18 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules) if (event->dev_db != NULL) udev_node_update_old_links(dev, event->dev_db); - /* change default 0600 to 0660 if a group is assigned */ - if (event->mode == 0600 && event->gid > 0) - event->mode = 0660; + if (!event->mode_set) { + if (udev_device_get_devnode_mode(dev) > 0) { + /* kernel supplied value */ + event->mode = udev_device_get_devnode_mode(dev); + } else if (event->gid > 0) { + /* default 0660 if a group is assigned */ + event->mode = 0660; + } else { + /* default 0600 */ + event->mode = 0600; + } + } err = udev_node_add(dev, event->mode, event->uid, event->gid); } @@ -647,7 +1040,7 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules) if (event->dev_db != NULL && udev_device_get_usec_initialized(event->dev_db) > 0) udev_device_set_usec_initialized(event->dev, udev_device_get_usec_initialized(event->dev_db)); else - udev_device_set_usec_initialized(event->dev, usec_monotonic()); + udev_device_set_usec_initialized(event->dev, now_usec()); /* (re)write database file */ udev_device_update_db(dev); @@ -682,14 +1075,15 @@ int udev_event_execute_run(struct udev_event *event, const sigset_t *sigmask) char program[UTIL_PATH_SIZE]; char **envp; - udev_event_apply_format(event, cmd, program, sizeof(program)); - envp = udev_device_get_properties_envp(event->dev); if (event->exec_delay > 0) { info(event->udev, "delay execution of '%s'\n", program); sleep(event->exec_delay); } - if (util_run_program(event->udev, program, envp, NULL, 0, NULL, sigmask, true) != 0) { - if (udev_list_entry_get_flags(list_entry)) + + udev_event_apply_format(event, cmd, program, sizeof(program)); + envp = udev_device_get_properties_envp(event->dev); + if (udev_event_spawn(event, program, envp, sigmask, NULL, 0) < 0) { + if (udev_list_entry_get_num(list_entry)) err = -1; } } diff --git a/udev/udev-node.c b/udev/udev-node.c index 5e791ffb6c..dc7d9c365a 100644 --- a/udev/udev-node.c +++ b/udev/udev-node.c @@ -386,7 +386,7 @@ int udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid) /* create/update symlinks, add symlinks to name index */ udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(dev)) { - if (udev_list_entry_get_flags(list_entry)) + if (udev_list_entry_get_num(list_entry)) /* simple unmanaged link name */ node_symlink(udev, udev_device_get_devnode(dev), udev_list_entry_get_name(list_entry)); else diff --git a/udev/udev-rules.c b/udev/udev-rules.c index 221865c9dc..63123e083a 100644 --- a/udev/udev-rules.c +++ b/udev/udev-rules.c @@ -139,6 +139,7 @@ enum token_type { TK_M_PARENTS_MAX, TK_M_TEST, /* val, mode_t */ + TK_M_EVENT_TIMEOUT, /* int */ TK_M_PROGRAM, /* val */ TK_M_IMPORT_FILE, /* val */ TK_M_IMPORT_PROG, /* val */ @@ -150,6 +151,7 @@ enum token_type { TK_A_STRING_ESCAPE_NONE, TK_A_STRING_ESCAPE_REPLACE, + TK_A_DB_PERSIST, TK_A_INOTIFY_WATCH, /* int */ TK_A_DEVLINK_PRIO, /* int */ TK_A_OWNER, /* val */ @@ -163,7 +165,6 @@ enum token_type { TK_A_TAG, /* val */ TK_A_NAME, /* val */ TK_A_DEVLINK, /* val */ - TK_A_EVENT_TIMEOUT, /* int */ TK_A_ATTR, /* val, attr */ TK_A_RUN, /* val, bool */ TK_A_GOTO, /* size_t */ @@ -273,6 +274,7 @@ static const char *token_str(enum token_type type) [TK_M_PARENTS_MAX] = "M PARENTS_MAX", [TK_M_TEST] = "M TEST", + [TK_M_EVENT_TIMEOUT] = "M EVENT_TIMEOUT", [TK_M_PROGRAM] = "M PROGRAM", [TK_M_IMPORT_FILE] = "M IMPORT_FILE", [TK_M_IMPORT_PROG] = "M IMPORT_PROG", @@ -284,6 +286,7 @@ static const char *token_str(enum token_type type) [TK_A_STRING_ESCAPE_NONE] = "A STRING_ESCAPE_NONE", [TK_A_STRING_ESCAPE_REPLACE] = "A STRING_ESCAPE_REPLACE", + [TK_A_DB_PERSIST] = "A DB_PERSIST", [TK_A_INOTIFY_WATCH] = "A INOTIFY_WATCH", [TK_A_DEVLINK_PRIO] = "A DEVLINK_PRIO", [TK_A_OWNER] = "A OWNER", @@ -297,7 +300,6 @@ static const char *token_str(enum token_type type) [TK_A_TAG] = "A ENV", [TK_A_NAME] = "A NAME", [TK_A_DEVLINK] = "A DEVLINK", - [TK_A_EVENT_TIMEOUT] = "A EVENT_TIMEOUT", [TK_A_ATTR] = "A ATTR", [TK_A_RUN] = "A RUN", [TK_A_GOTO] = "A GOTO", @@ -370,6 +372,7 @@ static void dump_token(struct udev_rules *rules, struct token *token) break; case TK_A_STRING_ESCAPE_NONE: case TK_A_STRING_ESCAPE_REPLACE: + case TK_A_DB_PERSIST: dbg(rules->udev, "%s\n", token_str(type)); break; case TK_M_TEST: @@ -394,7 +397,7 @@ static void dump_token(struct udev_rules *rules, struct token *token) case TK_A_STATIC_NODE: dbg(rules->udev, "%s '%s'\n", token_str(type), value); break; - case TK_A_EVENT_TIMEOUT: + case TK_M_EVENT_TIMEOUT: dbg(rules->udev, "%s %u\n", token_str(type), token->key.event_timeout); break; case TK_A_GOTO: @@ -728,7 +731,7 @@ static int import_property_from_string(struct udev_device *dev, char *line) entry = udev_device_add_property(dev, key, val); /* store in db, skip private keys */ if (key[0] != '.') - udev_list_entry_set_flags(entry, 1); + udev_list_entry_set_num(entry, true); } return 0; } @@ -747,17 +750,18 @@ static int import_file_into_properties(struct udev_device *dev, const char *file return 0; } -static int import_program_into_properties(struct udev_device *dev, const char *program) +static int import_program_into_properties(struct udev_event *event, const char *program, const sigset_t *sigmask) { - struct udev *udev = udev_device_get_udev(dev); + struct udev_device *dev = event->dev; char **envp; char result[UTIL_LINE_SIZE]; - size_t reslen; char *line; + int err; envp = udev_device_get_properties_envp(dev); - if (util_run_program(udev, program, envp, result, sizeof(result), &reslen, NULL, false) != 0) - return -1; + err = udev_event_spawn(event, program, envp, sigmask, result, sizeof(result)); + if (err < 0) + return err; line = result; while (line != NULL) { @@ -796,7 +800,7 @@ static int import_parent_into_properties(struct udev_device *dev, const char *fi entry = udev_device_add_property(dev, key, val); /* store in db, skip private keys */ if (key[0] != '.') - udev_list_entry_set_flags(entry, 1); + udev_list_entry_set_num(entry, true); } } return 0; @@ -1045,6 +1049,7 @@ static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type, break; case TK_A_STRING_ESCAPE_NONE: case TK_A_STRING_ESCAPE_REPLACE: + case TK_A_DB_PERSIST: break; case TK_A_RUN: token->key.value_off = add_string(rule_tmp->rules, value); @@ -1066,7 +1071,7 @@ static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type, case TK_A_STATIC_NODE: token->key.value_off = add_string(rule_tmp->rules, value); break; - case TK_A_EVENT_TIMEOUT: + case TK_M_EVENT_TIMEOUT: token->key.event_timeout = *(int *)data; break; case TK_RULE: @@ -1572,13 +1577,15 @@ static int add_rule(struct udev_rules *rules, char *line, rule_add_key(&rule_tmp, TK_A_DEVLINK_PRIO, op, NULL, &prio); dbg(rules->udev, "link priority=%i\n", prio); } + pos = strstr(value, "event_timeout="); if (pos != NULL) { int tout = atoi(&pos[strlen("event_timeout=")]); - rule_add_key(&rule_tmp, TK_A_EVENT_TIMEOUT, op, NULL, &tout); + rule_add_key(&rule_tmp, TK_M_EVENT_TIMEOUT, op, NULL, &tout); dbg(rules->udev, "event timeout=%i\n", tout); } + pos = strstr(value, "string_escape="); if (pos != NULL) { pos = &pos[strlen("string_escape=")]; @@ -1587,6 +1594,11 @@ static int add_rule(struct udev_rules *rules, char *line, else if (strncmp(pos, "replace", strlen("replace")) == 0) rule_add_key(&rule_tmp, TK_A_STRING_ESCAPE_REPLACE, op, NULL, NULL); } + + pos = strstr(value, "db_persist"); + if (pos != NULL) + rule_add_key(&rule_tmp, TK_A_DB_PERSIST, op, NULL, NULL); + pos = strstr(value, "nowatch"); if (pos != NULL) { const int off = 0; @@ -1602,11 +1614,13 @@ static int add_rule(struct udev_rules *rules, char *line, dbg(rules->udev, "inotify watch of device requested\n"); } } + pos = strstr(value, "static_node="); if (pos != NULL) { rule_add_key(&rule_tmp, TK_A_STATIC_NODE, op, &pos[strlen("static_node=")], NULL); rule_tmp.rule.rule.has_static_node = true; } + continue; } err(rules->udev, "unknown key '%s' in %s:%u\n", key, filename, lineno); @@ -1705,6 +1719,7 @@ static int parse_file(struct udev_rules *rules, const char *filename, unsigned s static int add_matching_files(struct udev *udev, struct udev_list_node *file_list, const char *dirname, const char *suffix) { DIR *dir; + struct dirent *dent; char filename[UTIL_PATH_SIZE]; dbg(udev, "open directory '%s'\n", dirname); @@ -1714,13 +1729,7 @@ static int add_matching_files(struct udev *udev, struct udev_list_node *file_lis return -1; } - for (;;) { - struct dirent *dent; - - dent = readdir(dir); - if (dent == NULL || dent->d_name[0] == '\0') - break; - + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { if (dent->d_name[0] == '.') continue; @@ -1736,7 +1745,12 @@ static int add_matching_files(struct udev *udev, struct udev_list_node *file_lis } util_strscpyl(filename, sizeof(filename), dirname, "/", dent->d_name, NULL); dbg(udev, "put file '%s' into list\n", filename); - udev_list_entry_add(udev, file_list, filename, NULL, 1, 1); + /* + * the basename is the key, the filename the value + * identical basenames from different directories overwrite each other + * entries are sorted after basename + */ + udev_list_entry_add(udev, file_list, dent->d_name, filename, UDEV_LIST_UNIQUE|UDEV_LIST_SORT); } closedir(dir); @@ -1746,9 +1760,8 @@ static int add_matching_files(struct udev *udev, struct udev_list_node *file_lis struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names) { struct udev_rules *rules; - struct stat statbuf; struct udev_list_node file_list; - struct udev_list_entry *file_loop, *file_tmp; + struct udev_list_entry *file_loop; struct token end_token; rules = calloc(1, sizeof(struct udev_rules)); @@ -1791,85 +1804,55 @@ struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names) memset(rules->trie_nodes, 0x00, sizeof(struct trie_node)); rules->trie_nodes_cur = 1; - if (udev_get_rules_path(udev) != NULL) { - /* custom rules location for testing */ - add_matching_files(udev, &file_list, udev_get_rules_path(udev), ".rules"); - } else { - char filename[PATH_MAX]; - struct udev_list_node sort_list; - struct udev_list_entry *sort_loop, *sort_tmp; + if (udev_get_rules_path(udev) == NULL) { + char filename[UTIL_PATH_SIZE]; + + /* /lib/udev -- default/package rules */ + add_matching_files(udev, &file_list, LIBEXECDIR "/rules.d", ".rules"); - /* read user/custom rules */ + /* /etc/udev -- system-specific/user/admin rules */ add_matching_files(udev, &file_list, SYSCONFDIR "/udev/rules.d", ".rules"); - /* read dynamic/temporary rules */ + /* /run/udev -- throw-away/temporary rules */ util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/rules.d", NULL); - udev_list_init(&sort_list); - add_matching_files(udev, &sort_list, filename, ".rules"); - - /* read default rules */ - add_matching_files(udev, &sort_list, LIBEXECDIR "/rules.d", ".rules"); - - /* sort all rules files by basename into list of files */ - udev_list_entry_foreach_safe(sort_loop, sort_tmp, udev_list_get_entry(&sort_list)) { - const char *sort_name = udev_list_entry_get_name(sort_loop); - const char *sort_base = strrchr(sort_name, '/'); - - if (sort_base == NULL) - continue; - /* sort entry into existing list */ - udev_list_entry_foreach_safe(file_loop, file_tmp, udev_list_get_entry(&file_list)) { - const char *file_name = udev_list_entry_get_name(file_loop); - const char *file_base = strrchr(file_name, '/'); - - if (file_base == NULL) - continue; - if (strcmp(file_base, sort_base) == 0) { - info(udev, "rule file basename '%s' already added, ignoring '%s'\n", - file_name, sort_name); - udev_list_entry_delete(sort_loop); - sort_loop = NULL; - break; - } - if (strcmp(file_base, sort_base) > 0) { - /* found later file, insert before */ - udev_list_entry_remove(sort_loop); - udev_list_entry_insert_before(sort_loop, file_loop); - sort_loop = NULL; - break; - } - } - /* current file already handled */ - if (sort_loop == NULL) - continue; - /* no later file, append to end of list */ - udev_list_entry_remove(sort_loop); - udev_list_entry_append(sort_loop, &file_list); - } + add_matching_files(udev, &file_list, filename, ".rules"); + } else { + /* custom rules files location for testing */ + add_matching_files(udev, &file_list, udev_get_rules_path(udev), ".rules"); } /* add all filenames to the string buffer */ udev_list_entry_foreach(file_loop, udev_list_get_entry(&file_list)) { - const char *filename = udev_list_entry_get_name(file_loop); + const char *filename = udev_list_entry_get_value(file_loop); unsigned int filename_off; filename_off = add_string(rules, filename); /* the offset in the rule is limited to unsigned short */ if (filename_off < USHRT_MAX) - udev_list_entry_set_flags(file_loop, filename_off); + udev_list_entry_set_num(file_loop, filename_off); } - /* parse list of files */ - udev_list_entry_foreach_safe(file_loop, file_tmp, udev_list_get_entry(&file_list)) { - const char *filename = udev_list_entry_get_name(file_loop); - unsigned int filename_off = udev_list_entry_get_flags(file_loop); + /* parse all rules files */ + udev_list_entry_foreach(file_loop, udev_list_get_entry(&file_list)) { + const char *filename = udev_list_entry_get_value(file_loop); + unsigned int filename_off = udev_list_entry_get_num(file_loop); + struct stat st; - if (stat(filename, &statbuf) == 0 && statbuf.st_size > 0) - parse_file(rules, filename, filename_off); - else - err(udev, "can not read '%s'\n", filename); - udev_list_entry_delete(file_loop); + if (stat(filename, &st) != 0) { + err(udev, "can not find '%s': %m\n", filename); + continue; + } + if (S_ISREG(st.st_mode) && st.st_size <= 0) { + info(udev, "ignore empty '%s'\n", filename); + continue; + } + if (S_ISCHR(st.st_mode)) { + info(udev, "ignore masked '%s'\n", filename); + continue; + } + parse_file(rules, filename, filename_off); } + udev_list_cleanup_entries(udev, &file_list); memset(&end_token, 0x00, sizeof(struct token)); end_token.type = TK_END; @@ -2068,7 +2051,7 @@ enum escape_type { ESCAPE_REPLACE, }; -int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event) +int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, const sigset_t *sigmask) { struct token *cur; struct token *rule; @@ -2270,6 +2253,10 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event goto nomatch; break; } + case TK_M_EVENT_TIMEOUT: + info(event->udev, "OPTIONS event_timeout=%u\n", cur->key.event_timeout); + event->timeout_usec = cur->key.event_timeout * 1000 * 1000; + break; case TK_M_PROGRAM: { char program[UTIL_PATH_SIZE]; @@ -2284,7 +2271,8 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event program, &rules->buf[rule->rule.filename_off], rule->rule.filename_line); - if (util_run_program(event->udev, program, envp, result, sizeof(result), NULL, NULL, false) != 0) { + + if (udev_event_spawn(event, program, envp, sigmask, result, sizeof(result)) < 0) { if (cur->key.op != OP_NOMATCH) goto nomatch; } else { @@ -2322,7 +2310,8 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event import, &rules->buf[rule->rule.filename_off], rule->rule.filename_line); - if (import_program_into_properties(event->dev, import) != 0) + + if (import_program_into_properties(event, import, sigmask) != 0) if (cur->key.op != OP_NOMATCH) goto nomatch; break; @@ -2337,7 +2326,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event struct udev_list_entry *entry; entry = udev_device_add_property(event->dev, key, value); - udev_list_entry_set_flags(entry, 1); + udev_list_entry_set_num(entry, true); } else { if (cur->key.op != OP_NOMATCH) goto nomatch; @@ -2365,7 +2354,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event if (pos[0] == '\0' || isspace(pos[0])) { /* we import simple flags as 'FLAG=1' */ entry = udev_device_add_property(event->dev, key, "1"); - udev_list_entry_set_flags(entry, 1); + udev_list_entry_set_num(entry, true); imported = true; } else if (pos[0] == '=') { const char *value; @@ -2376,7 +2365,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event pos++; pos[0] = '\0'; entry = udev_device_add_property(event->dev, key, value); - udev_list_entry_set_flags(entry, 1); + udev_list_entry_set_num(entry, true); imported = true; } } @@ -2407,6 +2396,9 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event case TK_A_STRING_ESCAPE_REPLACE: esc = ESCAPE_REPLACE; break; + case TK_A_DB_PERSIST: + udev_device_set_db_persist(event->dev); + break; case TK_A_INOTIFY_WATCH: if (event->inotify_watch_final) break; @@ -2451,19 +2443,22 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event } case TK_A_MODE: { - char mode[UTIL_NAME_SIZE]; + char mode_str[UTIL_NAME_SIZE]; + mode_t mode; char *endptr; if (event->mode_final) break; - if (cur->key.op == OP_ASSIGN_FINAL) - event->mode_final = true; - udev_event_apply_format(event, &rules->buf[cur->key.value_off], mode, sizeof(mode)); - event->mode = strtol(mode, &endptr, 8); + udev_event_apply_format(event, &rules->buf[cur->key.value_off], mode_str, sizeof(mode_str)); + mode = strtol(mode_str, &endptr, 8); if (endptr[0] != '\0') { - err(event->udev, "invalide mode '%s' set default mode 0600\n", mode); - event->mode = 0600; + err(event->udev, "ignoring invalid mode '%s'\n", mode_str); + break; } + if (cur->key.op == OP_ASSIGN_FINAL) + event->mode_final = true; + event->mode_set = true; + event->mode = mode; info(event->udev, "MODE %#o %s:%u\n", event->mode, &rules->buf[rule->rule.filename_off], @@ -2497,6 +2492,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event break; if (cur->key.op == OP_ASSIGN_FINAL) event->mode_final = true; + event->mode_set = true; event->mode = cur->key.mode; info(event->udev, "MODE %#o %s:%u\n", event->mode, @@ -2518,7 +2514,7 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event entry = udev_device_add_property(event->dev, name, temp_value); /* store in db, skip private keys */ if (name[0] != '.') - udev_list_entry_set_flags(entry, 1); + udev_list_entry_set_num(entry, true); } else { udev_device_add_property(event->dev, name, NULL); } @@ -2601,9 +2597,6 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event } } break; - case TK_A_EVENT_TIMEOUT: - udev_device_set_event_timeout(event->dev, cur->key.event_timeout); - break; case TK_A_ATTR: { const char *key_name = &rules->buf[cur->key.attr_off]; @@ -2640,9 +2633,9 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event &rules->buf[rule->rule.filename_off], rule->rule.filename_line); list_entry = udev_list_entry_add(event->udev, &event->run_list, - &rules->buf[cur->key.value_off], NULL, 1, 0); + &rules->buf[cur->key.value_off], NULL, UDEV_LIST_UNIQUE); if (cur->key.fail_on_error) - udev_list_entry_set_flags(list_entry, 1); + udev_list_entry_set_num(list_entry, true); break; } case TK_A_GOTO: diff --git a/udev/udev.h b/udev/udev.h index 6833e60d61..88c32ec203 100644 --- a/udev/udev.h +++ b/udev/udev.h @@ -26,9 +26,6 @@ #include "libudev.h" #include "libudev-private.h" -#define DEFAULT_FAKE_PARTITIONS_COUNT 15 -#define UDEV_EVENT_TIMEOUT 180 - #define UDEV_CTRL_SOCK_PATH "@/org/kernel/udev/udevd" struct udev_event { @@ -44,10 +41,15 @@ struct udev_event { gid_t gid; struct udev_list_node run_list; int exec_delay; + unsigned long long birth_usec; + unsigned long long timeout_usec; + int fd_signal; + bool sigterm; bool inotify_watch; bool inotify_watch_final; bool group_final; bool owner_final; + bool mode_set; bool mode_final; bool name_final; bool devlink_final; @@ -64,17 +66,20 @@ struct udev_watch { struct udev_rules; struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names); void udev_rules_unref(struct udev_rules *rules); -int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event); +int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event, const sigset_t *sigmask); void udev_rules_apply_static_dev_perms(struct udev_rules *rules); /* udev-event.c */ struct udev_event *udev_event_new(struct udev_device *dev); void udev_event_unref(struct udev_event *event); -int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules); -int udev_event_execute_run(struct udev_event *event, const sigset_t *sigset); size_t udev_event_apply_format(struct udev_event *event, const char *src, char *dest, size_t size); int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string, char *result, size_t maxsize, int read_value); +int udev_event_spawn(struct udev_event *event, + const char *cmd, char **envp, const sigset_t *sigmask, + char *result, size_t ressize); +int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules, const sigset_t *sigset); +int udev_event_execute_run(struct udev_event *event, const sigset_t *sigset); /* udev-watch.c */ int udev_watch_init(struct udev *udev); diff --git a/udev/udevadm-control.c b/udev/udevadm-control.c index 0447804c95..69da86563d 100644 --- a/udev/udevadm-control.c +++ b/udev/udevadm-control.c @@ -30,21 +30,25 @@ static void print_help(void) { printf("Usage: udevadm control COMMAND\n" + " --exit instruct the daemon to cleanup and exit\n" " --log-priority=<level> set the udev log level for the daemon\n" " --stop-exec-queue keep udevd from executing events, queue only\n" " --start-exec-queue execute events, flush queue\n" " --reload-rules reloads the rules files\n" " --property=<KEY>=<value> set a global property for all events\n" " --children-max=<N> maximum number of children\n" + " --timeout=<seconds> maximum time to block for a reply\n" " --help print this help text\n\n"); } int udevadm_control(struct udev *udev, int argc, char *argv[]) { struct udev_ctrl *uctrl = NULL; + int timeout = 60; int rc = 1; static const struct option options[] = { + { "exit", no_argument, NULL, 'e' }, { "log-priority", required_argument, NULL, 'l' }, { "stop-exec-queue", no_argument, NULL, 's' }, { "start-exec-queue", no_argument, NULL, 'S' }, @@ -52,6 +56,7 @@ int udevadm_control(struct udev *udev, int argc, char *argv[]) { "property", required_argument, NULL, 'p' }, { "env", required_argument, NULL, 'p' }, { "children-max", required_argument, NULL, 'm' }, + { "timeout", required_argument, NULL, 't' }, { "help", no_argument, NULL, 'h' }, {} }; @@ -67,39 +72,46 @@ int udevadm_control(struct udev *udev, int argc, char *argv[]) for (;;) { int option; - int i; - char *endp; - option = getopt_long(argc, argv, "l:sSRp:m:h", options, NULL); + option = getopt_long(argc, argv, "el:sSRp:m:h", options, NULL); if (option == -1) break; switch (option) { - case 'l': + case 'e': + if (udev_ctrl_send_exit(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'l': { + int i; + i = util_log_priority(optarg); if (i < 0) { fprintf(stderr, "invalid number '%s'\n", optarg); - goto exit; + goto out; } - if (udev_ctrl_send_set_log_level(uctrl, util_log_priority(optarg)) < 0) + if (udev_ctrl_send_set_log_level(uctrl, util_log_priority(optarg), timeout) < 0) rc = 2; else rc = 0; break; + } case 's': - if (udev_ctrl_send_stop_exec_queue(uctrl) < 0) + if (udev_ctrl_send_stop_exec_queue(uctrl, timeout) < 0) rc = 2; else rc = 0; break; case 'S': - if (udev_ctrl_send_start_exec_queue(uctrl) < 0) + if (udev_ctrl_send_start_exec_queue(uctrl, timeout) < 0) rc = 2; else rc = 0; break; case 'R': - if (udev_ctrl_send_reload_rules(uctrl) < 0) + if (udev_ctrl_send_reload_rules(uctrl, timeout) < 0) rc = 2; else rc = 0; @@ -107,34 +119,45 @@ int udevadm_control(struct udev *udev, int argc, char *argv[]) case 'p': if (strchr(optarg, '=') == NULL) { fprintf(stderr, "expect <KEY>=<value> instead of '%s'\n", optarg); - goto exit; + goto out; } - if (udev_ctrl_send_set_env(uctrl, optarg) < 0) + if (udev_ctrl_send_set_env(uctrl, optarg, timeout) < 0) rc = 2; else rc = 0; break; - case 'm': + case 'm': { + char *endp; + int i; + i = strtoul(optarg, &endp, 0); if (endp[0] != '\0' || i < 1) { fprintf(stderr, "invalid number '%s'\n", optarg); - goto exit; + goto out; } - if (udev_ctrl_send_set_children_max(uctrl, i) < 0) + if (udev_ctrl_send_set_children_max(uctrl, i, timeout) < 0) rc = 2; else rc = 0; break; + } + case 't': { + int seconds; + + seconds = atoi(optarg); + if (seconds >= 0) + timeout = seconds; + else + fprintf(stderr, "invalid timeout value\n"); + break; + } case 'h': print_help(); rc = 0; break; } } - - if (rc == 1) - err(udev, "unrecognized command\n"); -exit: +out: udev_ctrl_unref(uctrl); return rc; } diff --git a/udev/udevadm-info.c b/udev/udevadm-info.c index 9357f67208..87c1c32314 100644 --- a/udev/udevadm-info.c +++ b/udev/udevadm-info.c @@ -200,6 +200,81 @@ static int export_devices(struct udev *udev) return 0; } +static void cleanup_dir(DIR *dir, mode_t mask, int depth) +{ + struct dirent *dent; + + if (depth <= 0) + return; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + struct stat stats; + + if (dent->d_name[0] == '.') + continue; + if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0) + continue; + if ((stats.st_mode & mask) != 0) + continue; + if (S_ISDIR(stats.st_mode)) { + DIR *dir2; + + dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)); + if (dir2 != NULL) { + cleanup_dir(dir2, mask, depth-1); + closedir(dir2); + } + unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR); + } else { + unlinkat(dirfd(dir), dent->d_name, 0); + } + } +} + +static void cleanup_db(struct udev *udev) +{ + char filename[UTIL_PATH_SIZE]; + DIR *dir; + + util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/queue.bin", NULL); + unlink(filename); + + util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/data", NULL); + dir = opendir(filename); + if (dir != NULL) { + cleanup_dir(dir, S_ISVTX, 1); + closedir(dir); + } + + util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/links", NULL); + dir = opendir(filename); + if (dir != NULL) { + cleanup_dir(dir, 0, 2); + closedir(dir); + } + + util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/tags", NULL); + dir = opendir(filename); + if (dir != NULL) { + cleanup_dir(dir, 0, 2); + closedir(dir); + } + + util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/watch", NULL); + dir = opendir(filename); + if (dir != NULL) { + cleanup_dir(dir, 0, 1); + closedir(dir); + } + + util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/firmware-missing", NULL); + dir = opendir(filename); + if (dir != NULL) { + cleanup_dir(dir, 0, 1); + closedir(dir); + } +} + int udevadm_info(struct udev *udev, int argc, char *argv[]) { struct udev_device *device = NULL; @@ -216,6 +291,7 @@ int udevadm_info(struct udev *udev, int argc, char *argv[]) { "path", required_argument, NULL, 'p' }, { "query", required_argument, NULL, 'q' }, { "attribute-walk", no_argument, NULL, 'a' }, + { "cleanup-db", no_argument, NULL, 'c' }, { "export-db", no_argument, NULL, 'e' }, { "root", no_argument, NULL, 'r' }, { "run", no_argument, NULL, 'R' }, @@ -248,7 +324,7 @@ int udevadm_info(struct udev *udev, int argc, char *argv[]) int option; struct stat statbuf; - option = getopt_long(argc, argv, "aed:n:p:q:rxP:RVh", options, NULL); + option = getopt_long(argc, argv, "aced:n:p:q:rxP:RVh", options, NULL); if (option == -1) break; @@ -345,6 +421,9 @@ int udevadm_info(struct udev *udev, int argc, char *argv[]) case 'e': export_devices(udev); goto exit; + case 'c': + cleanup_db(udev); + goto exit; case 'x': export = true; break; @@ -371,6 +450,7 @@ int udevadm_info(struct udev *udev, int argc, char *argv[]) " --export export key/value pairs\n" " --export-prefix export the key name with a prefix\n" " --export-db export the content of the udev database\n" + " --cleanup-db cleanup the udev database\n" " --help\n\n"); goto exit; default: diff --git a/udev/udevadm-monitor.c b/udev/udevadm-monitor.c index 85252bb7d9..f3a72cf17e 100644 --- a/udev/udevadm-monitor.c +++ b/udev/udevadm-monitor.c @@ -27,18 +27,18 @@ #include <sys/time.h> #include <sys/socket.h> #include <sys/un.h> -#include <sys/select.h> +#include <sys/epoll.h> #include <linux/types.h> #include <linux/netlink.h> #include "udev.h" -static int udev_exit; +static bool udev_exit; static void sig_handler(int signum) { if (signum == SIGINT || signum == SIGTERM) - udev_exit = 1; + udev_exit = true; } static void print_device(struct udev_device *device, const char *source, int prop) @@ -69,14 +69,16 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) struct sigaction act; sigset_t mask; int option; - int prop = 0; - int print_kernel = 0; - int print_udev = 0; + bool prop = false; + bool print_kernel = false; + bool print_udev = false; struct udev_list_node subsystem_match_list; struct udev_list_node tag_match_list; struct udev_monitor *udev_monitor = NULL; struct udev_monitor *kernel_monitor = NULL; - fd_set readfds; + int fd_ep = -1; + int fd_kernel = -1, fd_udev = -1; + struct epoll_event ep_kernel, ep_udev; int rc = 0; static const struct option options[] = { @@ -92,6 +94,7 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) udev_list_init(&subsystem_match_list); udev_list_init(&tag_match_list); + for (;;) { option = getopt_long(argc, argv, "pekus:t:h", options, NULL); if (option == -1) @@ -100,13 +103,13 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) switch (option) { case 'p': case 'e': - prop = 1; + prop = true; break; case 'k': - print_kernel = 1; + print_kernel = true; break; case 'u': - print_udev = 1; + print_udev = true; break; case 's': { @@ -119,11 +122,11 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) devtype[0] = '\0'; devtype++; } - udev_list_entry_add(udev, &subsystem_match_list, subsys, devtype, 0, 0); + udev_list_entry_add(udev, &subsystem_match_list, subsys, devtype, 0); break; } case 't': - udev_list_entry_add(udev, &tag_match_list, optarg, NULL, 0, 0); + udev_list_entry_add(udev, &tag_match_list, optarg, NULL, 0); break; case 'h': printf("Usage: udevadm monitor [--property] [--kernel] [--udev] [--help]\n" @@ -133,14 +136,13 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) " --subsystem-match=<subsystem[/devtype]> filter events by subsystem\n" " --tag-match=<tag> filter events by tag\n" " --help\n\n"); - default: goto out; } } if (!print_kernel && !print_udev) { - print_kernel = 1; - print_udev =1; + print_kernel = true; + print_udev = true; } /* set signal handlers */ @@ -155,6 +157,12 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) sigaddset(&mask, SIGTERM); sigprocmask(SIG_UNBLOCK, &mask, NULL); + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + err(udev, "error creating epoll fd: %m\n"); + goto out; + } + printf("monitor will print the received events for:\n"); if (print_udev) { struct udev_list_entry *entry; @@ -166,6 +174,7 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) goto out; } udev_monitor_set_receive_buffer_size(udev_monitor, 128*1024*1024); + fd_udev = udev_monitor_get_fd(udev_monitor); udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) { const char *subsys = udev_list_entry_get_name(entry); @@ -187,8 +196,18 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) rc = 2; goto out; } + + memset(&ep_udev, 0, sizeof(struct epoll_event)); + ep_udev.events = EPOLLIN; + ep_udev.data.fd = fd_udev; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) { + err(udev, "fail to add fd to epoll: %m\n"); + goto out; + } + printf("UDEV - the event which udev sends out after rule processing\n"); } + if (print_kernel) { struct udev_list_entry *entry; @@ -199,6 +218,7 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) goto out; } udev_monitor_set_receive_buffer_size(kernel_monitor, 128*1024*1024); + fd_kernel = udev_monitor_get_fd(kernel_monitor); udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) { const char *subsys = udev_list_entry_get_name(entry); @@ -212,49 +232,54 @@ int udevadm_monitor(struct udev *udev, int argc, char *argv[]) rc = 4; goto out; } + + memset(&ep_kernel, 0, sizeof(struct epoll_event)); + ep_kernel.events = EPOLLIN; + ep_kernel.data.fd = fd_kernel; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_kernel, &ep_kernel) < 0) { + err(udev, "fail to add fd to epoll: %m\n"); + goto out; + } + printf("KERNEL - the kernel uevent\n"); } printf("\n"); while (!udev_exit) { int fdcount; + struct epoll_event ev[4]; + int i; - FD_ZERO(&readfds); - if (kernel_monitor != NULL) - FD_SET(udev_monitor_get_fd(kernel_monitor), &readfds); - if (udev_monitor != NULL) - FD_SET(udev_monitor_get_fd(udev_monitor), &readfds); - - fdcount = select(MAX(udev_monitor_get_fd(kernel_monitor), udev_monitor_get_fd(udev_monitor))+1, - &readfds, NULL, NULL, NULL); + fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), -1); if (fdcount < 0) { if (errno != EINTR) fprintf(stderr, "error receiving uevent message: %m\n"); continue; } - if ((kernel_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(kernel_monitor), &readfds)) { - struct udev_device *device; - - device = udev_monitor_receive_device(kernel_monitor); - if (device == NULL) - continue; - print_device(device, "KERNEL", prop); - udev_device_unref(device); - } - - if ((udev_monitor != NULL) && FD_ISSET(udev_monitor_get_fd(udev_monitor), &readfds)) { - struct udev_device *device; - - device = udev_monitor_receive_device(udev_monitor); - if (device == NULL) - continue; - print_device(device, "UDEV", prop); - udev_device_unref(device); + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_kernel && ev[i].events & EPOLLIN) { + struct udev_device *device; + + device = udev_monitor_receive_device(kernel_monitor); + if (device == NULL) + continue; + print_device(device, "KERNEL", prop); + udev_device_unref(device); + } else if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) { + struct udev_device *device; + + device = udev_monitor_receive_device(udev_monitor); + if (device == NULL) + continue; + print_device(device, "UDEV", prop); + udev_device_unref(device); + } } } - out: + if (fd_ep >= 0) + close(fd_ep); udev_monitor_unref(udev_monitor); udev_monitor_unref(kernel_monitor); udev_list_cleanup_entries(udev, &subsystem_match_list); diff --git a/udev/udevadm-settle.c b/udev/udevadm-settle.c index 1423cec6dc..b717ae60de 100644 --- a/udev/udevadm-settle.c +++ b/udev/udevadm-settle.c @@ -29,26 +29,13 @@ #include <getopt.h> #include <signal.h> #include <time.h> +#include <sys/inotify.h> +#include <sys/poll.h> #include <sys/stat.h> #include <sys/types.h> #include "udev.h" -#define DEFAULT_TIMEOUT 180 -#define LOOP_PER_SECOND 20 - -static volatile sig_atomic_t is_timeout; - -static void sig_handler(int signum) -{ - switch (signum) { - case SIGALRM: - is_timeout = 1; - case SIGUSR1: - ; - } -} - int udevadm_settle(struct udev *udev, int argc, char *argv[]) { static const struct option options[] = { @@ -60,30 +47,18 @@ int udevadm_settle(struct udev *udev, int argc, char *argv[]) { "help", no_argument, NULL, 'h' }, {} }; + unsigned long long start_usec = now_usec(); unsigned long long start = 0; unsigned long long end = 0; int quiet = 0; const char *exists = NULL; - int timeout = DEFAULT_TIMEOUT; - struct sigaction act; - sigset_t mask; + unsigned int timeout = 120; + struct pollfd pfd[1]; struct udev_queue *udev_queue = NULL; - int rc = 1; + int rc = EXIT_FAILURE; dbg(udev, "version %s\n", VERSION); - /* set signal handlers */ - memset(&act, 0x00, sizeof(act)); - act.sa_handler = sig_handler; - sigemptyset (&act.sa_mask); - act.sa_flags = 0; - sigaction(SIGALRM, &act, NULL); - sigaction(SIGUSR1, &act, NULL); - sigemptyset(&mask); - sigaddset(&mask, SIGUSR1); - sigaddset(&mask, SIGALRM); - sigprocmask(SIG_UNBLOCK, &mask, NULL); - for (;;) { int option; int seconds; @@ -125,11 +100,6 @@ int udevadm_settle(struct udev *udev, int argc, char *argv[]) } } - if (timeout > 0) - alarm(timeout); - else - is_timeout = 1; - udev_queue = udev_queue_new(udev); if (udev_queue == NULL) exit(2); @@ -168,62 +138,84 @@ int udevadm_settle(struct udev *udev, int argc, char *argv[]) uctrl = udev_ctrl_new_from_socket(udev, UDEV_CTRL_SOCK_PATH); if (uctrl != NULL) { - sigset_t oldmask; - - sigemptyset(&mask); - sigaddset(&mask, SIGUSR1); - sigaddset(&mask, SIGALRM); - sigprocmask(SIG_BLOCK, &mask, &oldmask); - if (udev_ctrl_send_settle(uctrl) > 0) - sigsuspend(&oldmask); - sigprocmask(SIG_SETMASK, &oldmask, NULL); + if (udev_ctrl_send_ping(uctrl, timeout) < 0) { + info(udev, "no connection to daemon\n"); + udev_ctrl_unref(uctrl); + rc = EXIT_SUCCESS; + goto out; + } udev_ctrl_unref(uctrl); } } + pfd[0].events = POLLIN; + pfd[0].fd = inotify_init1(IN_CLOEXEC); + if (pfd[0].fd < 0) { + err(udev, "inotify_init failed: %m\n"); + } else { + if (inotify_add_watch(pfd[0].fd, udev_get_run_path(udev), IN_MOVED_TO) < 0) { + err(udev, "watching '%s' failed\n", udev_get_run_path(udev)); + close(pfd[0].fd); + pfd[0].fd = -1; + } + } + for (;;) { struct stat statbuf; - const struct timespec duration = { 0 , 1000 * 1000 * 1000 / LOOP_PER_SECOND }; if (exists != NULL && stat(exists, &statbuf) == 0) { - rc = 0; + rc = EXIT_SUCCESS; break; } if (start > 0) { /* if asked for, wait for a specific sequence of events */ if (udev_queue_get_seqnum_sequence_is_finished(udev_queue, start, end) == 1) { - rc = 0; + rc = EXIT_SUCCESS; break; } } else { /* exit if queue is empty */ if (udev_queue_get_queue_is_empty(udev_queue)) { - rc = 0; + rc = EXIT_SUCCESS; break; } } - if (is_timeout) - break; + if (pfd[0].fd >= 0) { + /* wake up once every second, or whenever the queue file gets gets closed */ + if (poll(pfd, 1, 1000) > 0 && pfd[0].revents & POLLIN) { + char buf[sizeof(struct inotify_event) + PATH_MAX]; - nanosleep(&duration, NULL); - } + read(pfd[0].fd, buf, sizeof(buf)); + } + } else { + sleep(1); + } - /* if we reached the timeout, print the list of remaining events */ - if (is_timeout) { - struct udev_list_entry *list_entry; - - if (!quiet && udev_queue_get_queued_list_entry(udev_queue) != NULL) { - info(udev, "timeout waiting for udev queue\n"); - printf("\nudevadm settle - timeout of %i seconds reached, the event queue contains:\n", timeout); - udev_list_entry_foreach(list_entry, udev_queue_get_queued_list_entry(udev_queue)) - printf(" %s (%s)\n", - udev_list_entry_get_name(list_entry), - udev_list_entry_get_value(list_entry)); + if (timeout > 0) { + unsigned long long age_usec; + + age_usec = now_usec() - start_usec; + if (age_usec / (1000 * 1000) >= timeout) { + struct udev_list_entry *list_entry; + + if (!quiet && udev_queue_get_queued_list_entry(udev_queue) != NULL) { + info(udev, "timeout waiting for udev queue\n"); + printf("\nudevadm settle - timeout of %i seconds reached, the event queue contains:\n", timeout); + udev_list_entry_foreach(list_entry, udev_queue_get_queued_list_entry(udev_queue)) + printf(" %s (%s)\n", + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + } + + break; + } } } - +out: + if (pfd[0].fd >= 0) + close(pfd[0].fd); udev_queue_unref(udev_queue); return rc; } diff --git a/udev/udevadm-test.c b/udev/udevadm-test.c index 4db70c49dd..bb7a0fd8a1 100644 --- a/udev/udevadm-test.c +++ b/udev/udevadm-test.c @@ -27,23 +27,27 @@ #include <signal.h> #include <syslog.h> #include <getopt.h> +#include <sys/signalfd.h> #include "udev.h" int udevadm_test(struct udev *udev, int argc, char *argv[]) { + int resolve_names = 1; char filename[UTIL_PATH_SIZE]; const char *action = "add"; const char *syspath = NULL; - struct udev_event *event; - struct udev_device *dev; + struct udev_event *event = NULL; + struct udev_device *dev = NULL; struct udev_rules *rules = NULL; struct udev_list_entry *entry; + sigset_t mask, sigmask_orig; int err; int rc = 0; static const struct option options[] = { { "action", required_argument, NULL, 'a' }, + { "resolve-names", required_argument, NULL, 'N' }, { "help", no_argument, NULL, 'h' }, {} }; @@ -53,7 +57,7 @@ int udevadm_test(struct udev *udev, int argc, char *argv[]) for (;;) { int option; - option = getopt_long(argc, argv, "a:s:fh", options, NULL); + option = getopt_long(argc, argv, "a:s:N:fh", options, NULL); if (option == -1) break; @@ -62,21 +66,34 @@ int udevadm_test(struct udev *udev, int argc, char *argv[]) case 'a': action = optarg; break; + case 'N': + if (strcmp (optarg, "early") == 0) { + resolve_names = 1; + } else if (strcmp (optarg, "late") == 0) { + resolve_names = 0; + } else if (strcmp (optarg, "never") == 0) { + resolve_names = -1; + } else { + fprintf(stderr, "resolve-names must be early, late or never\n"); + err(udev, "resolve-names must be early, late or never\n"); + exit(EXIT_FAILURE); + } + break; case 'h': printf("Usage: udevadm test OPTIONS <syspath>\n" " --action=<string> set action string\n" " --help\n\n"); - exit(0); + exit(EXIT_SUCCESS); default: - exit(1); + exit(EXIT_FAILURE); } } syspath = argv[optind]; if (syspath == NULL) { fprintf(stderr, "syspath parameter missing\n"); - rc = 1; - goto exit; + rc = 2; + goto out; } printf("This program is for debugging only, it does not run any program,\n" @@ -84,11 +101,13 @@ int udevadm_test(struct udev *udev, int argc, char *argv[]) "some values may be different, or not available at a simulation run.\n" "\n"); - rules = udev_rules_new(udev, 1); + sigprocmask(SIG_SETMASK, NULL, &sigmask_orig); + + rules = udev_rules_new(udev, resolve_names); if (rules == NULL) { fprintf(stderr, "error reading rules\n"); - rc = 1; - goto exit; + rc = 3; + goto out; } /* add /sys if needed */ @@ -101,8 +120,8 @@ int udevadm_test(struct udev *udev, int argc, char *argv[]) dev = udev_device_new_from_syspath(udev, filename); if (dev == NULL) { fprintf(stderr, "unable to open device '%s'\n", filename); - rc = 2; - goto exit; + rc = 4; + goto out; } /* skip reading of db, but read kernel parameters */ @@ -111,24 +130,34 @@ int udevadm_test(struct udev *udev, int argc, char *argv[]) udev_device_set_action(dev, action); event = udev_event_new(dev); - err = udev_event_execute_rules(event, rules); - if (udev_device_get_event_timeout(dev) >= 0) - info(udev, "custom event timeout: %i\n", udev_device_get_event_timeout(dev)); + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, &sigmask_orig); + event->fd_signal = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (event->fd_signal < 0) { + fprintf(stderr, "error creating signalfd\n"); + rc = 5; + goto out; + } + + err = udev_event_execute_rules(event, rules, &sigmask_orig); udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) - info(udev, "%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); + printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); - if (err == 0) + if (err == 0) { udev_list_entry_foreach(entry, udev_list_get_entry(&event->run_list)) { char program[UTIL_PATH_SIZE]; udev_event_apply_format(event, udev_list_entry_get_name(entry), program, sizeof(program)); - info(udev, "run: '%s'\n", program); + printf("run: '%s'\n", program); } + } +out: + if (event != NULL && event->fd_signal >= 0) + close(event->fd_signal); udev_event_unref(event); udev_device_unref(dev); -exit: udev_rules_unref(rules); return rc; } diff --git a/udev/udevadm.c b/udev/udevadm.c index 336d723705..2a29158917 100644 --- a/udev/udevadm.c +++ b/udev/udevadm.c @@ -25,7 +25,7 @@ #include "udev.h" -static int debug; +static bool debug; static void log_fn(struct udev *udev, int priority, const char *file, int line, const char *fn, @@ -101,7 +101,7 @@ static const struct command cmds[] = { .name = "test", .cmd = udevadm_test, .help = "simulation run", - .debug = 1, + .debug = true, }, { .name = "version", @@ -117,7 +117,7 @@ static const struct command cmds[] = { static int run_command(struct udev *udev, const struct command *cmd, int argc, char *argv[]) { if (cmd->debug) { - debug = 1; + debug = true; if (udev_get_log_priority(udev) < LOG_INFO) udev_set_log_priority(udev, LOG_INFO); } @@ -155,7 +155,7 @@ int main(int argc, char *argv[]) switch (option) { case 'd': - debug = 1; + debug = true; if (udev_get_log_priority(udev) < LOG_INFO) udev_set_log_priority(udev, LOG_INFO); break; diff --git a/udev/udevadm.xml b/udev/udevadm.xml index 7a682a68ef..b5fb971f3b 100644 --- a/udev/udevadm.xml +++ b/udev/udevadm.xml @@ -150,6 +150,12 @@ </listitem> </varlistentry> <varlistentry> + <term><option>--cleanup-db</option></term> + <listitem> + <para>Cleanup the udev database.</para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--version</option></term> <listitem> <para>Print version.</para> @@ -256,7 +262,7 @@ <term><option>--timeout=<replaceable>seconds</replaceable></option></term> <listitem> <para>Maximum number of seconds to wait for the event queue to become empty. - The default value is 180 seconds. A value of 0 will check if the queue is empty + The default value is 120 seconds. A value of 0 will check if the queue is empty and always return immediately.</para> </listitem> </varlistentry> @@ -297,6 +303,12 @@ <para>Modify the internal state of the running udev daemon.</para> <variablelist> <varlistentry> + <term><option>--exit</option></term> + <listitem> + <para>Signal and wait for udevd to exit.</para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--log-priority=<replaceable>value</replaceable></option></term> <listitem> <para>Set the internal log level of udevd. Valid values are the numerical @@ -340,6 +352,12 @@ </listitem> </varlistentry> <varlistentry> + <term><option>--timeout=</option><replaceable>seconds</replaceable></term> + <listitem> + <para>The maximum number seonds to wait for a reply from udevd.</para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--help</option></term> <listitem> <para>Print help text.</para> diff --git a/udev/udevd.c b/udev/udevd.c index df5c1995bc..e7384e19a2 100644 --- a/udev/udevd.c +++ b/udev/udevd.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2009 Kay Sievers <kay.sievers@vrfy.org> + * Copyright (C) 2004-2011 Kay Sievers <kay.sievers@vrfy.org> * Copyright (C) 2004 Chris Friesen <chris_friesen@sympatico.ca> * Copyright (C) 2009 Canonical Ltd. * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com> @@ -36,7 +36,7 @@ #include <sys/socket.h> #include <sys/un.h> #include <sys/signalfd.h> -#include <sys/select.h> +#include <sys/epoll.h> #include <sys/poll.h> #include <sys/wait.h> #include <sys/stat.h> @@ -47,9 +47,6 @@ #include "udev.h" #include "sd-daemon.h" -#define UDEVD_PRIORITY -4 -#define UDEV_PRIORITY -2 - static bool debug; static void log_fn(struct udev *udev, int priority, @@ -76,33 +73,18 @@ static struct udev_queue_export *udev_queue_export; static struct udev_ctrl *udev_ctrl; static struct udev_monitor *monitor; static int worker_watch[2] = { -1, -1 }; -static pid_t settle_pid; +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 int children; static int children_max; static int exec_delay; -static sigset_t orig_sigmask; -static struct udev_list_node event_list; -static struct udev_list_node worker_list; +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 poll_fd { - FD_CONTROL, - FD_NETLINK, - FD_INOTIFY, - FD_SIGNAL, - FD_WORKER, -}; - -static struct pollfd pfd[] = { - [FD_NETLINK] = { .events = POLLIN, .fd = -1 }, - [FD_WORKER] = { .events = POLLIN, .fd = -1 }, - [FD_SIGNAL] = { .events = POLLIN, .fd = -1 }, - [FD_INOTIFY] = { .events = POLLIN, .fd = -1 }, - [FD_CONTROL] = { .events = POLLIN, .fd = -1 }, -}; enum event_state { EVENT_UNDEF, @@ -135,6 +117,8 @@ static struct event *node_to_event(struct udev_list_node *node) return (struct event *)event; } +static void event_queue_cleanup(struct udev *udev, enum event_state type); + enum worker_state { WORKER_UNDEF, WORKER_RUNNING, @@ -167,61 +151,65 @@ static struct worker *node_to_worker(struct udev_list_node *node) return (struct worker *)worker; } -static void event_queue_delete(struct event *event) +static void event_queue_delete(struct event *event, bool export) { udev_list_node_remove(&event->node); - /* 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); - - info(event->udev, "seq %llu done with %i\n", udev_device_get_seqnum(event->dev), event->exitcode); + 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); + 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++; return 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) { worker->refcount--; if (worker->refcount > 0) return; - - udev_list_node_remove(&worker->node); - udev_monitor_unref(worker->monitor); - children--; info(worker->udev, "worker [%u] cleaned up\n", worker->pid); - free(worker); + worker_cleanup(worker); +} + +static void worker_list_cleanup(struct udev *udev) +{ + struct udev_list_node *loop, *tmp; + + udev_list_node_foreach_safe(loop, tmp, &worker_list) { + struct worker *worker = node_to_worker(loop); + + worker_cleanup(worker); + } } static void worker_new(struct event *event) { + struct udev *udev = event->udev; 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(event->udev, NULL); + worker_monitor = udev_monitor_new_from_netlink(udev, NULL); if (worker_monitor == NULL) return; /* allow the main daemon netlink address to send devices to the worker */ @@ -235,80 +223,93 @@ static void worker_new(struct event *event) } /* worker + event reference */ worker->refcount = 2; - worker->udev = event->udev; + worker->udev = udev; 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; + event->dev = NULL; + free(worker); + worker_list_cleanup(udev); + event_queue_cleanup(udev, EVENT_UNDEF); udev_queue_export_unref(udev_queue_export); udev_monitor_unref(monitor); udev_ctrl_unref(udev_ctrl); - close(pfd[FD_SIGNAL].fd); + close(fd_signal); + close(fd_ep); close(worker_watch[READ_END]); - udev_log_close(); - udev_log_init("udevd-work"); - setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY); - - /* 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); - /* initial device */ - dev = event->dev; - - do { + for (;;) { struct udev_event *udev_event; - struct worker_message msg = {}; - int err; + struct worker_message msg; int failed = 0; + int err; - info(event->udev, "seq %llu running\n", udev_device_get_seqnum(dev)); + 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); + failed = udev_event_execute_run(udev_event, &sigmask_orig); /* apply/restore inotify watch */ if (err == 0 && udev_event->inotify_watch) { - udev_watch_begin(udev_event->udev, dev); + udev_watch_begin(udev, dev); udev_device_update_db(dev); } @@ -316,6 +317,7 @@ 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) @@ -323,36 +325,70 @@ static void worker_new(struct event *event) msg.pid = getpid(); send(worker_watch[WRITE_END], &msg, sizeof(struct worker_message), 0); - info(event->udev, "seq %llu processed with %i\n", udev_device_get_seqnum(dev), err); - udev_event_unref(udev_event); + info(udev, "seq %llu processed with %i\n", udev_device_get_seqnum(dev), err); + 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); - if (pmon.revents & POLLIN) { - dev = udev_monitor_receive_device(worker_monitor); - if (dev != NULL) - break; + /* 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; } - } - } while (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); + } 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; + } + } + } + } + } +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(0); + exit(rc); } case -1: udev_monitor_unref(worker_monitor); event->state = EVENT_QUEUED; free(worker); - err(event->udev, "fork of child failed: %m\n"); + err(udev, "fork of child failed: %m\n"); break; default: /* close monitor, but keep address around */ @@ -364,7 +400,7 @@ static void worker_new(struct event *event) event->state = EVENT_RUNNING; udev_list_node_append(&worker->node, &worker_list); children++; - info(event->udev, "seq %llu forked new worker [%u]\n", udev_device_get_seqnum(event->dev), pid); + info(udev, "seq %llu forked new worker [%u]\n", udev_device_get_seqnum(event->dev), pid); break; } } @@ -535,7 +571,7 @@ static bool is_devpath_busy(struct event *event) return false; } -static void events_start(struct udev *udev) +static void event_queue_start(struct udev *udev) { struct udev_list_node *loop; @@ -555,14 +591,28 @@ static void events_start(struct udev *udev) } } -static void worker_returned(void) +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) { + struct event *event = node_to_event(loop); + + if (match_type != EVENT_UNDEF && match_type != event->state) + continue; + + event_queue_delete(event, false); + } +} + +static void worker_returned(int fd_worker) { for (;;) { 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); + size = recv(fd_worker, &msg, sizeof(struct worker_message), MSG_DONTWAIT); if (size != sizeof(struct worker_message)) break; @@ -575,7 +625,7 @@ static void worker_returned(void) /* worker returned */ worker->event->exitcode = msg.exitcode; - event_queue_delete(worker->event); + event_queue_delete(worker->event, true); worker->event = NULL; if (worker->state != WORKER_KILLED) worker->state = WORKER_IDLE; @@ -586,16 +636,21 @@ static void worker_returned(void) } /* receive the udevd message from userspace */ -static void 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_msg *ctrl_msg; + struct udev_ctrl_connection *ctrl_conn; + struct udev_ctrl_msg *ctrl_msg = NULL; const char *str; int i; - ctrl_msg = udev_ctrl_receive_msg(uctrl); + ctrl_conn = udev_ctrl_get_connection(uctrl); + if (ctrl_conn == NULL) + goto out; + + ctrl_msg = udev_ctrl_receive_msg(ctrl_conn); if (ctrl_msg == NULL) - return; + goto out; i = udev_ctrl_get_set_log_level(ctrl_msg); if (i >= 0) { @@ -652,13 +707,18 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl) children_max = i; } - 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; + if (udev_ctrl_get_ping(ctrl_msg) > 0) + info(udev, "udevd message (SYNC) received\n"); + + if (udev_ctrl_get_exit(ctrl_msg) > 0) { + info(udev, "udevd message (EXIT) received\n"); + udev_exit = true; + /* keep reference to block the client until we exit */ + udev_ctrl_connection_ref(ctrl_conn); } +out: udev_ctrl_msg_unref(ctrl_msg); + return udev_ctrl_connection_unref(ctrl_conn); } /* read inotify messages */ @@ -668,7 +728,7 @@ static int handle_inotify(struct udev *udev) char *buf; struct inotify_event *ev; - if ((ioctl(pfd[FD_INOTIFY].fd, FIONREAD, &nbytes) < 0) || (nbytes <= 0)) + if ((ioctl(fd_inotify, FIONREAD, &nbytes) < 0) || (nbytes <= 0)) return 0; buf = malloc(nbytes); @@ -677,7 +737,7 @@ static int handle_inotify(struct udev *udev) return -1; } - nbytes = read(pfd[FD_INOTIFY].fd, buf, nbytes); + nbytes = read(fd_inotify, buf, nbytes); for (pos = 0; pos < nbytes; pos += sizeof(struct inotify_event) + ev->len) { struct udev_device *dev; @@ -706,9 +766,11 @@ static int handle_inotify(struct udev *udev) info(udev, "device %s closed, synthesising 'change'\n", udev_device_get_devnode(dev)); util_strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); fd = open(filename, O_WRONLY); - if (fd < 0 || write(fd, "change", 6) < 0) - info(udev, "error writing uevent: %m\n"); - close(fd); + if (fd >= 0) { + if (write(fd, "change", 6) < 0) + info(udev, "error writing uevent: %m\n"); + close(fd); + } } if (ev->mask & IN_IGNORED) udev_watch_end(udev, dev); @@ -744,15 +806,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); - /* drop reference from running event */ + event_queue_delete(worker->event, true); + /* drop reference taken for state 'running' */ worker_unref(worker); } } @@ -994,7 +1070,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); } @@ -1063,10 +1139,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; @@ -1081,6 +1192,11 @@ int main(int argc, char *argv[]) { "version", no_argument, NULL, 'V' }, {} }; + int fd_ctrl = -1; + int fd_netlink = -1; + int fd_worker = -1; + struct epoll_event ep_ctrl, ep_inotify, ep_signal, ep_netlink, ep_worker; + struct udev_ctrl_connection *ctrl_conn = NULL; int rc = 1; udev = udev_new(); @@ -1108,6 +1224,8 @@ int main(int argc, char *argv[]) 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 */ @@ -1218,47 +1336,140 @@ 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_DGRAM, -1)) - udev_ctrl = udev_ctrl_new_from_fd(udev, SD_LISTEN_FDS_START); - else + + 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); + 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_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 (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; } - pfd[FD_CONTROL].fd = 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); - pfd[FD_NETLINK].fd = udev_monitor_get_fd(monitor); - pfd[FD_INOTIFY].fd = udev_watch_init(udev); - if (pfd[FD_INOTIFY].fd < 0) { + if (daemonize) { + pid_t pid; + int fd; + + pid = fork(); + switch (pid) { + case 0: + break; + case -1: + err(udev, "fork of daemon failed: %m\n"); + rc = 4; + goto exit; + default: + rc = EXIT_SUCCESS; + goto exit_keep_queue; + } + + 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"); err(udev, "error initializing inotify\n"); rc = 4; @@ -1266,15 +1477,15 @@ int main(int argc, char *argv[]) } if (udev_get_rules_path(udev) != NULL) { - inotify_add_watch(pfd[FD_INOTIFY].fd, udev_get_rules_path(udev), + 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(pfd[FD_INOTIFY].fd, LIBEXECDIR "/rules.d", + inotify_add_watch(fd_inotify, LIBEXECDIR "/rules.d", IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); - inotify_add_watch(pfd[FD_INOTIFY].fd, SYSCONFDIR "/udev/rules.d", + inotify_add_watch(fd_inotify, SYSCONFDIR "/udev/rules.d", IN_DELETE | IN_MOVE | IN_CLOSE_WRITE); /* watch dynamic rules directory */ @@ -1283,30 +1494,30 @@ int main(int argc, char *argv[]) util_create_path(udev, filename); mkdir(filename, 0755); } - inotify_add_watch(pfd[FD_INOTIFY].fd, filename, + 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); - 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"); + 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"); + 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; } - pfd[FD_WORKER].fd = worker_watch[READ_END]; + fd_worker = worker_watch[READ_END]; rules = udev_rules_new(udev, resolve_names); if (rules == NULL) { @@ -1314,69 +1525,43 @@ 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; - } - - /* 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); + memset(&ep_ctrl, 0, sizeof(struct epoll_event)); + ep_ctrl.events = EPOLLIN; + ep_ctrl.data.fd = fd_ctrl; - if (daemonize) { - pid_t pid; + memset(&ep_inotify, 0, sizeof(struct epoll_event)); + ep_inotify.events = EPOLLIN; + ep_inotify.data.fd = fd_inotify; - pid = fork(); - switch (pid) { - case 0: - break; - case -1: - err(udev, "fork of daemon failed: %m\n"); - rc = 4; - goto exit; - default: - rc = 0; - goto exit; - } - } else { - sd_notify(1, "READY=1"); - } + memset(&ep_signal, 0, sizeof(struct epoll_event)); + ep_signal.events = EPOLLIN; + ep_signal.data.fd = fd_signal; - /* set scheduling priority for the main daemon process */ - setpriority(PRIO_PROCESS, 0, UDEVD_PRIORITY); + memset(&ep_netlink, 0, sizeof(struct epoll_event)); + ep_netlink.events = EPOLLIN; + ep_netlink.data.fd = fd_netlink; - setsid(); + memset(&ep_worker, 0, sizeof(struct epoll_event)); + ep_worker.events = EPOLLIN; + ep_worker.data.fd = fd_worker; - f = fopen("/dev/kmsg", "w"); - if (f != NULL) { - fprintf(f, "<30>udev[%u]: starting version " VERSION "\n", getpid()); - fclose(f); + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + err(udev, "error creating epoll fd: %m\n"); + goto exit; } - - 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 (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_ctrl, &ep_ctrl) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_inotify, &ep_inotify) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_signal, &ep_signal) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_netlink, &ep_netlink) < 0 || + epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_worker, &ep_worker) < 0) { + err(udev, "fail to add fds to epoll: %m\n"); + goto exit; } + /* if needed, convert old database from earlier udev version */ + convert_db(udev); + if (children_max <= 0) { int memsize = mem_size_mb(); @@ -1393,30 +1578,79 @@ int main(int argc, char *argv[]) udev_list_init(&event_list); udev_list_init(&worker_list); - while (!udev_exit) { + for (;;) { + struct epoll_event ev[8]; int fdcount; int timeout; + bool is_worker, is_signal, is_inotify, is_netlink, is_ctrl; + int i; + + if (udev_exit) { + /* close sources of new events and discard buffered events */ + if (fd_ctrl >= 0) { + epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_ctrl, NULL); + fd_ctrl = -1; + } + if (monitor != NULL) { + epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_netlink, NULL); + udev_monitor_unref(monitor); + monitor = NULL; + } + if (fd_inotify >= 0) { + epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_inotify, NULL); + close(fd_inotify); + fd_inotify = -1; + } - /* set timeout to kill idle workers */ - if (udev_list_is_empty(&event_list) && children > 2) + /* discard queued events and kill workers */ + event_queue_cleanup(udev, EVENT_QUEUED); + worker_kill(udev, 0); + + /* exit after all has cleaned up */ + if (udev_list_is_empty(&event_list) && udev_list_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) { + /* set timeout to kill idle workers */ timeout = 3 * 1000; - else + } else { timeout = -1; - /* wait for events */ - fdcount = poll(pfd, ARRAY_SIZE(pfd), timeout); + } + fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), timeout); if (fdcount < 0) continue; - /* timeout - kill idle workers */ - if (fdcount == 0) + if (fdcount == 0) { + if (udev_exit) { + info(udev, "timeout, giving up waiting for workers to finish\n"); + break; + } + + /* timeout - kill idle workers */ worker_kill(udev, 2); + } + + is_worker = is_signal = is_inotify = is_netlink = is_ctrl = false; + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_worker && ev[i].events & EPOLLIN) + is_worker = true; + else if (ev[i].data.fd == fd_netlink && ev[i].events & EPOLLIN) + is_netlink = true; + else if (ev[i].data.fd == fd_signal && ev[i].events & EPOLLIN) + is_signal = true; + else if (ev[i].data.fd == fd_inotify && ev[i].events & EPOLLIN) + is_inotify = true; + else if (ev[i].data.fd == fd_ctrl && ev[i].events & EPOLLIN) + is_ctrl = true; + } /* event has finished */ - if (pfd[FD_WORKER].revents & POLLIN) - worker_returned(); + if (is_worker) + worker_returned(fd_worker); - /* get kernel uevent */ - if (pfd[FD_NETLINK].revents & POLLIN) { + if (is_netlink) { struct udev_device *dev; dev = udev_monitor_receive_device(monitor); @@ -1426,32 +1660,37 @@ int main(int argc, char *argv[]) } /* start new events */ - if (!udev_list_is_empty(&event_list) && !stop_exec_queue) - events_start(udev); + if (!udev_list_is_empty(&event_list) && !udev_exit && !stop_exec_queue) + event_queue_start(udev); - /* get signal */ - if (pfd[FD_SIGNAL].revents & POLLIN) { + if (is_signal) { struct signalfd_siginfo fdsi; ssize_t size; - size = read(pfd[FD_SIGNAL].fd, &fdsi, sizeof(struct signalfd_siginfo)); + size = read(fd_signal, &fdsi, sizeof(struct signalfd_siginfo)); if (size == sizeof(struct signalfd_siginfo)) handle_signal(udev, fdsi.ssi_signo); } + /* we are shutting down, the events below are not handled anymore */ + if (udev_exit) + continue; + /* device node and rules directory inotify watch */ - if (pfd[FD_INOTIFY].revents & POLLIN) + if (is_inotify) handle_inotify(udev); /* - * get control message - * * This needs to be after the inotify handling, to make sure, - * that the settle signal is send back after the possibly generated + * that the ping is send back after the possibly generated * "change" events by the inotify device node watch. + * + * A single time we may receive a client connection which we need to + * keep open to block the client. It will be closed right before we + * exit. */ - if (pfd[FD_CONTROL].revents & POLLIN) - handle_ctrl_msg(udev_ctrl); + if (is_ctrl) + ctrl_conn = handle_ctrl_msg(udev_ctrl); /* rules changed, set by inotify or a HUP signal */ if (reload_config) { @@ -1467,19 +1706,25 @@ int main(int argc, char *argv[]) } } - udev_queue_export_cleanup(udev_queue_export); - rc = 0; + rc = EXIT_SUCCESS; exit: - udev_queue_export_unref(udev_queue_export); + udev_queue_export_cleanup(udev_queue_export); +exit_keep_queue: + if (fd_ep >= 0) + close(fd_ep); + worker_list_cleanup(udev); + event_queue_cleanup(udev, EVENT_UNDEF); udev_rules_unref(rules); - udev_ctrl_unref(udev_ctrl); - if (pfd[FD_SIGNAL].fd >= 0) - close(pfd[FD_SIGNAL].fd); + if (fd_signal >= 0) + close(fd_signal); if (worker_watch[READ_END] >= 0) close(worker_watch[READ_END]); if (worker_watch[WRITE_END] >= 0) close(worker_watch[WRITE_END]); udev_monitor_unref(monitor); + udev_queue_export_unref(udev_queue_export); + udev_ctrl_connection_unref(ctrl_conn); + udev_ctrl_unref(udev_ctrl); udev_selinux_exit(udev); udev_unref(udev); udev_log_close(); |