chiark / gitweb /
udevd: convert to event worker processes
authorKay Sievers <kay.sievers@vrfy.org>
Wed, 3 Jun 2009 23:44:04 +0000 (01:44 +0200)
committerKay Sievers <kay.sievers@vrfy.org>
Wed, 3 Jun 2009 23:44:04 +0000 (01:44 +0200)
Event processes now get re-used after they handled an event. This reduces
pressure on the CPU significantly because cloned event processes no longer
cause page faults in the main daemon. After the events have settled, the
no longer needed worker processes get killed.

13 files changed:
NEWS
README
TODO
configure.ac
udev/Makefile.am
udev/lib/libudev-monitor.c
udev/lib/libudev-private.h
udev/udev-event.c
udev/udev-sysdeps.h [deleted file]
udev/udev-watch.c
udev/udev.h
udev/udevadm.xml
udev/udevd.c

diff --git a/NEWS b/NEWS
index ac44d7a77de3065eb8a61eeb9e5df33bdfe51e53..8b51c10a84bdbd208319d075ba412002df98eccb 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,9 +2,17 @@ udev 143
 ========
 Bugfixes.
 
+Event processes now get re-used after they handled an event. This reduces
+pressure on the CPU significantly because cloned event processes no longer
+cause page faults in the main daemon. After the events have settled, the
+no longer needed worker processes get killed.
+
+To be able to use signalfd(), udev depends on kernel version 2.6.25 now.
+Also inotify support is required now to run udev.
+
 The format of the queue exported by the udev damon has changed. There is
-no longer a /dev/.udev/queue/ directory. The queue can be accessed with
-udevadm settle and libudedv.
+no longer a /dev/.udev/queue/ directory. The current event queue can be
+accessed with udevadm settle and libudedv.
 
 udev 142
 ========
diff --git a/README b/README
index 773bc5508d3bdf513b29e622b9067789abf36b06..a14e5c0fcff04e40b81ab462d236aa12d99fab8e 100644 (file)
--- a/README
+++ b/README
@@ -9,11 +9,13 @@ Important Note:
   recommend to replace a distro's udev installation with the upstream version.
 
 Requirements:
-  - Version 2.6.22 of the Linux kernel for reliable operation of this release of
-    udev. The kernel must not use the CONFIG_SYSFS_DEPRECATED* option.
+  - Version 2.6.25 of the Linux kernel with sysfs, procfs, signalfd, inotify,
+    unix domain sockets, networking and hotplug enabled.
 
-  - The kernel must have sysfs, unix domain sockets and networking enabled.
-    Unix domain sockets (CONFIG_UNIX) as a loadable kernel module is not
+  - For reliable operation, the kernel must not use the CONFIG_SYSFS_DEPRECATED*
+    option.
+
+  - Unix domain sockets (CONFIG_UNIX) as a loadable kernel module is not
     supported.
 
   - The proc filesystem must be mounted on /proc/, the sysfs filesystem must
@@ -29,21 +31,18 @@ Operation:
   Udev creates and removes device nodes in /dev/, based on events the kernel
   sends out on device discovery or removal.
 
-  - Very early in the boot process, the /dev/ directory should get a 'tmpfs'
-    filesystem mounted, which is populated from scratch by udev. Created nodes
-    or changed permissions will not survive a reboot, which is intentional.
+  - Early in the boot process, the /dev/ directory should get a 'tmpfs'
+    filesystem mounted, which is maintained by udev. Created nodes or changed
+    permissions will not survive a reboot, which is intentional.
 
   - The content of /lib/udev/devices/ directory which contains the nodes,
     symlinks and directories, which are always expected to be in /dev, should
     be copied over to the tmpfs mounted /dev, to provide the required nodes
     to initialize udev and continue booting.
 
-  - The old hotplug helper /sbin/hotplug should be disabled on bootup, before
-    actions like loading kernel modules are taken, which may cause a lot of
-    events.
-
-  - The udevd daemon must be started on bootup to receive netlink uevents
-    from the kernel driver core.
+  - The old hotplug helper /sbin/hotplug should be disabled in the kernel
+    configuration, it is not needed, and may render the system unusable
+    because of a fork-bombing behavior.
 
   - All kernel events are matched against a set of specified rules in
     /lib/udev/rules.d/ which make it possible to hook into the event
diff --git a/TODO b/TODO
index 5b6af64e467435990e7f983306fe47520bf94ba9..bedccdb635506cd7ab3d99e221ffb7a027c577bc 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,3 +1,4 @@
+
   o add tests for kernel provided DEVNAME logic
   o drop modprobe floppy alias (SUSE), it will be in the module (2.6.30)
   o remove MMC rules, they got a modalias now (2.6.30)
index f1d008e000e5407d250d6655968b9546a7351dd9..9857d52e4efa3aa538ba0214c73500490b261506 100644 (file)
@@ -5,6 +5,7 @@ AC_PREREQ(2.60)
 AM_INIT_AUTOMAKE([check-news foreign 1.9 dist-bzip2])
 AC_DISABLE_STATIC
 AC_USE_SYSTEM_EXTENSIONS
+dnl AM_SILENT_RULES
 AC_SYS_LARGEFILE
 AC_CONFIG_MACRO_DIR([m4])
 AC_PROG_LIBTOOL
@@ -23,10 +24,6 @@ AC_SUBST(LIBUDEV_LT_AGE)
 
 AC_PATH_PROG([XSLTPROC], [xsltproc])
 
-AC_CHECK_LIB(c, inotify_init,
-       [AC_DEFINE([HAVE_INOTIFY], 1, [inotify available])],
-       [AC_MSG_WARN([inotify support disabled])])
-
 AC_ARG_WITH(udev-prefix,
        AS_HELP_STRING([--with-udev-prefix=DIR], [add prefix to internal udev path names]),
        [], [with_udev_prefix='${exec_prefix}'])
index 6cd2f23dc3b88d3b079343dd0cd2b293a6308e26..94989e64bf57eac7ee7edb45a471641aa894c4ee 100644 (file)
@@ -14,7 +14,6 @@ common_ldadd =
 
 common_files = \
        udev.h \
-       udev-sysdeps.h \
        udev-event.c \
        udev-watch.c \
        udev-node.c \
index 395a4d27e1ee9f7904a42f8c549685e50d06bae6..33a0605492a128f174b080b2b84b6206907fc81c 100644 (file)
@@ -32,15 +32,17 @@ struct udev_monitor {
        int refcount;
        int sock;
        struct sockaddr_nl snl;
-       struct sockaddr_nl snl_peer;
+       struct sockaddr_nl snl_trusted_sender;
+       struct sockaddr_nl snl_destination;
        struct sockaddr_un sun;
        socklen_t addrlen;
        struct udev_list_node filter_subsystem_list;
 };
 
 enum udev_monitor_netlink_group {
-       UDEV_MONITOR_KERNEL     = 1,
-       UDEV_MONITOR_UDEV       = 2,
+       UDEV_MONITOR_NONE,
+       UDEV_MONITOR_KERNEL,
+       UDEV_MONITOR_UDEV,
 };
 
 #define UDEV_MONITOR_MAGIC             0xcafe1dea
@@ -171,11 +173,11 @@ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char
                return NULL;
 
        if (name == NULL)
-               return NULL;
-       if (strcmp(name, "kernel") == 0)
-               group = UDEV_MONITOR_KERNEL;
+               group = UDEV_MONITOR_NONE;
        else if (strcmp(name, "udev") == 0)
                group = UDEV_MONITOR_UDEV;
+       else if (strcmp(name, "kernel") == 0)
+               group = UDEV_MONITOR_KERNEL;
        else
                return NULL;
 
@@ -193,8 +195,10 @@ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char
 
        udev_monitor->snl.nl_family = AF_NETLINK;
        udev_monitor->snl.nl_groups = group;
-       udev_monitor->snl_peer.nl_family = AF_NETLINK;
-       udev_monitor->snl_peer.nl_groups = UDEV_MONITOR_UDEV;
+
+       /* default destination for sending */
+       udev_monitor->snl_destination.nl_family = AF_NETLINK;
+       udev_monitor->snl_destination.nl_groups = UDEV_MONITOR_UDEV;
 
        dbg(udev, "monitor %p created with NETLINK_KOBJECT_UEVENT (%u)\n", udev_monitor, group);
        return udev_monitor;
@@ -281,6 +285,12 @@ int udev_monitor_filter_update(struct udev_monitor *udev_monitor)
        return err;
 }
 
+int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender)
+{
+       udev_monitor->snl_trusted_sender.nl_pid = sender->snl.nl_pid;
+       return 0;
+}
+
 int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
 {
        int err;
@@ -293,6 +303,19 @@ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor)
                udev_monitor_filter_update(udev_monitor);
                err = bind(udev_monitor->sock,
                           (struct sockaddr *)&udev_monitor->snl, sizeof(struct sockaddr_nl));
+               if (err == 0) {
+                       struct sockaddr_nl snl;
+                       socklen_t addrlen;
+
+                       /*
+                        * get the address the kernel has assigned us
+                        * it is usually, but not neccessarily the pid
+                        */
+                       addrlen = sizeof(struct sockaddr_nl);
+                       err = getsockname(udev_monitor->sock, (struct sockaddr *)&snl, &addrlen);
+                       if (err == 0)
+                               udev_monitor->snl.nl_pid = snl.nl_pid;
+               }
        } else {
                return -EINVAL;
        }
@@ -314,6 +337,15 @@ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int
        return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size));
 }
 
+int udev_monitor_disconnect(struct udev_monitor *udev_monitor)
+{
+       int err;
+
+       err = close(udev_monitor->sock);
+       udev_monitor->sock = -1;
+       return err;
+}
+
 /**
  * udev_monitor_ref:
  * @udev_monitor: udev monitor
@@ -478,10 +510,13 @@ retry:
 
        if (udev_monitor->snl.nl_family != 0) {
                if (snl.nl_groups == 0) {
-                       info(udev_monitor->udev, "unicast netlink message ignored\n");
-                       return NULL;
-               }
-               if (snl.nl_groups == UDEV_MONITOR_KERNEL) {
+                       /* unicast message, check if we trust the sender */
+                       if (udev_monitor->snl_trusted_sender.nl_pid == 0 ||
+                           snl.nl_pid != udev_monitor->snl_trusted_sender.nl_pid) {
+                               info(udev_monitor->udev, "unicast netlink message ignored\n");
+                               return NULL;
+                       }
+               } else if (snl.nl_groups == UDEV_MONITOR_KERNEL) {
                        if (snl.nl_pid > 0) {
                                info(udev_monitor->udev, "multicast kernel netlink message from pid %d ignored\n", snl.nl_pid);
                                return NULL;
@@ -621,7 +656,8 @@ retry:
        return udev_device;
 }
 
-int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_device *udev_device)
+int udev_monitor_send_device(struct udev_monitor *udev_monitor,
+                            struct udev_monitor *destination, struct udev_device *udev_device)
 {
        struct msghdr smsg;
        struct iovec iov[2];
@@ -683,8 +719,16 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_devi
                memset(&smsg, 0x00, sizeof(struct msghdr));
                smsg.msg_iov = iov;
                smsg.msg_iovlen = 2;
-               /* no destination besides the muticast group, we will always get ECONNREFUSED */
-               smsg.msg_name = &udev_monitor->snl_peer;
+               /*
+                * Use custom address for target, or the default one.
+                *
+                * If we send to a muticast group, we will get
+                * ECONNREFUSED, which is expected.
+                */
+               if (destination != NULL)
+                       smsg.msg_name = &destination->snl;
+               else
+                       smsg.msg_name = &udev_monitor->snl_destination;
                smsg.msg_namelen = sizeof(struct sockaddr_nl);
        } else {
                return -1;
index dc02a842b883154b10fbe75a5945dcb648fc697a..5512341e9e5e9ec399c08c514a5b68d3e73ad742 100644 (file)
@@ -86,7 +86,10 @@ int udev_device_delete_db(struct udev_device *udev_device);
 int udev_device_rename_db(struct udev_device *udev_device, const char *devpath);
 
 /* libudev-monitor - netlink/unix socket communication  */
-int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_device *udev_device);
+int udev_monitor_disconnect(struct udev_monitor *udev_monitor);
+int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender);
+int udev_monitor_send_device(struct udev_monitor *udev_monitor,
+                            struct udev_monitor *destination, struct udev_device *udev_device);
 int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size);
 
 /* libudev-ctrl - daemon runtime setup */
index d52125193ce0f4a956f6bc5244c526a1d4bf774e..3f69c0bb7a86c35ed8b3d1eb737e2d4a37d355fd 100644 (file)
@@ -734,18 +734,13 @@ int udev_event_execute_run(struct udev_event *event)
                        monitor = udev_monitor_new_from_socket(event->udev, &cmd[strlen("socket:")]);
                        if (monitor == NULL)
                                continue;
-                       udev_monitor_send_device(monitor, event->dev);
+                       udev_monitor_send_device(monitor, NULL, event->dev);
                        udev_monitor_unref(monitor);
                } else {
                        char program[UTIL_PATH_SIZE];
                        char **envp;
 
                        udev_event_apply_format(event, cmd, program, sizeof(program));
-                       if (event->trace)
-                               fprintf(stderr, "run  %s (%llu) '%s'\n",
-                                      udev_device_get_syspath(event->dev),
-                                      udev_device_get_seqnum(event->dev),
-                                      program);
                        envp = udev_device_get_properties_envp(event->dev);
                        if (util_run_program(event->udev, program, envp, NULL, 0, NULL) != 0) {
                                if (!udev_list_entry_get_flag(list_entry))
diff --git a/udev/udev-sysdeps.h b/udev/udev-sysdeps.h
deleted file mode 100644 (file)
index 35671ba..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * wrapping of libc features and kernel interfaces
- *
- * Copyright (C) 2005-2008 Kay Sievers <kay.sievers@vrfy.org>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef _UDEV_SYSDEPS_H_
-#define _UDEV_SYSDEPS_H_
-
-#include <stdint.h>
-#include <errno.h>
-
-#ifndef HAVE_INOTIFY
-static inline int inotify_init(void)
-{
-       errno = ENOSYS;
-       return -1;
-}
-
-static inline int inotify_add_watch(int fd, const char *name, uint32_t mask)
-{
-       return -1;
-}
-
-#define IN_CREATE      0
-#define IN_DELETE      0
-#define IN_MOVE                0
-#define IN_CLOSE_WRITE 0
-
-#endif /* HAVE_INOTIFY */
-#endif
index 53492e5f357fd2fcb73128af2a95721fd55bd99b..5a49c963da82a0651aa50c7612580419abc063c6 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#ifdef HAVE_INOTIFY
 #include <sys/inotify.h>
-#endif
 
 #include "udev.h"
 
-int inotify_fd = -1;
+static int inotify_fd = -1;
 
 /* inotify descriptor, will be shared with rules directory;
  * set to cloexec since we need our children to be able to add
  * watches for us
  */
-void udev_watch_init(struct udev *udev)
+int udev_watch_init(struct udev *udev)
 {
        inotify_fd = inotify_init();
        if (inotify_fd >= 0)
                util_set_fd_cloexec(inotify_fd);
-       else if (errno == ENOSYS)
-               info(udev, "unable to use inotify, udevd will not monitor rule files changes\n");
        else
                err(udev, "inotify_init failed: %m\n");
+       return inotify_fd;
 }
 
 /* move any old watches directory out of the way, and then restore
index 8f2c1c63dda66965ed11cc2cf218fb1677802134..718797539030dae45f0505241c1c6fbe5ea2e3f7 100644 (file)
@@ -22,7 +22,6 @@
 #include <sys/types.h>
 #include <sys/param.h>
 
-#include "udev-sysdeps.h"
 #include "lib/libudev.h"
 #include "lib/libudev-private.h"
 
@@ -53,7 +52,6 @@ static inline void logging_close(void)
 }
 
 struct udev_event {
-       struct udev_list_node node;
        struct udev *udev;
        struct udev_device *dev;
        struct udev_device *dev_parent;
@@ -64,10 +62,6 @@ struct udev_event {
        uid_t uid;
        gid_t gid;
        struct udev_list_node run_list;
-       pid_t pid;
-       int exitstatus;
-       time_t queue_time;
-       unsigned long long int delaying_seqnum;
        unsigned int group_final:1;
        unsigned int owner_final:1;
        unsigned int mode_final:1;
@@ -76,7 +70,6 @@ struct udev_event {
        unsigned int run_final:1;
        unsigned int ignore_device:1;
        unsigned int inotify_watch:1;
-       unsigned int trace:1;
 };
 
 struct udev_watch {
@@ -101,8 +94,7 @@ int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string,
                                   char *result, size_t maxsize, int read_value);
 
 /* udev-watch.c */
-extern int inotify_fd;
-void udev_watch_init(struct udev *udev);
+int udev_watch_init(struct udev *udev);
 void udev_watch_restore(struct udev *udev);
 void udev_watch_begin(struct udev *udev, struct udev_device *dev);
 void udev_watch_end(struct udev *udev, struct udev_device *dev);
index 538180babeae16b28e9ed9b3f35ac5c70f20a2f0..2e03d9886eb85d9e4ab1faaed56d32123e2923be 100644 (file)
               <term><option>--reload-rules</option></term>
               <listitem>
                 <para>Signal udevd to reload the rules files.
-                Usually the udev daemon detects changes automatically, this may
-                only be needed on systems without inotify support. Reloading rules
-                does not apply any changes to already existing devices.</para>
+                The udev daemon detects changes automatically, this option is
+                usually not needed. Reloading rules does not apply any changes
+                to already existing devices.</para>
               </listitem>
             </varlistentry>
             <varlistentry>
index 37b547ad656a5f57bd130e7be102995d8f20082e..c0852022e246dae89f7bcc21aa728ffd1f1cb543 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2004-2009 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>
 #include <time.h>
 #include <getopt.h>
 #include <dirent.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/signalfd.h>
 #include <sys/select.h>
 #include <sys/poll.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/ioctl.h>
-#ifdef HAVE_INOTIFY
 #include <sys/inotify.h>
-#endif
 
 #include "udev.h"
 
 #define UDEVD_PRIORITY                 -4
 #define UDEV_PRIORITY                  -2
 
-/* maximum limit of forked childs */
-#define UDEVD_MAX_CHILDS               256
-
 static int debug;
 
 static void log_fn(struct udev *udev, int priority,
@@ -61,84 +59,159 @@ static void log_fn(struct udev *udev, int priority,
        }
 }
 
-static void reap_sigchilds(void);
-
 static int debug_trace;
 static struct udev_rules *rules;
 static struct udev_queue_export *udev_queue_export;
 static struct udev_ctrl *udev_ctrl;
-static struct udev_monitor *kernel_monitor;
-static volatile sig_atomic_t sigchilds_waiting;
-static volatile sig_atomic_t udev_exit;
-static volatile sig_atomic_t reload_config;
-static volatile sig_atomic_t signal_received;
-static volatile pid_t settle_pid;
-static int run_exec_q;
-static int stop_exec_q;
+static struct udev_monitor *monitor;
+static int worker_watch[2];
+static pid_t settle_pid;
+static int stop_exec_queue;
+static int reload_config;
 static int max_childs;
 static int childs;
 static struct udev_list_node event_list;
-
-static struct udev_event *node_to_event(struct udev_list_node *node)
+static struct udev_list_node worker_list;
+static int udev_exit;
+static volatile sig_atomic_t worker_exit;
+
+enum poll_fd {
+       FD_CONTROL,
+       FD_NETLINK,
+       FD_INOTIFY,
+       FD_SIGNAL,
+       FD_WORKER,
+};
+
+static struct pollfd pfd[] = {
+       [FD_NETLINK] = { .events = POLLIN },
+       [FD_WORKER] =  { .events = POLLIN },
+       [FD_SIGNAL] =  { .events = POLLIN },
+       [FD_INOTIFY] = { .events = POLLIN },
+       [FD_CONTROL] = { .events = POLLIN },
+};
+
+enum event_state {
+       EVENT_UNDEF,
+       EVENT_QUEUED,
+       EVENT_RUNNING,
+};
+
+struct event {
+       struct udev_list_node node;
+       struct udev *udev;
+       struct udev_device *dev;
+       enum event_state state;
+       int exitcode;
+       unsigned long long int delaying_seqnum;
+       unsigned long long int seqnum;
+       const char *devpath;
+       size_t devpath_len;
+       const char *devpath_old;
+};
+
+static struct event *node_to_event(struct udev_list_node *node)
 {
        char *event;
 
        event = (char *)node;
-       event -= offsetof(struct udev_event, node);
-       return (struct udev_event *)event;
+       event -= offsetof(struct event, node);
+       return (struct event *)event;
+}
+
+enum worker_state {
+       WORKER_UNDEF,
+       WORKER_RUNNING,
+       WORKER_IDLE,
+       WORKER_KILLED,
+};
+
+struct worker {
+       struct udev_list_node node;
+       pid_t pid;
+       struct udev_monitor *monitor;
+       enum worker_state state;
+       struct event *event;
+};
+
+/* passed from worker to main process */
+struct worker_message {
+       pid_t pid;
+       int exitcode;
+};
+
+static struct worker *node_to_worker(struct udev_list_node *node)
+{
+       char *worker;
+
+       worker = (char *)node;
+       worker -= offsetof(struct worker, node);
+       return (struct worker *)worker;
 }
 
-static void event_queue_delete(struct udev_event *event)
+static void event_queue_delete(struct event *event)
 {
        udev_list_node_remove(&event->node);
 
        /* mark as failed, if "add" event returns non-zero */
-       if (event->exitstatus && strcmp(udev_device_get_action(event->dev), "add") == 0)
+       if (event->exitcode && strcmp(udev_device_get_action(event->dev), "add") == 0)
                udev_queue_export_device_failed(udev_queue_export, event->dev);
        else
                udev_queue_export_device_finished(udev_queue_export, event->dev);
 
        udev_device_unref(event->dev);
-       udev_event_unref(event);
+       free(event);
 }
 
 static void event_sig_handler(int signum)
 {
-       if (signum == SIGALRM)
+       switch (signum) {
+       case SIGALRM:
                _exit(1);
+               break;
+       case SIGTERM:
+               worker_exit = 1;
+               break;
+       }
+}
+
+static void worker_unref(struct worker *worker)
+{
+       udev_monitor_unref(worker->monitor);
+       free(worker);
 }
 
-static void event_fork(struct udev_event *event)
+static void worker_new(struct event *event)
 {
+       struct worker *worker;
+       struct udev_monitor *worker_monitor;
        pid_t pid;
        struct sigaction act;
-       int err;
-
-#if 0
-       /* single process, no forking, just for testing/profiling */
-       err = udev_event_execute_rules(event, rules);
-       if (err == 0 && !event->ignore_device && udev_get_run(event->udev))
-               udev_event_execute_run(event);
-       info(event->udev, "seq %llu exit with %i\n", udev_device_get_seqnum(event->dev), err);
-       event_queue_delete(event);
-       return;
-#endif
 
-       if (debug_trace) {
-               event->trace = 1;
-               fprintf(stderr, "fork %s (%llu)\n",
-                      udev_device_get_syspath(event->dev),
-                      udev_device_get_seqnum(event->dev));
-       }
+       /* listen for new events */
+       worker_monitor = udev_monitor_new_from_netlink(event->udev, NULL);
+       if (worker_monitor == NULL)
+               return;
+       /* allow the main daemon netlink address to send devices to the worker */
+       udev_monitor_allow_unicast_sender(worker_monitor, monitor);
+       udev_monitor_enable_receiving(worker_monitor);
+
+       worker = calloc(1, sizeof(struct worker));
+       if (worker == NULL)
+               return;
 
        pid = fork();
        switch (pid) {
-       case 0:
-               /* child */
+       case 0: {
+               sigset_t mask;
+               struct udev_device *dev;
+
                udev_queue_export_unref(udev_queue_export);
                udev_ctrl_unref(udev_ctrl);
+               close(pfd[FD_SIGNAL].fd);
+               close(worker_watch[READ_END]);
                logging_close();
-               logging_init("udevd-event");
+               logging_init("udevd-work");
                setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY);
 
                /* set signal handlers */
@@ -146,78 +219,182 @@ static void event_fork(struct udev_event *event)
                act.sa_handler = event_sig_handler;
                sigemptyset (&act.sa_mask);
                act.sa_flags = 0;
+               sigaction(SIGTERM, &act, NULL);
                sigaction(SIGALRM, &act, NULL);
 
-               /* reset to default */
-               act.sa_handler = SIG_DFL;
-               sigaction(SIGINT, &act, NULL);
-               sigaction(SIGTERM, &act, NULL);
-               sigaction(SIGCHLD, &act, NULL);
-               sigaction(SIGHUP, &act, NULL);
+               /* unblock signals */
+               sigfillset(&mask);
+               sigdelset(&mask, SIGTERM);
+               sigdelset(&mask, SIGALRM);
+               sigprocmask(SIG_SETMASK, &mask, NULL);
 
-               /* set timeout to prevent hanging processes */
-               alarm(UDEV_EVENT_TIMEOUT);
+               /* request TERM signal if parent exits */
+               prctl(PR_SET_PDEATHSIG, SIGTERM);
 
-               /* apply rules, create node, symlinks */
-               err = udev_event_execute_rules(event, rules);
+               /* initial device */
+               dev = event->dev;
 
-               /* rules may change/disable the timeout */
-               if (udev_device_get_event_timeout(event->dev) >= 0)
-                       alarm(udev_device_get_event_timeout(event->dev));
+               while (!worker_exit) {
+                       struct udev_event *udev_event;
+                       struct worker_message msg;
+                       int err;
 
-               /* execute RUN= */
-               if (err == 0 && !event->ignore_device && udev_get_run(event->udev))
-                       udev_event_execute_run(event);
+                       udev_event = udev_event_new(dev);
+                       if (udev_event == NULL)
+                               _exit(3);
 
-               /* apply/restore inotify watch */
-               if (err == 0 && event->inotify_watch) {
-                       udev_watch_begin(event->udev, event->dev);
-                       udev_device_update_db(event->dev);
-               }
+                       /* set timeout to prevent hanging processes */
+                       alarm(UDEV_EVENT_TIMEOUT);
+
+                       /* apply rules, create node, symlinks */
+                       err = udev_event_execute_rules(udev_event, rules);
+
+                       /* rules may change/disable the timeout */
+                       if (udev_device_get_event_timeout(dev) >= 0)
+                               alarm(udev_device_get_event_timeout(dev));
+
+                       /* execute RUN= */
+                       if (err == 0 && !udev_event->ignore_device && udev_get_run(udev_event->udev))
+                               udev_event_execute_run(udev_event);
+
+                       /* reset alarm */
+                       alarm(0);
+
+                       /* apply/restore inotify watch */
+                       if (err == 0 && udev_event->inotify_watch) {
+                               udev_watch_begin(udev_event->udev, dev);
+                               udev_device_update_db(dev);
+                       }
 
-               /* send processed event back to the kernel netlink socket */
-               udev_monitor_send_device(kernel_monitor, event->dev);
+                       /* send processed event back to libudev listeners */
+                       udev_monitor_send_device(worker_monitor, NULL, dev);
 
-               info(event->udev, "seq %llu exit with %i\n", udev_device_get_seqnum(event->dev), err);
+                       info(event->udev, "seq %llu finished with %i\n", udev_device_get_seqnum(dev), err);
+                       udev_device_unref(dev);
+                       udev_event_unref(udev_event);
+
+                       /* send back the result of the event execution */
+                       msg.exitcode = err;
+                       msg.pid = getpid();
+                       send(worker_watch[WRITE_END], &msg, sizeof(struct worker_message), 0);
+
+                       /* wait for more device messages from udevd */
+                       do
+                               dev = udev_monitor_receive_device(worker_monitor);
+                       while (!worker_exit && dev == NULL);
+               }
+
+               udev_monitor_unref(worker_monitor);
                logging_close();
-               if (err != 0)
-                       exit(1);
                exit(0);
+       }
        case -1:
+               udev_monitor_unref(worker_monitor);
+               event->state = EVENT_QUEUED;
+               free(worker);
                err(event->udev, "fork of child failed: %m\n");
-               event_queue_delete(event);
                break;
        default:
-               /* get SIGCHLD in main loop */
-               info(event->udev, "seq %llu forked, pid [%d], '%s' '%s', %ld seconds old\n",
-                    udev_device_get_seqnum(event->dev),
-                    pid,
-                    udev_device_get_action(event->dev),
-                    udev_device_get_subsystem(event->dev),
-                    time(NULL) - event->queue_time);
-               event->pid = pid;
+               /* close monitor, but keep address around */
+               udev_monitor_disconnect(worker_monitor);
+               worker->monitor = worker_monitor;
+               worker->pid = pid;
+               worker->state = WORKER_RUNNING;
+               worker->event = event;
+               event->state = EVENT_RUNNING;
+               udev_list_node_append(&worker->node, &worker_list);
                childs++;
+               break;
        }
 }
 
-static void event_queue_insert(struct udev_event *event)
+static void event_run(struct event *event)
 {
-       event->queue_time = time(NULL);
+       struct udev_list_node *loop;
+
+       udev_list_node_foreach(loop, &worker_list) {
+               struct worker *worker = node_to_worker(loop);
+               ssize_t count;
 
-       udev_queue_export_device_queued(udev_queue_export, event->dev);
-       info(event->udev, "seq %llu queued, '%s' '%s'\n", udev_device_get_seqnum(event->dev),
-            udev_device_get_action(event->dev), udev_device_get_subsystem(event->dev));
+               if (worker->state != WORKER_IDLE)
+                       continue;
 
+               worker->event = event;
+               worker->state = WORKER_RUNNING;
+               event->state = EVENT_RUNNING;
+               count = udev_monitor_send_device(monitor, worker->monitor, event->dev);
+               if (count < 0) {
+                       err(event->udev, "worker [%u] did not accept message, kill it\n", worker->pid);
+                       event->state = EVENT_QUEUED;
+                       worker->state = WORKER_KILLED;
+                       kill(worker->pid, SIGKILL);
+                       continue;
+               }
+               return;
+       }
+
+       if (childs >= max_childs) {
+               info(event->udev, "maximum number (%i) of childs reached\n", childs);
+               return;
+       }
+
+       /* start new worker and pass initial device */
+       worker_new(event);
+}
+
+static void event_queue_insert(struct udev_device *dev)
+{
+       struct event *event;
+
+       event = calloc(1, sizeof(struct event));
+       if (event == NULL)
+               return;
+
+       event->udev = udev_device_get_udev(dev);
+       event->dev = dev;
+       event->seqnum = udev_device_get_seqnum(dev);
+       event->devpath = udev_device_get_devpath(dev);
+       event->devpath_len = strlen(event->devpath);
+       event->devpath_old = udev_device_get_devpath_old(dev);
+
+       udev_queue_export_device_queued(udev_queue_export, dev);
+       info(event->udev, "seq %llu queued, '%s' '%s'\n", udev_device_get_seqnum(dev),
+            udev_device_get_action(dev), udev_device_get_subsystem(dev));
+
+       event->state = EVENT_QUEUED;
        udev_list_node_append(&event->node, &event_list);
-       run_exec_q = 1;
 
        /* run all events with a timeout set immediately */
-       if (udev_device_get_timeout(event->dev) > 0) {
-               event_fork(event);
+       if (udev_device_get_timeout(dev) > 0) {
+               worker_new(event);
                return;
        }
 }
 
+static void worker_kill(int retain)
+{
+       struct udev_list_node *loop;
+       int max;
+
+       if (childs <= retain)
+               return;
+
+       max = childs - retain;
+
+       udev_list_node_foreach(loop, &worker_list) {
+               struct worker *worker = node_to_worker(loop);
+
+               if (max-- <= 0)
+                       break;
+
+               if (worker->state == WORKER_KILLED)
+                       continue;
+
+               worker->state = WORKER_KILLED;
+               kill(worker->pid, SIGTERM);
+       }
+}
+
 static int mem_size_mb(void)
 {
        FILE *f;
@@ -241,112 +418,111 @@ static int mem_size_mb(void)
        return memsize;
 }
 
-static int compare_devpath(const char *running, const char *waiting)
-{
-       int i = 0;
-
-       while (running[i] != '\0' && running[i] == waiting[i])
-               i++;
-
-       /* identical device event found */
-       if (running[i] == '\0' && waiting[i] == '\0')
-               return 1;
-
-       /* parent device event found */
-       if (running[i] == '\0' && waiting[i] == '/')
-               return 2;
-
-       /* child device event found */
-       if (running[i] == '/' && waiting[i] == '\0')
-               return 3;
-
-       /* no matching event */
-       return 0;
-}
-
 /* lookup event for identical, parent, child device */
-static int devpath_busy(struct udev_event *event)
+static int devpath_busy(struct event *event)
 {
        struct udev_list_node *loop;
+       size_t common;
 
        /* check if queue contains events we depend on */
        udev_list_node_foreach(loop, &event_list) {
-               struct udev_event *loop_event = node_to_event(loop);
+               struct event *loop_event = node_to_event(loop);
 
                /* we already found a later event, earlier can not block us, no need to check again */
-               if (udev_device_get_seqnum(loop_event->dev) < event->delaying_seqnum)
+               if (loop_event->seqnum < event->delaying_seqnum)
                        continue;
 
                /* event we checked earlier still exists, no need to check again */
-               if (udev_device_get_seqnum(loop_event->dev) == event->delaying_seqnum)
+               if (loop_event->seqnum == event->delaying_seqnum)
                        return 2;
 
                /* found ourself, no later event can block us */
-               if (udev_device_get_seqnum(loop_event->dev) >= udev_device_get_seqnum(event->dev))
+               if (loop_event->seqnum >= event->seqnum)
                        break;
 
                /* check our old name */
-               if (udev_device_get_devpath_old(event->dev) != NULL)
-                       if (strcmp(udev_device_get_devpath(loop_event->dev), udev_device_get_devpath_old(event->dev)) == 0) {
-                               event->delaying_seqnum = udev_device_get_seqnum(loop_event->dev);
+               if (event->devpath_old != NULL)
+                       if (strcmp(loop_event->devpath, event->devpath_old) == 0) {
+                               event->delaying_seqnum = loop_event->seqnum;
                                return 3;
                        }
 
-               /* check identical, parent, or child device event */
-               if (compare_devpath(udev_device_get_devpath(loop_event->dev), udev_device_get_devpath(event->dev)) != 0) {
-                       dbg(event->udev, "%llu, device event still pending %llu (%s)\n",
-                           udev_device_get_seqnum(event->dev),
-                           udev_device_get_seqnum(loop_event->dev),
-                           udev_device_get_devpath(loop_event->dev));
-                       event->delaying_seqnum = udev_device_get_seqnum(loop_event->dev);
+               /* compare devpath */
+               common = MIN(loop_event->devpath_len, event->devpath_len);
+
+               /* one devpath is contained in the other? */
+               if (memcmp(loop_event->devpath, event->devpath, common) != 0)
+                       continue;
+
+               /* identical device event found */
+               if (loop_event->devpath_len == event->devpath_len) {
+                       event->delaying_seqnum = loop_event->seqnum;
                        return 4;
                }
+
+               /* parent device event found */
+               if (event->devpath[common] == '/') {
+                       event->delaying_seqnum = loop_event->seqnum;
+                       return 5;
+               }
+
+               /* child device event found */
+               if (loop_event->devpath[common] == '/') {
+                       event->delaying_seqnum = loop_event->seqnum;
+                       return 6;
+               }
+
+               /* no matching device */
+               continue;
        }
+
        return 0;
 }
 
-/* serializes events for the identical and parent and child devices */
-static void event_queue_manager(struct udev *udev)
+static void events_start(struct udev *udev)
 {
        struct udev_list_node *loop;
-       struct udev_list_node *tmp;
-
-start_over:
-       if (udev_list_is_empty(&event_list)) {
-               if (childs > 0) {
-                       err(udev, "event list empty, but childs count is %i", childs);
-                       childs = 0;
-               }
-               return;
-       }
 
-       udev_list_node_foreach_safe(loop, tmp, &event_list) {
-               struct udev_event *loop_event = node_to_event(loop);
-
-               if (childs >= max_childs) {
-                       info(udev, "maximum number (%i) of childs reached\n", childs);
-                       break;
-               }
+       udev_list_node_foreach(loop, &event_list) {
+               struct event *event = node_to_event(loop);
 
-               if (loop_event->pid != 0)
+               if (event->state != EVENT_QUEUED)
                        continue;
 
                /* do not start event if parent or child event is still running */
-               if (devpath_busy(loop_event) != 0) {
-                       dbg(udev, "delay seq %llu (%s)\n",
-                           udev_device_get_seqnum(loop_event->dev),
-                           udev_device_get_devpath(loop_event->dev));
+               if (devpath_busy(event) != 0) {
+                       dbg(udev, "delay seq %llu (%s)\n", event->seqnum, event->devpath);
                        continue;
                }
 
-               event_fork(loop_event);
-               dbg(udev, "moved seq %llu to running list\n", udev_device_get_seqnum(loop_event->dev));
+               event_run(event);
+       }
+}
+
+static void worker_returned(void)
+{
+       while (1) {
+               struct worker_message msg;
+               ssize_t size;
+               struct udev_list_node *loop;
+
+               size = recv(pfd[FD_WORKER].fd, &msg, sizeof(struct worker_message), MSG_DONTWAIT);
+               if (size != sizeof(struct worker_message))
+                       break;
+
+               /* lookup worker who sent the signal */
+               udev_list_node_foreach(loop, &worker_list) {
+                       struct worker *worker = node_to_worker(loop);
 
-               /* retry if events finished in the meantime */
-               if (sigchilds_waiting) {
-                       sigchilds_waiting = 0;
-                       reap_sigchilds();
-                       goto start_over;
+                       if (worker->pid != msg.pid)
+                               continue;
+
+                       /* worker returned */
+                       worker->event->exitcode = msg.exitcode;
+                       event_queue_delete(worker->event);
+                       worker->event = NULL;
+                       worker->state = WORKER_IDLE;
+                       break;
                }
        }
 }
@@ -367,17 +543,17 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl)
        if (i >= 0) {
                info(udev, "udevd message (SET_LOG_PRIORITY) received, log_priority=%i\n", i);
                udev_set_log_priority(udev, i);
+               worker_kill(0);
        }
 
        if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) {
                info(udev, "udevd message (STOP_EXEC_QUEUE) received\n");
-               stop_exec_q = 1;
+               stop_exec_queue = 1;
        }
 
        if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) {
                info(udev, "udevd message (START_EXEC_QUEUE) received\n");
-               stop_exec_q = 0;
-               event_queue_manager(udev);
+               stop_exec_queue = 0;
        }
 
        if (udev_ctrl_get_reload_rules(ctrl_msg) > 0) {
@@ -409,6 +585,7 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl)
                        }
                        free(key);
                }
+               worker_kill(0);
        }
 
        i = udev_ctrl_get_set_max_childs(ctrl_msg);
@@ -420,6 +597,8 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl)
        settle_pid = udev_ctrl_get_settle(ctrl_msg);
        if (settle_pid > 0) {
                info(udev, "udevd message (SETTLE) received\n");
+               kill(settle_pid, SIGUSR1);
+               settle_pid = 0;
        }
        udev_ctrl_msg_unref(ctrl_msg);
 }
@@ -427,22 +606,20 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl)
 /* read inotify messages */
 static int handle_inotify(struct udev *udev)
 {
-       int nbytes, pos;
+       ssize_t nbytes, pos;
        char *buf;
        struct inotify_event *ev;
 
-       if ((ioctl(inotify_fd, FIONREAD, &nbytes) < 0) || (nbytes <= 0))
+       if ((ioctl(pfd[FD_INOTIFY].fd, FIONREAD, &nbytes) < 0) || (nbytes <= 0))
                return 0;
 
        buf = malloc(nbytes);
        if (buf == NULL) {
                err(udev, "error getting buffer for inotify, disable watching\n");
-               close(inotify_fd);
-               inotify_fd = -1;
-               return 0;
+               return -1;
        }
 
-       read(inotify_fd, buf, nbytes);
+       nbytes = read(pfd[FD_INOTIFY].fd, buf, nbytes);
 
        for (pos = 0; pos < nbytes; pos += sizeof(struct inotify_event) + ev->len) {
                struct udev_device *dev;
@@ -476,72 +653,48 @@ static int handle_inotify(struct udev *udev)
 
        }
 
-       free (buf);
+       free(buf);
        return 0;
 }
 
-static void sig_handler(int signum)
+static void handle_signal(int signo)
 {
-       switch (signum) {
-               case SIGINT:
-               case SIGTERM:
-                       udev_exit = 1;
-                       break;
-               case SIGCHLD:
-                       /* set flag, then write to pipe if needed */
-                       sigchilds_waiting = 1;
-                       break;
-               case SIGHUP:
-                       reload_config = 1;
-                       break;
-       }
+       switch (signo) {
+       case SIGINT:
+       case SIGTERM:
+               udev_exit = 1;
+               break;
+       case SIGCHLD:
+               while (1) {
+                       pid_t pid;
+                       struct udev_list_node *loop, *tmp;
 
-       signal_received = 1;
-}
+                       pid = waitpid(-1, NULL, WNOHANG);
+                       if (pid <= 0)
+                               break;
 
-static void udev_done(int pid, int exitstatus)
-{
-       struct udev_list_node *loop;
+                       udev_list_node_foreach_safe(loop, tmp, &worker_list) {
+                               struct worker *worker = node_to_worker(loop);
 
-       /* find event associated with pid and delete it */
-       udev_list_node_foreach(loop, &event_list) {
-               struct udev_event *loop_event = node_to_event(loop);
-
-               if (loop_event->pid == pid) {
-                       info(loop_event->udev, "seq %llu cleanup, pid [%d], status %i, %ld seconds old\n",
-                            udev_device_get_seqnum(loop_event->dev), loop_event->pid,
-                            exitstatus, time(NULL) - loop_event->queue_time);
-                       loop_event->exitstatus = exitstatus;
-                       if (debug_trace)
-                               fprintf(stderr, "exit %s (%llu)\n",
-                                      udev_device_get_syspath(loop_event->dev),
-                                      udev_device_get_seqnum(loop_event->dev));
-                       event_queue_delete(loop_event);
-                       childs--;
-
-                       /* there may be dependent events waiting */
-                       run_exec_q = 1;
-                       return;
-               }
-       }
-}
+                               if (worker->pid != pid)
+                                       continue;
 
-static void reap_sigchilds(void)
-{
-       pid_t pid;
-       int status;
+                               /* fail event, if worker died unexpectedly */
+                               if (worker->event != NULL) {
+                                       worker->event->exitcode = 127;
+                                       event_queue_delete(worker->event);
+                               }
 
-       while (1) {
-               pid = waitpid(-1, &status, WNOHANG);
-               if (pid <= 0)
-                       break;
-               if (WIFEXITED(status))
-                       status = WEXITSTATUS(status);
-               else if (WIFSIGNALED(status))
-                       status = WTERMSIG(status) + 128;
-               else
-                       status = 0;
-               udev_done(pid, status);
+                               udev_list_node_remove(&worker->node);
+                               worker_unref(worker);
+                               childs--;
+                               break;
+                       }
+               }
+               break;
+       case SIGHUP:
+               reload_config = 1;
+               break;
        }
 }
 
@@ -576,7 +729,7 @@ int main(int argc, char *argv[])
 {
        struct udev *udev;
        int fd;
-       struct sigaction act;
+       sigset_t mask;
        const char *value;
        int daemonize = 0;
        int resolve_names = 1;
@@ -669,29 +822,76 @@ int main(int argc, char *argv[])
                rc = 1;
                goto exit;
        }
-
        if (udev_ctrl_enable_receiving(udev_ctrl) < 0) {
                fprintf(stderr, "error binding control socket, seems udevd is already running\n");
                err(udev, "error binding control socket, seems udevd is already running\n");
                rc = 1;
                goto exit;
        }
+       pfd[FD_CONTROL].fd = udev_ctrl_get_fd(udev_ctrl);
 
-       kernel_monitor = udev_monitor_new_from_netlink(udev, "kernel");
-       if (kernel_monitor == NULL || udev_monitor_enable_receiving(kernel_monitor) < 0) {
+       monitor = udev_monitor_new_from_netlink(udev, "kernel");
+       if (monitor == NULL || udev_monitor_enable_receiving(monitor) < 0) {
                fprintf(stderr, "error initializing netlink socket\n");
                err(udev, "error initializing netlink socket\n");
                rc = 3;
                goto exit;
        }
-       udev_monitor_set_receive_buffer_size(kernel_monitor, 128*1024*1024);
+       udev_monitor_set_receive_buffer_size(monitor, 128*1024*1024);
+       pfd[FD_NETLINK].fd = udev_monitor_get_fd(monitor);
+
+       pfd[FD_INOTIFY].fd = udev_watch_init(udev);
+       if (pfd[FD_INOTIFY].fd < 0) {
+               fprintf(stderr, "error initializing inotify\n");
+               err(udev, "error initializing inotify\n");
+               rc = 4;
+               goto exit;
+       }
+
+       if (udev_get_rules_path(udev) != NULL) {
+               inotify_add_watch(pfd[FD_INOTIFY].fd, udev_get_rules_path(udev),
+                                 IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+       } else {
+               char filename[UTIL_PATH_SIZE];
+
+               inotify_add_watch(pfd[FD_INOTIFY].fd, UDEV_PREFIX "/lib/udev/rules.d",
+                                 IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+               inotify_add_watch(pfd[FD_INOTIFY].fd, SYSCONFDIR "/udev/rules.d",
+                                 IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+
+               /* watch dynamic rules directory */
+               util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/rules.d", NULL);
+               inotify_add_watch(pfd[FD_INOTIFY].fd, filename,
+                                 IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+       }
+       udev_watch_restore(udev);
+
+       /* block and listen to all signals on signalfd */
+       sigfillset(&mask);
+       sigprocmask(SIG_SETMASK, &mask, NULL);
+       pfd[FD_SIGNAL].fd = signalfd(-1, &mask, 0);
+       if (pfd[FD_SIGNAL].fd < 0) {
+               fprintf(stderr, "error getting signalfd\n");
+               err(udev, "error getting signalfd\n");
+               rc = 5;
+               goto exit;
+       }
+
+       /* unnamed socket from workers to the main daemon */
+       if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, worker_watch) < 0) {
+               fprintf(stderr, "error getting socketpair\n");
+               err(udev, "error getting socketpair\n");
+               rc = 6;
+               goto exit;
+       }
+       pfd[FD_WORKER].fd = worker_watch[READ_END];
 
        rules = udev_rules_new(udev, resolve_names);
        if (rules == NULL) {
                err(udev, "error reading rules\n");
                goto exit;
        }
-       udev_list_init(&event_list);
+
        udev_queue_export = udev_queue_export_new(udev);
        if (udev_queue_export == NULL) {
                err(udev, "error creating queue file\n");
@@ -704,19 +904,19 @@ int main(int argc, char *argv[])
                pid = fork();
                switch (pid) {
                case 0:
-                       dbg(udev, "daemonized fork running\n");
                        break;
                case -1:
                        err(udev, "fork of daemon failed: %m\n");
                        rc = 4;
                        goto exit;
                default:
-                       dbg(udev, "child [%u] running, parent exits\n", pid);
                        rc = 0;
                        goto exit;
                }
        }
 
+       startup_log(udev);
+
        /* redirect std{out,err} */
        if (!debug && !debug_trace) {
                dup2(fd, STDIN_FILENO);
@@ -742,159 +942,115 @@ int main(int argc, char *argv[])
                close(fd);
        }
 
-       startup_log(udev);
-
-       /* set signal handlers */
-       memset(&act, 0x00, sizeof(struct sigaction));
-       act.sa_handler = sig_handler;
-       sigemptyset(&act.sa_mask);
-       act.sa_flags = SA_RESTART;
-       sigaction(SIGINT, &act, NULL);
-       sigaction(SIGTERM, &act, NULL);
-       sigaction(SIGCHLD, &act, NULL);
-       sigaction(SIGHUP, &act, NULL);
-
-       /* watch rules directory */
-       udev_watch_init(udev);
-       if (inotify_fd >= 0) {
-               if (udev_get_rules_path(udev) != NULL) {
-                       inotify_add_watch(inotify_fd, udev_get_rules_path(udev),
-                                         IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-               } else {
-                       char filename[UTIL_PATH_SIZE];
-
-                       inotify_add_watch(inotify_fd, UDEV_PREFIX "/lib/udev/rules.d",
-                                         IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-                       inotify_add_watch(inotify_fd, SYSCONFDIR "/udev/rules.d",
-                                         IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-
-                       /* watch dynamic rules directory */
-                       util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev/rules.d", NULL);
-                       inotify_add_watch(inotify_fd, filename,
-                                         IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-               }
-
-               udev_watch_restore(udev);
-       }
-
        /* in trace mode run one event after the other */
        if (debug_trace) {
                max_childs = 1;
        } else {
                int memsize = mem_size_mb();
+
                if (memsize > 0)
-                       max_childs = 128 + (memsize / 4);
+                       max_childs = 128 + (memsize / 8);
                else
-                       max_childs = UDEVD_MAX_CHILDS;
+                       max_childs = 128;
        }
+
        /* possibly overwrite maximum limit of executed events */
        value = getenv("UDEVD_MAX_CHILDS");
        if (value)
                max_childs = strtoul(value, NULL, 10);
        info(udev, "initialize max_childs to %u\n", max_childs);
 
+       udev_list_init(&event_list);
+       udev_list_init(&worker_list);
+
        while (!udev_exit) {
-               sigset_t blocked_mask, orig_mask;
-               struct pollfd pfd[4];
-               struct pollfd *ctrl_poll, *monitor_poll, *inotify_poll = NULL;
-               int nfds = 0;
                int fdcount;
+               int timeout;
 
-               sigfillset(&blocked_mask);
-               sigprocmask(SIG_SETMASK, &blocked_mask, &orig_mask);
-               if (signal_received) {
-                       sigprocmask(SIG_SETMASK, &orig_mask, NULL);
-                       goto handle_signals;
-               }
+               /* set timeout to kill idle workers */
+               if (udev_list_is_empty(&event_list) && childs > 2)
+                       timeout = 3 * 1000;
+               else
+                       timeout = -1;
+               /* wait for events */
+               fdcount = poll(pfd, ARRAY_SIZE(pfd), timeout);
+               if (fdcount < 0)
+                       continue;
 
-               ctrl_poll = &pfd[nfds++];
-               ctrl_poll->fd = udev_ctrl_get_fd(udev_ctrl);
-               ctrl_poll->events = POLLIN;
+               /* timeout - kill idle workers */
+               if (fdcount == 0)
+                       worker_kill(2);
 
-               monitor_poll = &pfd[nfds++];
-               monitor_poll->fd = udev_monitor_get_fd(kernel_monitor);
-               monitor_poll->events = POLLIN;
+               /* event has finished */
+               if (pfd[FD_WORKER].revents & POLLIN)
+                       worker_returned();
 
-               if (inotify_fd >= 0) {
-                       inotify_poll = &pfd[nfds++];
-                       inotify_poll->fd = inotify_fd;
-                       inotify_poll->events = POLLIN;
-               }
+               /* get kernel uevent */
+               if (pfd[FD_NETLINK].revents & POLLIN) {
+                       struct udev_device *dev;
 
-               fdcount = ppoll(pfd, nfds, NULL, &orig_mask);
-               sigprocmask(SIG_SETMASK, &orig_mask, NULL);
-               if (fdcount < 0) {
-                       if (errno == EINTR)
-                               goto handle_signals;
-                       err(udev, "error in select: %m\n");
-                       continue;
+                       dev = udev_monitor_receive_device(monitor);
+                       if (dev != NULL)
+                               event_queue_insert(dev);
+                       else
+                               udev_device_unref(dev);
                }
 
-               /* get control message */
-               if (ctrl_poll->revents & POLLIN)
-                       handle_ctrl_msg(udev_ctrl);
-
-               /* get kernel uevent */
-               if (monitor_poll->revents & POLLIN) {
-                       struct udev_device *dev;
+               /* start new events */
+               if (!udev_list_is_empty(&event_list) && !stop_exec_queue)
+                       events_start(udev);
 
-                       dev = udev_monitor_receive_device(kernel_monitor);
-                       if (dev != NULL) {
-                               struct udev_event *event;
+               /* get signal */
+               if (pfd[FD_SIGNAL].revents & POLLIN) {
+                       struct signalfd_siginfo fdsi;
+                       ssize_t size;
 
-                               event = udev_event_new(dev);
-                               if (event != NULL)
-                                       event_queue_insert(event);
-                               else
-                                       udev_device_unref(dev);
-                       }
+                       size = read(pfd[FD_SIGNAL].fd, &fdsi, sizeof(struct signalfd_siginfo));
+                       if (size == sizeof(struct signalfd_siginfo))
+                               handle_signal(fdsi.ssi_signo);
                }
 
-               /* rules directory inotify watch */
-               if (inotify_poll && (inotify_poll->revents & POLLIN))
+               /* device node and rules directory inotify watch */
+               if (pfd[FD_INOTIFY].revents & POLLIN)
                        handle_inotify(udev);
 
-handle_signals:
-               signal_received = 0;
+               /*
+                * 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
+                * "change" events by the inotify device node watch.
+                */
+               if (pfd[FD_CONTROL].revents & POLLIN)
+                       handle_ctrl_msg(udev_ctrl);
 
                /* rules changed, set by inotify or a HUP signal */
                if (reload_config) {
                        struct udev_rules *rules_new;
 
-                       reload_config = 0;
+                       worker_kill(0);
                        rules_new = udev_rules_new(udev, resolve_names);
                        if (rules_new != NULL) {
                                udev_rules_unref(rules);
                                rules = rules_new;
                        }
-               }
-
-               if (sigchilds_waiting) {
-                       sigchilds_waiting = 0;
-                       reap_sigchilds();
-               }
-
-               if (run_exec_q) {
-                       run_exec_q = 0;
-                       if (!stop_exec_q)
-                               event_queue_manager(udev);
-               }
-
-               if (settle_pid > 0) {
-                       kill(settle_pid, SIGUSR1);
-                       settle_pid = 0;
+                       reload_config = 0;
                }
        }
+
        udev_queue_export_cleanup(udev_queue_export);
        rc = 0;
 exit:
-
        udev_queue_export_unref(udev_queue_export);
        udev_rules_unref(rules);
        udev_ctrl_unref(udev_ctrl);
-       if (inotify_fd >= 0)
-               close(inotify_fd);
-       udev_monitor_unref(kernel_monitor);
+       if (pfd[FD_SIGNAL].fd >= 0)
+               close(pfd[FD_SIGNAL].fd);
+       if (worker_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_selinux_exit(udev);
        udev_unref(udev);
        logging_close();