Re: [GIT] Experimental threaded udev

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Jun 2, 2009 at 16:05, Kay Sievers <kay.sievers@xxxxxxxx> wrote:

> Version 5. I guess we should go for the signalfd stuff, it looks prett
> nice. The socketpair instead of the rtsignal is also nice, it allows
> us to pass back arbitrary data from finished events to the main
> daemon.

Version 7. We need to handle the netlink unicast addresses, they are
not neccessarily the process pid, so we need to remember the netlink
address.

We also keep 2 workers around, and don't kill them, to handle incoming
events without forking.

Thanks,
Kay
diff --git a/NEWS b/NEWS
index ac44d7a..8b51c10 100644
--- 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 773bc55..a14e5c0 100644
--- 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 5b6af64..bedccdb 100644
--- 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)
diff --git a/configure.ac b/configure.ac
index f1d008e..9857d52 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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}'])
diff --git a/udev/Makefile.am b/udev/Makefile.am
index 6cd2f23..94989e6 100644
--- a/udev/Makefile.am
+++ b/udev/Makefile.am
@@ -14,7 +14,6 @@ common_ldadd =
 
 common_files = \
 	udev.h \
-	udev-sysdeps.h \
 	udev-event.c \
 	udev-watch.c \
 	udev-node.c \
diff --git a/udev/lib/libudev-monitor.c b/udev/lib/libudev-monitor.c
index 395a4d2..33a0605 100644
--- a/udev/lib/libudev-monitor.c
+++ b/udev/lib/libudev-monitor.c
@@ -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;
diff --git a/udev/lib/libudev-private.h b/udev/lib/libudev-private.h
index dc02a84..5512341 100644
--- a/udev/lib/libudev-private.h
+++ b/udev/lib/libudev-private.h
@@ -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 */
diff --git a/udev/udev-event.c b/udev/udev-event.c
index d521251..3f69c0b 100644
--- a/udev/udev-event.c
+++ b/udev/udev-event.c
@@ -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
index 35671ba..0000000
--- a/udev/udev-sysdeps.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * wrapping of libc features and kernel interfaces
- *
- * Copyright (C) 2005-2008 Kay Sievers <kay.sievers@xxxxxxxx>
- *
- * 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
diff --git a/udev/udev-watch.c b/udev/udev-watch.c
index 53492e5..5a49c96 100644
--- a/udev/udev-watch.c
+++ b/udev/udev-watch.c
@@ -26,27 +26,24 @@
 #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
diff --git a/udev/udev.h b/udev/udev.h
index 8f2c1c6..7187975 100644
--- a/udev/udev.h
+++ b/udev/udev.h
@@ -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);
diff --git a/udev/udevadm.xml b/udev/udevadm.xml
index 538180b..2e03d98 100644
--- a/udev/udevadm.xml
+++ b/udev/udevadm.xml
@@ -285,9 +285,9 @@
               <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>
diff --git a/udev/udevd.c b/udev/udevd.c
index 37b547a..dce81ac 100644
--- a/udev/udevd.c
+++ b/udev/udevd.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@xxxxxxxx>
+ * Copyright (C) 2004-2009 Kay Sievers <kay.sievers@xxxxxxxx>
  * Copyright (C) 2004 Chris Friesen <chris_friesen@xxxxxxxxxxxx>
  * Copyright (C) 2009 Canonical Ltd.
  * Copyright (C) 2009 Scott James Remnant <scott@xxxxxxxxxxxx>
@@ -30,23 +30,21 @@
 #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_run(struct event *event)
+{
+	struct udev_list_node *loop;
+
+	udev_list_node_foreach(loop, &worker_list) {
+		struct worker *worker = node_to_worker(loop);
+		ssize_t count;
+
+		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_event *event)
+static void event_queue_insert(struct udev_device *dev)
 {
-	event->queue_time = time(NULL);
+	struct event *event;
 
-	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));
+	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);
+
+			if (worker->pid != msg.pid)
+				continue;
 
-		/* retry if events finished in the meantime */
-		if (sigchilds_waiting) {
-			sigchilds_waiting = 0;
-			reap_sigchilds();
-			goto start_over;
+			/* 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,109 @@ 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)
+		if (pfd[FD_CONTROL].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;
-
 		/* 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();

[Index of Archives]     [Linux Kernel]     [Linux DVB]     [Asterisk Internet PBX]     [DCCP]     [Netdev]     [X.org]     [Util Linux NG]     [Fedora Women]     [ALSA Devel]     [Linux USB]

  Powered by Linux