Re: [GIT] Experimental threaded udev

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

 



On Mon, Jun 1, 2009 at 21:39, Kay Sievers <kay.sievers@xxxxxxxx> wrote:
> On Mon, Jun 1, 2009 at 18:24, Alan Jenkins <alan-jenkins@xxxxxxxxxxxxxx> wrote:
>
>> I don't think the signal handler should be doing list traversal, in case it
>> interrupts list manipulation in the main loop.  Or perhaps the main loop
>> should only allow signals during ppoll().
>
> Make sense. we can block them during list mangling, or just go for
> signalfd, and convert all the signal handling to it.

Here is a version that uses signalfd(), and a socketpair() from the
workers to pass data back. Same numbers so far, but the code looks a
lot simpler.

Kay
diff --git a/configure.ac b/configure.ac
index f1d008e..f126146 100644
--- a/configure.ac
+++ b/configure.ac
@@ -23,10 +23,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..54c9576 100644
--- a/udev/lib/libudev-monitor.c
+++ b/udev/lib/libudev-monitor.c
@@ -32,7 +32,6 @@ struct udev_monitor {
 	int refcount;
 	int sock;
 	struct sockaddr_nl snl;
-	struct sockaddr_nl snl_peer;
 	struct sockaddr_un sun;
 	socklen_t addrlen;
 	struct udev_list_node filter_subsystem_list;
@@ -171,8 +170,8 @@ 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 = 0;
+	else if (strcmp(name, "kernel") == 0)
 		group = UDEV_MONITOR_KERNEL;
 	else if (strcmp(name, "udev") == 0)
 		group = UDEV_MONITOR_UDEV;
@@ -193,8 +192,6 @@ 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;
 
 	dbg(udev, "monitor %p created with NETLINK_KOBJECT_UEVENT (%u)\n", udev_monitor, group);
 	return udev_monitor;
@@ -434,7 +431,6 @@ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monito
 	struct iovec iov;
 	char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
 	struct cmsghdr *cmsg;
-	struct sockaddr_nl snl;
 	struct ucred *cred;
 	char buf[8192];
 	ssize_t buflen;
@@ -459,11 +455,6 @@ retry:
 	smsg.msg_control = cred_msg;
 	smsg.msg_controllen = sizeof(cred_msg);
 
-	if (udev_monitor->snl.nl_family != 0) {
-		smsg.msg_name = &snl;
-		smsg.msg_namelen = sizeof(snl);
-	}
-
 	buflen = recvmsg(udev_monitor->sock, &smsg, 0);
 	if (buflen < 0) {
 		if (errno != EINTR)
@@ -476,20 +467,6 @@ retry:
 		return NULL;
 	}
 
-	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) {
-			if (snl.nl_pid > 0) {
-				info(udev_monitor->udev, "multicast kernel netlink message from pid %d ignored\n", snl.nl_pid);
-				return NULL;
-			}
-			is_kernel = 1;
-		}
-	}
-
 	cmsg = CMSG_FIRSTHDR(&smsg);
 	if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
 		info(udev_monitor->udev, "no sender credentials received, message ignored\n");
@@ -621,7 +598,7 @@ 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_device *udev_device, pid_t pid)
 {
 	struct msghdr smsg;
 	struct iovec iov[2];
@@ -660,6 +637,7 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_devi
 	} else if (udev_monitor->snl.nl_family != 0) {
 		const char *val;
 		struct udev_monitor_netlink_header nlh;
+		struct sockaddr_nl snl_peer;
 
 
 		/* add versioned header */
@@ -680,11 +658,18 @@ int udev_monitor_send_device(struct udev_monitor *udev_monitor, struct udev_devi
 		iov[1].iov_base = (char *)buf;
 		iov[1].iov_len = blen;
 
+		/* we will always get ECONNREFUSED when sending to the muticast group */
+		memset(&snl_peer, 0x00, sizeof(struct sockaddr_nl));
+		snl_peer.nl_family = AF_NETLINK;
+		if (pid > 0)
+			snl_peer.nl_pid = pid;
+		else
+			snl_peer.nl_groups = UDEV_MONITOR_UDEV;
+
 		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;
+		smsg.msg_name = &snl_peer;
 		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..e600802 100644
--- a/udev/lib/libudev-private.h
+++ b/udev/lib/libudev-private.h
@@ -86,7 +86,7 @@ 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_send_device(struct udev_monitor *udev_monitor, struct udev_device *udev_device, pid_t pid);
 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..8ab262a 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, event->dev, 0);
 			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/udevd.c b/udev/udevd.c
index 37b547a..7e6d244 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,163 +59,311 @@ 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_socket[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_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;
+};
 
-static struct udev_event *node_to_event(struct udev_list_node *node)
+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;
+	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 event_fork(struct udev_event *event)
+static void worker_new(struct event *event)
 {
+	struct worker *worker;
 	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));
-	}
+	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);
+		udev_monitor_unref(monitor);
+		close(pfd[FD_SIGNAL].fd);
+		close(worker_socket[READ_END]);
 		logging_close();
-		logging_init("udevd-event");
+		logging_init("udevd-work");
 		setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY);
 
+		/* re-open socket to listen to udevd only, and send back libudev events */
+		monitor = udev_monitor_new_from_netlink(event->udev, NULL);
+		if (monitor == NULL)
+			_exit(2);
+		udev_monitor_enable_receiving(monitor);
+
 		/* set signal handlers */
 		memset(&act, 0x00, sizeof(act));
 		act.sa_handler = event_sig_handler;
 		sigemptyset (&act.sa_mask);
 		act.sa_flags = 0;
+		sigaction(SIGTERM, &act, NULL);
 		sigaction(SIGALRM, &act, NULL);
 
-		/* 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 main daemon 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 libudev listeners */
+			udev_monitor_send_device(monitor, dev, 0);
+
+			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_socket[WRITE_END], &msg, sizeof(struct worker_message), 0);
 
-		/* send processed event back to the kernel netlink socket */
-		udev_monitor_send_device(kernel_monitor, event->dev);
+			/* wait for more device messages from udevd */
+			do
+				dev = udev_monitor_receive_device(monitor);
+			while (!worker_exit && dev == NULL);
+		}
 
-		info(event->udev, "seq %llu exit with %i\n", udev_device_get_seqnum(event->dev), err);
+		udev_monitor_unref(monitor);
 		logging_close();
-		if (err != 0)
-			exit(1);
 		exit(0);
+	}
 	case -1:
+		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;
+		event->state = EVENT_RUNNING;
+		worker->event = event;
+		worker->pid = pid;
+		worker->state = WORKER_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_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));
+	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, event->dev, worker->pid);
+		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;
+	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_idle(void)
+{
+	struct udev_list_node *loop;
+
+	udev_list_node_foreach(loop, &worker_list) {
+		struct worker *worker = node_to_worker(loop);
+
+		if (worker->state != WORKER_IDLE)
+			continue;
+
+		worker->state = WORKER_KILLED;
+		kill(worker->pid, SIGTERM);
+	}
+}
+
 static int mem_size_mb(void)
 {
 	FILE *f;
@@ -265,13 +411,13 @@ static int compare_devpath(const char *running, const char *waiting)
 }
 
 /* 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;
 
 	/* 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)
@@ -305,52 +451,6 @@ static int devpath_busy(struct udev_event *event)
 	return 0;
 }
 
-/* serializes events for the identical and parent and child devices */
-static void event_queue_manager(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;
-		}
-
-		if (loop_event->pid != 0)
-			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));
-			continue;
-		}
-
-		event_fork(loop_event);
-		dbg(udev, "moved seq %llu to running list\n", udev_device_get_seqnum(loop_event->dev));
-
-		/* retry if events finished in the meantime */
-		if (sigchilds_waiting) {
-			sigchilds_waiting = 0;
-			reap_sigchilds();
-			goto start_over;
-		}
-	}
-}
-
 /* receive the udevd message from userspace */
 static void handle_ctrl_msg(struct udev_ctrl *uctrl)
 {
@@ -371,13 +471,12 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl)
 
 	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) {
@@ -420,6 +519,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 +528,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;
@@ -480,71 +579,6 @@ static int handle_inotify(struct udev *udev)
 	return 0;
 }
 
-static void sig_handler(int signum)
-{
-	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;
-	}
-
-	signal_received = 1;
-}
-
-static void udev_done(int pid, int exitstatus)
-{
-	struct udev_list_node *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;
-		}
-	}
-}
-
-static void reap_sigchilds(void)
-{
-	pid_t pid;
-	int status;
-
-	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);
-	}
-}
-
 static void startup_log(struct udev *udev)
 {
 	FILE *f;
@@ -576,7 +610,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 +703,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_NETLINK].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_NETLINK].fd, UDEV_PREFIX "/lib/udev/rules.d",
+				  IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+		inotify_add_watch(pfd[FD_NETLINK].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_NETLINK].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_socket) < 0) {
+		fprintf(stderr, "error getting socketpair\n");
+		err(udev, "error getting socketpair\n");
+		rc = 6;
+		goto exit;
+	}
+	pfd[FD_WORKER].fd = worker_socket[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 +785,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 +823,189 @@ 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);
 		else
-			max_childs = UDEVD_MAX_CHILDS;
+			max_childs = 256;
 	}
+
 	/* 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;
-		}
-
-		ctrl_poll = &pfd[nfds++];
-		ctrl_poll->fd = udev_ctrl_get_fd(udev_ctrl);
-		ctrl_poll->events = POLLIN;
-
-		monitor_poll = &pfd[nfds++];
-		monitor_poll->fd = udev_monitor_get_fd(kernel_monitor);
-		monitor_poll->events = POLLIN;
+		/* set timeout to kill idle workers */
+		if (udev_list_is_empty(&event_list) && !udev_list_is_empty(&worker_list))
+			timeout = 10 * 1000;
+		else
+			timeout = -1;
+		/* wait for events */
+		fdcount = poll(pfd, ARRAY_SIZE(pfd), timeout);
+		if (fdcount < 0)
+			continue;
 
-		if (inotify_fd >= 0) {
-			inotify_poll = &pfd[nfds++];
-			inotify_poll->fd = inotify_fd;
-			inotify_poll->events = POLLIN;
+		/* timeout - kill idle workers */
+		if (fdcount == 0)
+			worker_kill_idle();
+
+		/* event has finished */
+		if (pfd[FD_WORKER].revents & POLLIN) {
+			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;
+
+					/* worker returned */
+					worker->event->exitcode = msg.exitcode;
+					event_queue_delete(worker->event);
+					worker->event = NULL;
+					worker->state = WORKER_IDLE;
+					break;
+				}
+			}
 		}
 
-		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;
+		/* get kernel uevent */
+		if (pfd[FD_NETLINK].revents & POLLIN) {
+			struct udev_device *dev;
+
+			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) {
+			struct udev_list_node *loop;
 
-			dev = udev_monitor_receive_device(kernel_monitor);
-			if (dev != NULL) {
-				struct udev_event *event;
+			udev_list_node_foreach(loop, &event_list) {
+				struct event *event = node_to_event(loop);
+
+				/* skip running events */
+				if (event->state != EVENT_QUEUED)
+					continue;
+
+				/* do not start event if parent or child event is still running */
+				if (devpath_busy(event) != 0) {
+					dbg(udev, "delay seq %llu (%s)\n",
+					    udev_device_get_seqnum(event->dev),
+					    udev_device_get_devpath(event->dev));
+					continue;
+				}
 
-				event = udev_event_new(dev);
-				if (event != NULL)
-					event_queue_insert(event);
-				else
-					udev_device_unref(dev);
+				event_run(event);
 			}
 		}
 
-		/* rules directory inotify watch */
-		if (inotify_poll && (inotify_poll->revents & POLLIN))
-			handle_inotify(udev);
+		/* get signal */
+		if (pfd[FD_SIGNAL].revents & POLLIN) {
+			struct signalfd_siginfo fdsi;
+			ssize_t size;
+
+			size = read(pfd[FD_SIGNAL].fd, &fdsi, sizeof(struct signalfd_siginfo));
+			if (size != sizeof(struct signalfd_siginfo))
+				continue;
+			switch (fdsi.ssi_signo) {
+			case SIGINT:
+			case SIGTERM:
+				udev_exit = 1;
+				break;
+			case SIGCHLD:
+				while (1) {
+					pid_t pid;
+					struct udev_list_node *loop, *tmp;
+
+					pid = waitpid(-1, NULL, WNOHANG);
+					if (pid <= 0)
+						break;
+
+					udev_list_node_foreach_safe(loop, tmp, &worker_list) {
+						struct worker *worker = node_to_worker(loop);
+
+						if (worker->pid != pid)
+							continue;
+
+						/* fail event, if worker died unexpectedly */
+						if (worker->event != NULL) {
+							worker->event->exitcode = 127;
+							event_queue_delete(worker->event);
+						}
+
+						udev_list_node_remove(&worker->node);
+						free(worker);
+						childs--;
+						break;
+					}
+				}
+				break;
+			case SIGHUP:
+				reload_config = 1;
+				break;
+			}
+		}
 
-handle_signals:
-		signal_received = 0;
+		/* device node and rules directory inotify watch */
+		if (pfd[FD_INOTIFY].revents & POLLIN)
+			handle_inotify(udev);
 
 		/* rules changed, set by inotify or a HUP signal */
 		if (reload_config) {
 			struct udev_rules *rules_new;
 
-			reload_config = 0;
+			worker_kill_idle();
 			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_socket[READ_END] >= 0)
+		close(worker_socket[READ_END]);
+	if (worker_socket[WRITE_END] >= 0)
+		close(worker_socket[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