Re: Revisiting threaded udevd

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

 



Scott James Remnant wrote:
On Thu, 2008-12-25 at 17:36 +0000, Alan Jenkins wrote:
I think I got it right, maybe I missed something that was in your
original patch?

Could be. Either way, I found your problem:

udev-rules.c: In function ‘udev_rules_apply_to_event’:
udev-rules.c:2201: warning: passing argument 1 of ‘udev_exec’ from incompatible pointer type

(The relevant line says event->udev, when it should probably be event->dev).

I'd already updated my original patches, so you might try them instead (attached). They're not quite as well tested as before, but they should still work. I'd be interested in any results of yours.

The only vaguely unusual thing I was doing was running udevd in an
existing booted system, and running udevadm trigger on that - so there
would already have been udevdb entries for everything.

That's exactly how I test.

Thanks
Alan
diff --git a/udev/Makefile.am b/udev/Makefile.am
index 2d185f4..5739442 100644
--- a/udev/Makefile.am
+++ b/udev/Makefile.am
@@ -18,6 +18,7 @@ common_files = \
 	udev-event.c \
 	udev-node.c \
 	udev-rules.c \
+	udev-exec.c \
 	udev-util.c \
 	lib/libudev.h \
 	lib/libudev-private.h \
diff --git a/udev/test-udev.c b/udev/test-udev.c
index ff1c353..a26f308 100644
--- a/udev/test-udev.c
+++ b/udev/test-udev.c
@@ -60,6 +60,9 @@ int main(int argc, char *argv[])
 	info(udev, "version %s\n", VERSION);
 	udev_selinux_init(udev);
 
+	/* fork exec daemon before opening fds or changing signal handling */
+	udev_exec_init(udev);
+
 	/* set signal handlers */
 	memset(&act, 0x00, sizeof(act));
 	act.sa_handler = (void (*)(int)) sig_handler;
@@ -69,9 +72,6 @@ int main(int argc, char *argv[])
 	sigaction(SIGINT, &act, NULL);
 	sigaction(SIGTERM, &act, NULL);
 
-	/* trigger timeout to prevent hanging processes */
-	alarm(UDEV_EVENT_TIMEOUT);
-
 	action = getenv("ACTION");
 	devpath = getenv("DEVPATH");
 	subsystem = getenv("SUBSYSTEM");
@@ -99,10 +99,6 @@ int main(int argc, char *argv[])
 	event = udev_event_new(dev);
 	err = udev_event_execute_rules(event, rules);
 
-	/* rules may change/disable the timeout */
-	if (udev_device_get_event_timeout(dev) >= 0)
-		alarm(udev_device_get_event_timeout(dev));
-
 	if (err == 0 && !event->ignore_device && udev_get_run(udev))
 		udev_event_execute_run(event);
 
diff --git a/udev/udev-event.c b/udev/udev-event.c
index bc69239..f98638c 100644
--- a/udev/udev-event.c
+++ b/udev/udev-event.c
@@ -717,7 +717,7 @@ int udev_event_execute_run(struct udev_event *event)
 				       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_exec(event->dev, program, NULL, 0, NULL) != 0) {
 				if (!udev_list_entry_get_flag(list_entry))
 					err = -1;
 			}
diff --git a/udev/udev-exec.c b/udev/udev-exec.c
new file mode 100644
index 0000000..ae385bf
--- /dev/null
+++ b/udev/udev-exec.c
@@ -0,0 +1,592 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include "udev.h"
+
+/*
+ * This daemon avoids problems with fork+exec from a multithreaded process -
+ * performance, and the need to close irrelevant file descriptors.
+ *
+ * TODO: Timeouts have not been tested
+ */
+
+#define MSG_SIZE 2048
+
+struct child {
+	struct udev_list_node node; /* must be first member */
+	pid_t pid;
+	int statuspipe; /* Pipe to write exit status to */
+};
+
+static int exec_msg(struct udev *udev, int sock, struct child **childp)
+{
+	struct msghdr msg;
+	struct iovec iov[1];
+	char msgbuf[MSG_SIZE];
+	int retval;
+	size_t count;
+	struct cmsghdr *cmsg;
+	char fdbuf[CMSG_SPACE(sizeof(int) * 4)];  /* ancillary data buffer */
+	int *fdptr = NULL;
+	int statuspipe;
+	int inpipe, outpipe, errpipe;
+	char *cmd, *env;
+	size_t cmdlen, envlen;
+	char program[UTIL_PATH_SIZE];
+	char *argv[(MSG_SIZE / 2) + 1];
+	char *envp[(MSG_SIZE / 4) + 1];
+	struct child *child;
+	pid_t pid;
+	int i;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.msg_control = fdbuf;
+	msg.msg_controllen = sizeof(fdbuf);
+
+	iov[0].iov_base = msgbuf;
+	iov[0].iov_len = sizeof(msgbuf);
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 1;
+
+	retval = recvmsg(sock, &msg, 0);
+	if (retval < 0) {
+		err(udev, "read error on socket (quitting): %m\n");
+		return -1;
+	}
+	count = retval;
+	if (count == 0) {
+		info(udev, "socket closed by udevd (quitting)\n");
+		return -1;
+	}
+
+	/* get file descriptors from message */
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg,cmsg)) {
+		if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+			if (cmsg->cmsg_len < CMSG_LEN(sizeof(int) * 4)) {
+				err(udev, "message has too few pipes\n");
+				return 0;
+			}
+
+			fdptr = (int *) CMSG_DATA(cmsg);
+			inpipe = fdptr[0];
+			outpipe = fdptr[1];
+			errpipe = fdptr[2];
+			statuspipe = fdptr[3];
+			break;
+		}
+	}
+	if (fdptr == NULL) {
+		err(udev, "message lacks pipes\n");
+		return 0;
+	}
+
+	/* get command string and environment buffer from message */
+	cmd = &msgbuf[0];
+	cmdlen = strnlen(msgbuf, count);
+	if (cmdlen == count) {
+		err(udev, "message command lacks nul terminator\n");
+		return 0;
+	}
+	count -= (cmdlen + 1);
+	env = &msgbuf[cmdlen + 1];
+	envlen = count;
+
+	/* build argv from command */
+	info(udev, "'%s'\n", cmd);
+	i = 0;
+	if (strchr(cmd, ' ') != NULL) {
+		char *pos = cmd;
+
+		while (pos != NULL && pos[0] != '\0') {
+			if (pos[0] == '\'') {
+				/* do not separate quotes */
+				pos++;
+				argv[i] = strsep(&pos, "\'");
+				while (pos != NULL && pos[0] == ' ')
+					pos++;
+			} else {
+				argv[i] = strsep(&pos, " ");
+			}
+			dbg(udev, "arg[%i] '%s'\n", i, argv[i]);
+			i++;
+		}
+		argv[i] = NULL;
+	} else {
+		argv[0] = cmd;
+		argv[1] = NULL;
+	}
+
+	/* allow programs in /lib/udev/ to be called without the path */
+	if (strchr(argv[0], '/') == NULL) {
+		util_strlcpy(program, UDEV_PREFIX "/lib/udev/", sizeof(program));
+		util_strlcat(program, argv[0], sizeof(program));
+		argv[0] = program;
+	}
+
+	/* build envp from environment buffer */
+	i = 0;
+	if (envlen > 0) {
+		size_t off = 0;
+
+		while (off < envlen) {
+			envp[i] = &env[off];
+			off += strlen(&env[off]);
+			off++;
+			i++;
+		}
+		envp[i] = NULL;
+	} else {
+		envp[0] = NULL;
+	}
+
+
+	child = malloc(sizeof(struct child));
+	if (child == NULL)
+		return 0;
+
+	pid = fork();
+	switch(pid) {
+	case 0:
+		close(sock);
+		close(statuspipe);
+
+		dup2(inpipe, STDIN_FILENO);
+		close(inpipe);
+		dup2(outpipe, STDOUT_FILENO);
+		close(outpipe);
+		dup2(errpipe, STDERR_FILENO);
+		close(errpipe);
+
+		execve(argv[0], argv, envp);
+		if (errno == ENOENT || errno == ENOTDIR) {
+			/* may be on a filesytem which is not mounted right now */
+			info(udev, "program '%s' not found\n", argv[0]);
+		} else {
+			/* other problems */
+			err(udev, "exec of program '%s' failed\n", argv[0]);
+		}
+		_exit(1);
+	case -1:
+		err(udev, "fork of '%s' failed: %m\n", argv[0]);
+		free(child);
+		return 0;
+	default:
+		close(inpipe);
+		close(outpipe);
+		close(errpipe);
+
+		child->pid = pid;
+		child->statuspipe = statuspipe;
+
+		*childp = child;
+		return 0;
+	}
+}
+
+static int signal_pipe[2] = {-1, -1};
+
+static void asmlinkage sig_handler(int signum)
+{
+	/* write to pipe, which will wakeup select() */
+	write(signal_pipe[WRITE_END], "", 1);
+}
+
+static void udev_exec_daemon(struct udev *udev, int sock)
+{
+	struct udev_list_node child_list; /* Oldest first */
+	struct udev_list_node *child_loop;
+	struct sigaction act;
+	fd_set fds;
+	int maxfd;
+	int retval;
+
+	udev_list_init(&child_list);
+
+	/* setup signal handler pipe */
+	retval = pipe(signal_pipe);
+	if (retval < 0) {
+		err(udev, "error getting pipes: %m\n");
+		goto exit;
+	}
+
+	retval = fcntl(signal_pipe[WRITE_END], F_GETFL, 0);
+	if (retval < 0) {
+		err(udev, "error fcntl on pipe: %m\n");
+		goto exit;
+	}
+	retval = fcntl(signal_pipe[WRITE_END], F_SETFL, retval | O_NONBLOCK);
+	if (retval < 0) {
+		err(udev, "error fcntl on pipe: %m\n");
+		goto exit;
+	}
+
+	/* reset handlers to default */
+	memset(&act, 0x00, sizeof(struct sigaction));
+	sigemptyset(&act.sa_mask);
+	act.sa_handler = SIG_DFL;
+	sigaction(SIGINT, &act, NULL);
+	sigaction(SIGTERM, &act, NULL);
+	sigaction(SIGHUP, &act, NULL);
+
+	/* set SIGCHLD handler */
+	act.sa_handler = sig_handler;
+	act.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+	sigaction(SIGCHLD, &act, NULL);
+
+	maxfd = sock;
+	maxfd = UDEV_MAX(maxfd, signal_pipe[READ_END]);
+	while(1) {
+		FD_ZERO(&fds);
+		FD_SET(sock, &fds);
+		FD_SET(signal_pipe[READ_END], &fds);
+
+		retval = select(maxfd+1, &fds, NULL, NULL, NULL);
+		if (retval < 0) {
+			if (errno == EINTR)
+				continue;
+
+			err(udev, "error in select: %m\n");
+			goto exit;
+		}
+
+		/* received a signal, clear our notification pipe */
+		if (FD_ISSET(signal_pipe[READ_END], &fds)) {
+			char buf[256];
+
+			read(signal_pipe[READ_END], &buf, sizeof(buf));
+		}
+
+		/* handle finished children */
+		while (1) {
+			pid_t pid;
+			int status;
+
+			pid = waitpid(-1, &status, WNOHANG);
+			if (pid <= 0)
+				break;
+
+			udev_list_node_foreach(child_loop, &child_list) {
+				struct child *child = (struct child *) child_loop;
+
+				if (child->pid == pid) {
+					write(child->statuspipe, &status, sizeof(status));
+					close(child->statuspipe);
+					udev_list_node_remove(&child->node);
+					free(child);
+					break;
+				}
+			}
+		}
+
+		/* read command and fork child */
+		if (FD_ISSET(sock, &fds)) {
+			struct child *new_child;
+
+			retval = exec_msg(udev, sock, &new_child);
+			if (retval == -1)
+				goto exit; /* read from socket failed - udevd has been terminated */
+
+			if (new_child)
+				udev_list_node_append(&new_child->node, &child_list);
+		}
+	}
+
+exit:
+	if (signal_pipe[READ_END] >= 0)
+		close(signal_pipe[READ_END]);
+	if (signal_pipe[WRITE_END] >= 0)
+		close(signal_pipe[WRITE_END]);
+	close(sock);
+
+	udev_unref(udev);
+	exit(1);
+}
+
+static int udev_exec_sock = -1;
+
+/* Use saved fd in case we re-create /dev/null */
+static int devnull = -1;
+
+int udev_exec_init(struct udev *udev)
+{
+	int sock[2];
+	int retval;
+	pid_t pid;
+
+	/* connection-oriented datagram socket -
+           daemon will die when we close it */
+	retval = socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, sock);
+	if (retval != 0) {
+		err(udev, "error getting socket pair: %m\n");
+		return -1;
+	}
+
+	retval = fflush(NULL);
+	if (retval != 0) {
+		err(udev, "error flushing buffers before fork: %m\n");
+		return -1;
+	}
+
+	pid = fork();
+	switch(pid) {
+	case 0:
+		close(sock[0]);
+		logging_close();
+		logging_init("udev-exec");
+		setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY);
+
+		udev_exec_daemon(udev, sock[1]);
+
+		/* not reached */
+		exit(1);
+	case -1:
+		err(udev, "failed to fork udev-exec daemon: %m\n");
+		return -1;
+	default:
+		udev_exec_sock = sock[0];
+		close(sock[1]);
+	}
+
+	devnull = open("/dev/null", O_RDWR);
+	if (devnull < 0) {
+		err(udev, "open /dev/null failed: %m\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+void udev_exec_cleanup(struct udev *udev)
+{
+	close(udev_exec_sock);
+	close(devnull);
+}
+
+int udev_exec(struct udev_device *dev, const char *command,
+	      char *result, size_t ressize, size_t *reslen)
+{
+	struct udev *udev = udev_device_get_udev(dev);
+	char **envp = udev_device_get_properties_envp(dev);
+	int outpipe[2] = {-1, -1};
+	int errpipe[2] = {-1, -1};
+	int statuspipe[2] = {-1, -1};
+	int status;
+	struct msghdr msg;
+	struct cmsghdr *cmsg;
+	char fdbuf[CMSG_SPACE(sizeof(int) * 4)];  /* ancillary data buffer */
+	int *fdptr;
+	struct iovec iov[2];
+	char envbuf[2048];
+	size_t bufpos = 0;
+	int i;
+	ssize_t count;
+	int maxfd;
+	struct timeval timeout;
+	size_t respos = 0;
+	int err = 0;
+
+	/* prepare pipes from child to parent */
+	if (result != NULL || udev_get_log_priority(udev) >= LOG_INFO) {
+		if (pipe(outpipe) != 0) {
+			err(udev, "pipe failed: %m\n");
+			return -1;
+		}
+	} else
+		outpipe[WRITE_END] = dup(devnull);
+
+	if (udev_get_log_priority(udev) >= LOG_INFO) {
+		if (pipe(errpipe) != 0) {
+			err(udev, "pipe failed: %m\n");
+			return -1;
+		}
+	} else
+		errpipe[WRITE_END] = dup(devnull);
+
+	if (pipe(statuspipe) != 0) {
+		err(udev, "pipe failed: %s\n", strerror(errno));
+		return -1;
+	}
+
+	/* child end of pipes are passed in ancillary data */
+	memset(&msg, 0, sizeof(msg));
+	msg.msg_control = fdbuf;
+	msg.msg_controllen = sizeof(fdbuf);
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(int) * 4);
+	fdptr = (int *) CMSG_DATA(cmsg);
+	fdptr[0] = devnull;
+	fdptr[1] = outpipe[WRITE_END];
+	fdptr[2] = errpipe[WRITE_END];
+	fdptr[3] = statuspipe[WRITE_END];
+	msg.msg_controllen = cmsg->cmsg_len;
+
+	for (i = 0; envp[i] != NULL && bufpos < (sizeof(envbuf)); i++) {
+		bufpos += util_strlcpy(&envbuf[bufpos], envp[i], sizeof(envbuf) - bufpos);
+		bufpos++;
+	}
+	if (bufpos > sizeof(envbuf))
+		bufpos = sizeof(envbuf);
+
+	/* prepare message payload: command + environment */
+	iov[0].iov_base = (char *) command;
+	iov[0].iov_len = strlen(command) + 1;
+	iov[1].iov_base = envbuf;
+	iov[1].iov_len = bufpos;
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 2;
+
+	/* dispatch request to daemon */
+	count = sendmsg(udev_exec_sock, &msg, 0);
+	if (count < 0) {
+		/* report error, but don't return without closing pipes */
+		err(udev, "failed to send message to exec daemon: %s\n", strerror(errno));
+	}
+
+	/* parent closes child ends of pipes */
+	close(outpipe[WRITE_END]);
+	close(errpipe[WRITE_END]);
+	close(statuspipe[WRITE_END]);
+
+	/* read child output */
+	if (udev_device_get_event_timeout(dev) >= 0)
+		timeout.tv_sec = udev_device_get_event_timeout(dev);
+	else
+		timeout.tv_sec = UDEV_EVENT_TIMEOUT;
+	timeout.tv_usec = 0;
+
+	maxfd = statuspipe[READ_END];
+	maxfd = UDEV_MAX(maxfd, outpipe[READ_END]);
+	maxfd = UDEV_MAX(maxfd, errpipe[READ_END]);
+	while (outpipe[READ_END] > 0 || errpipe[READ_END] > 0 || statuspipe[READ_END] > 0) {
+		int fdcount;
+		fd_set readfds;
+
+		FD_ZERO(&readfds);
+		if (outpipe[READ_END] > 0)
+			FD_SET(outpipe[READ_END], &readfds);
+		if (errpipe[READ_END] > 0)
+			FD_SET(errpipe[READ_END], &readfds);
+		if (statuspipe[READ_END] > 0)
+			FD_SET(statuspipe[READ_END], &readfds);
+		fdcount = select(maxfd+1, &readfds, NULL, NULL, &timeout);
+		if (fdcount < 0) {
+			if (errno == EINTR)
+				continue;
+			err = -1;
+			break;
+		}
+
+		if (timeout.tv_sec == 0 && timeout.tv_usec == 0) {
+			err = -1;
+			break;
+		}
+
+		/* get stdout */
+		if (outpipe[READ_END] > 0 && FD_ISSET(outpipe[READ_END], &readfds)) {
+			char inbuf[1024];
+			char *pos;
+			char *line;
+
+			count = read(outpipe[READ_END], inbuf, sizeof(inbuf)-1);
+			if (count <= 0) {
+				close(outpipe[READ_END]);
+				outpipe[READ_END] = -1;
+				if (count < 0) {
+					err(udev, "stdin read failed: %m\n");
+					err = -1;
+				}
+				continue;
+			}
+			inbuf[count] = '\0';
+
+			/* store result for rule processing */
+			if (result) {
+				if (respos + count < ressize) {
+					memcpy(&result[respos], inbuf, count);
+					respos += count;
+				} else {
+					err(udev, "ressize %ld too short\n", (long)ressize);
+					err = -1;
+				}
+			}
+			pos = inbuf;
+			while ((line = strsep(&pos, "\n")))
+				if (pos || line[0] != '\0')
+					info(udev, "'%s' (stdout) '%s'\n", command, line);
+		}
+
+		/* get stderr */
+		if (errpipe[READ_END] > 0 && FD_ISSET(errpipe[READ_END], &readfds)) {
+			char errbuf[1024];
+			char *pos;
+			char *line;
+
+			count = read(errpipe[READ_END], errbuf, sizeof(errbuf)-1);
+			if (count <= 0) {
+				close(errpipe[READ_END]);
+				errpipe[READ_END] = -1;
+				if (count < 0)
+					err(udev, "stderr read failed: %m\n");
+				continue;
+			}
+			errbuf[count] = '\0';
+			pos = errbuf;
+			while ((line = strsep(&pos, "\n")))
+				if (pos || line[0] != '\0')
+					info(udev, "'%s' (stderr) '%s'\n", command, line);
+		}
+
+		/* get exitstatus */
+		if (statuspipe[READ_END] > 0 && FD_ISSET(statuspipe[READ_END], &readfds)) {
+			count = read(statuspipe[READ_END], &status, sizeof(status));
+
+			if (count <= 0) {
+				if (count < 0)
+					err(udev, "'%s' error reading exit status: %m\n", command);
+				else
+					err(udev, "'%s' EOF on status pipe\n", command);
+				err = -1;
+
+				close(statuspipe[READ_END]);
+				statuspipe[READ_END] = -1;
+				continue;
+			}
+
+			if (WIFEXITED(status))
+				info(udev, "'%s' returned with status %i\n", command, WEXITSTATUS(status));
+			else
+				err(udev, "'%s' abnormal exit\n", command);
+			err = status;
+
+			close(statuspipe[READ_END]);
+			statuspipe[READ_END] = -1;
+		}
+	}
+	if (outpipe[READ_END] > 0)
+		close(outpipe[READ_END]);
+	if (outpipe[READ_END] > 0)
+		close(errpipe[READ_END]);
+	if (statuspipe[READ_END] > 0)
+		close(statuspipe[READ_END]);
+
+	/* return the childs stdout string */
+	if (result) {
+		result[respos] = '\0';
+		dbg(udev, "result='%s'\n", result);
+		if (reslen)
+			*reslen = respos;
+	}
+
+	return err;
+}
diff --git a/udev/udev-rules.c b/udev/udev-rules.c
index afd2e88..736f8ef 100644
--- a/udev/udev-rules.c
+++ b/udev/udev-rules.c
@@ -730,14 +730,11 @@ static int import_file_into_properties(struct udev_device *dev, const char *file
 
 static int import_program_into_properties(struct udev_device *dev, const char *program)
 {
-	struct udev *udev = udev_device_get_udev(dev);
-	char **envp;
 	char result[2048];
 	size_t reslen;
 	char *line;
 
-	envp = udev_device_get_properties_envp(dev);
-	if (util_run_program(udev, program, envp, result, sizeof(result), &reslen) != 0)
+	if (udev_exec(dev, program, result, sizeof(result), &reslen) != 0)
 		return -1;
 
 	line = result;
@@ -2190,19 +2187,17 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
 		case TK_M_PROGRAM:
 			{
 				char program[UTIL_PATH_SIZE];
-				char **envp;
 				char result[UTIL_PATH_SIZE];
 
 				free(event->program_result);
 				event->program_result = NULL;
 				util_strlcpy(program, &rules->buf[cur->key.value_off], sizeof(program));
 				udev_event_apply_format(event, program, sizeof(program));
-				envp = udev_device_get_properties_envp(event->dev);
 				info(event->udev, "PROGRAM '%s' %s:%u\n",
 				     program,
 				     &rules->buf[rule->rule.filename_off],
 				     rule->rule.filename_line);
-				if (util_run_program(event->udev, program, envp, result, sizeof(result), NULL) != 0) {
+				if (udev_exec(event->dev, program, result, sizeof(result), NULL) != 0) {
 					if (cur->key.op != OP_NOMATCH)
 						goto nomatch;
 				} else {
diff --git a/udev/udev-util.c b/udev/udev-util.c
index 3d5eb76..be7ebfd 100644
--- a/udev/udev-util.c
+++ b/udev/udev-util.c
@@ -25,7 +25,6 @@
 #include <ctype.h>
 #include <pwd.h>
 #include <grp.h>
-#include <sys/wait.h>
 
 #include "udev.h"
 
@@ -238,215 +237,3 @@ int util_resolve_subsys_kernel(struct udev *udev, const char *string,
 	udev_device_unref(dev);
 	return 0;
 }
-
-int util_run_program(struct udev *udev, const char *command, char **envp,
-		     char *result, size_t ressize, size_t *reslen)
-{
-	int status;
-	int outpipe[2] = {-1, -1};
-	int errpipe[2] = {-1, -1};
-	pid_t pid;
-	char arg[UTIL_PATH_SIZE];
-	char program[UTIL_PATH_SIZE];
-	char *argv[(sizeof(arg) / 2) + 1];
-	int devnull;
-	int i;
-	int err = 0;
-
-	/* build argv from command */
-	util_strlcpy(arg, command, sizeof(arg));
-	i = 0;
-	if (strchr(arg, ' ') != NULL) {
-		char *pos = arg;
-
-		while (pos != NULL && pos[0] != '\0') {
-			if (pos[0] == '\'') {
-				/* do not separate quotes */
-				pos++;
-				argv[i] = strsep(&pos, "\'");
-				while (pos != NULL && pos[0] == ' ')
-					pos++;
-			} else {
-				argv[i] = strsep(&pos, " ");
-			}
-			dbg(udev, "arg[%i] '%s'\n", i, argv[i]);
-			i++;
-		}
-		argv[i] = NULL;
-	} else {
-		argv[0] = arg;
-		argv[1] = NULL;
-	}
-	info(udev, "'%s'\n", command);
-
-	/* prepare pipes from child to parent */
-	if (result != NULL || udev_get_log_priority(udev) >= LOG_INFO) {
-		if (pipe(outpipe) != 0) {
-			err(udev, "pipe failed: %m\n");
-			return -1;
-		}
-	}
-	if (udev_get_log_priority(udev) >= LOG_INFO) {
-		if (pipe(errpipe) != 0) {
-			err(udev, "pipe failed: %m\n");
-			return -1;
-		}
-	}
-
-	/* allow programs in /lib/udev/ to be called without the path */
-	if (strchr(argv[0], '/') == NULL) {
-		util_strlcpy(program, UDEV_PREFIX "/lib/udev/", sizeof(program));
-		util_strlcat(program, argv[0], sizeof(program));
-		argv[0] = program;
-	}
-
-	pid = fork();
-	switch(pid) {
-	case 0:
-		/* child closes parent ends of pipes */
-		if (outpipe[READ_END] > 0)
-			close(outpipe[READ_END]);
-		if (errpipe[READ_END] > 0)
-			close(errpipe[READ_END]);
-
-		/* discard child output or connect to pipe */
-		devnull = open("/dev/null", O_RDWR);
-		if (devnull > 0) {
-			dup2(devnull, STDIN_FILENO);
-			if (outpipe[WRITE_END] < 0)
-				dup2(devnull, STDOUT_FILENO);
-			if (errpipe[WRITE_END] < 0)
-				dup2(devnull, STDERR_FILENO);
-			close(devnull);
-		} else
-			err(udev, "open /dev/null failed: %m\n");
-		if (outpipe[WRITE_END] > 0) {
-			dup2(outpipe[WRITE_END], STDOUT_FILENO);
-			close(outpipe[WRITE_END]);
-		}
-		if (errpipe[WRITE_END] > 0) {
-			dup2(errpipe[WRITE_END], STDERR_FILENO);
-			close(errpipe[WRITE_END]);
-		}
-		execve(argv[0], argv, envp);
-		if (errno == ENOENT || errno == ENOTDIR) {
-			/* may be on a filesytem which is not mounted right now */
-			info(udev, "program '%s' not found\n", argv[0]);
-		} else {
-			/* other problems */
-			err(udev, "exec of program '%s' failed\n", argv[0]);
-		}
-		_exit(1);
-	case -1:
-		err(udev, "fork of '%s' failed: %m\n", argv[0]);
-		return -1;
-	default:
-		/* read from child if requested */
-		if (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
-			ssize_t count;
-			size_t respos = 0;
-
-			/* parent closes child ends of pipes */
-			if (outpipe[WRITE_END] > 0)
-				close(outpipe[WRITE_END]);
-			if (errpipe[WRITE_END] > 0)
-				close(errpipe[WRITE_END]);
-
-			/* read child output */
-			while (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
-				int fdcount;
-				fd_set readfds;
-
-				FD_ZERO(&readfds);
-				if (outpipe[READ_END] > 0)
-					FD_SET(outpipe[READ_END], &readfds);
-				if (errpipe[READ_END] > 0)
-					FD_SET(errpipe[READ_END], &readfds);
-				fdcount = select(UDEV_MAX(outpipe[READ_END], errpipe[READ_END])+1, &readfds, NULL, NULL, NULL);
-				if (fdcount < 0) {
-					if (errno == EINTR)
-						continue;
-					err = -1;
-					break;
-				}
-
-				/* get stdout */
-				if (outpipe[READ_END] > 0 && FD_ISSET(outpipe[READ_END], &readfds)) {
-					char inbuf[1024];
-					char *pos;
-					char *line;
-
-					count = read(outpipe[READ_END], inbuf, sizeof(inbuf)-1);
-					if (count <= 0) {
-						close(outpipe[READ_END]);
-						outpipe[READ_END] = -1;
-						if (count < 0) {
-							err(udev, "stdin read failed: %m\n");
-							err = -1;
-						}
-						continue;
-					}
-					inbuf[count] = '\0';
-
-					/* store result for rule processing */
-					if (result) {
-						if (respos + count < ressize) {
-							memcpy(&result[respos], inbuf, count);
-							respos += count;
-						} else {
-							err(udev, "ressize %ld too short\n", (long)ressize);
-							err = -1;
-						}
-					}
-					pos = inbuf;
-					while ((line = strsep(&pos, "\n")))
-						if (pos || line[0] != '\0')
-							info(udev, "'%s' (stdout) '%s'\n", argv[0], line);
-				}
-
-				/* get stderr */
-				if (errpipe[READ_END] > 0 && FD_ISSET(errpipe[READ_END], &readfds)) {
-					char errbuf[1024];
-					char *pos;
-					char *line;
-
-					count = read(errpipe[READ_END], errbuf, sizeof(errbuf)-1);
-					if (count <= 0) {
-						close(errpipe[READ_END]);
-						errpipe[READ_END] = -1;
-						if (count < 0)
-							err(udev, "stderr read failed: %m\n");
-						continue;
-					}
-					errbuf[count] = '\0';
-					pos = errbuf;
-					while ((line = strsep(&pos, "\n")))
-						if (pos || line[0] != '\0')
-							info(udev, "'%s' (stderr) '%s'\n", argv[0], line);
-				}
-			}
-			if (outpipe[READ_END] > 0)
-				close(outpipe[READ_END]);
-			if (errpipe[READ_END] > 0)
-				close(errpipe[READ_END]);
-
-			/* return the childs stdout string */
-			if (result) {
-				result[respos] = '\0';
-				dbg(udev, "result='%s'\n", result);
-				if (reslen)
-					*reslen = respos;
-			}
-		}
-		waitpid(pid, &status, 0);
-		if (WIFEXITED(status)) {
-			info(udev, "'%s' returned with status %i\n", argv[0], WEXITSTATUS(status));
-			if (WEXITSTATUS(status) != 0)
-				err = -1;
-		} else {
-			err(udev, "'%s' abnormal exit\n", command);
-			err = -1;
-		}
-	}
-	return err;
-}
diff --git a/udev/udev.h b/udev/udev.h
index ea8c8d9..0bd6ca1 100644
--- a/udev/udev.h
+++ b/udev/udev.h
@@ -35,6 +35,9 @@
 #define READ_END				0
 #define WRITE_END				1
 
+#define UDEVD_PRIORITY			-4 /* nice level for daemon */
+#define UDEV_PRIORITY			-2 /* nice level for programs run by daemon*/
+
 static inline void logging_init(const char *program_name)
 {
 	openlog(program_name, LOG_PID | LOG_CONS, LOG_DAEMON);
@@ -100,14 +103,18 @@ extern int udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t
 extern int udev_node_remove(struct udev_device *dev, int test);
 extern void udev_node_update_old_links(struct udev_device *dev, struct udev_device *dev_old, int test);
 
+/* udev-exec.c */
+extern int udev_exec_init(struct udev *udev);
+extern void udev_exec_cleanup(struct udev *udev);
+extern int udev_exec(struct udev_device *dev, const char *command,
+		     char *result, size_t ressize, size_t *reslen);
+
 /* udev-util.c */
 extern int util_create_path(struct udev *udev, const char *path);
 extern int util_delete_path(struct udev *udev, const char *path);
 extern int util_unlink_secure(struct udev *udev, const char *filename);
 extern uid_t util_lookup_user(struct udev *udev, const char *user);
 extern gid_t util_lookup_group(struct udev *udev, const char *group);
-extern int util_run_program(struct udev *udev, const char *command, char **envp,
-			    char *result, size_t ressize, size_t *reslen);
 extern int util_resolve_subsys_kernel(struct udev *udev, const char *string,
 				      char *result, size_t maxsize, int read_value);
 
diff --git a/udev/udevd.c b/udev/udevd.c
index bfd7323..d57006b 100644
--- a/udev/udevd.c
+++ b/udev/udevd.c
@@ -214,16 +214,9 @@ static void event_fork(struct udev_event *event)
 		sigaction(SIGCHLD, &act, NULL);
 		sigaction(SIGHUP, &act, NULL);
 
-		/* set timeout to prevent hanging processes */
-		alarm(UDEV_EVENT_TIMEOUT);
-
 		/* apply rules, create node, symlinks */
 		err = udev_event_execute_rules(event, rules);
 
-		/* rules may change/disable the timeout */
-		if (udev_device_get_event_timeout(event->dev) >= 0)
-			alarm(udev_device_get_event_timeout(event->dev));
-
 		/* execute RUN= */
 		if (err == 0 && !event->ignore_device && udev_get_run(event->udev))
 			udev_event_execute_run(event);
@@ -706,6 +699,13 @@ int main(int argc, char *argv[])
 	if (write(STDERR_FILENO, 0, 0) < 0)
 		dup2(fd, STDERR_FILENO);
 
+	chdir("/");
+	umask(022);
+	setsid();
+
+	/* fork exec daemon before opening more fds or changing signal handling */
+	udev_exec_init(udev);
+
 	/* init control socket, bind() ensures, that only one udevd instance is running */
 	udev_ctrl = udev_ctrl_new_from_socket(udev, UDEV_CTRL_SOCK_PATH);
 	if (udev_ctrl == NULL) {
@@ -799,10 +799,6 @@ int main(int argc, char *argv[])
 	/* set scheduling priority for the daemon */
 	setpriority(PRIO_PROCESS, 0, UDEVD_PRIORITY);
 
-	chdir("/");
-	umask(022);
-	setsid();
-
 	/* OOM_DISABLE == -17 */
 	fd = open("/proc/self/oom_adj", O_RDWR);
 	if (fd < 0)
@@ -986,6 +982,7 @@ exit:
 	if (inotify_fd >= 0)
 		close(inotify_fd);
 	udev_monitor_unref(kernel_monitor);
+	udev_exec_cleanup(udev);
 	udev_selinux_exit(udev);
 	udev_unref(udev);
 	logging_close();
diff --git a/udev/udev-exec.c b/udev/udev-exec.c
index ae385bf..6ad4fc1 100644
--- a/udev/udev-exec.c
+++ b/udev/udev-exec.c
@@ -153,7 +153,9 @@ static int exec_msg(struct udev *udev, int sock, struct child **childp)
 	if (child == NULL)
 		return 0;
 
-	pid = fork();
+	/* CAUTION.  Child may (or may not) share memory - including stack.
+	   Child should do the minimum necessary before exec() */
+	pid = vfork();
 	switch(pid) {
 	case 0:
 		close(sock);
diff --git a/acx_pthread.m4 b/acx_pthread.m4
new file mode 100644
index 0000000..eb09f5a
--- /dev/null
+++ b/acx_pthread.m4
@@ -0,0 +1,275 @@
+# ===========================================================================
+#              http://autoconf-archive.cryp.to/acx_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   ACX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+#   This macro figures out how to build C programs using POSIX threads. It
+#   sets the PTHREAD_LIBS output variable to the threads library and linker
+#   flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+#   flags that are needed. (The user can also force certain compiler
+#   flags/libs to be tested by setting these environment variables.)
+#
+#   Also sets PTHREAD_CC to any special C compiler that is needed for
+#   multi-threaded programs (defaults to the value of CC otherwise). (This
+#   is necessary on AIX to use the special cc_r compiler alias.)
+#
+#   NOTE: You are assumed to not only compile your program with these flags,
+#   but also link it with them as well. e.g. you should link with
+#   $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+#   If you are only building threads programs, you may wish to use these
+#   variables in your default LIBS, CFLAGS, and CC:
+#
+#          LIBS="$PTHREAD_LIBS $LIBS"
+#          CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+#          CC="$PTHREAD_CC"
+#
+#   In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+#   has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name
+#   (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+#   ACTION-IF-FOUND is a list of shell commands to run if a threads library
+#   is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+#   is not found. If ACTION-IF-FOUND is not specified, the default action
+#   will define HAVE_PTHREAD.
+#
+#   Please let the authors know if this macro fails on any platform, or if
+#   you have any other suggestions or comments. This macro was based on work
+#   by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+#   from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+#   Alejandro Forero Cuervo to the autoconf macro repository. We are also
+#   grateful for the helpful feedback of numerous users.
+#
+# LAST MODIFICATION
+#
+#   2008-04-12
+#
+# COPYLEFT
+#
+#   Copyright (c) 2008 Steven G. Johnson <stevenj@xxxxxxxxxxxx>
+#
+#   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 3 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/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Macro Archive. When you make and
+#   distribute a modified version of the Autoconf Macro, you may extend this
+#   special exception to the GPL to apply to your modified version as well.
+
+AC_DEFUN([ACX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_LANG_SAVE
+AC_LANG_C
+acx_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on True64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then
+        save_CFLAGS="$CFLAGS"
+        CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+        save_LIBS="$LIBS"
+        LIBS="$PTHREAD_LIBS $LIBS"
+        AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])
+        AC_TRY_LINK_FUNC(pthread_join, acx_pthread_ok=yes)
+        AC_MSG_RESULT($acx_pthread_ok)
+        if test x"$acx_pthread_ok" = xno; then
+                PTHREAD_LIBS=""
+                PTHREAD_CFLAGS=""
+        fi
+        LIBS="$save_LIBS"
+        CFLAGS="$save_CFLAGS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try.  Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+acx_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important.  Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+#       other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)
+# -pthreads: Solaris/gcc
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+#      doesn't hurt to check since this sometimes defines pthreads too;
+#      also defines -D_REENTRANT)
+#      ... -mt is also the pthreads flag for HP/aCC
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case "${host_cpu}-${host_os}" in
+        *solaris*)
+
+        # On Solaris (at least, for some versions), libc contains stubbed
+        # (non-functional) versions of the pthreads routines, so link-based
+        # tests will erroneously succeed.  (We need to link with -pthreads/-mt/
+        # -lpthread.)  (The stubs are missing pthread_cleanup_push, or rather
+        # a function called by this macro, so we could check for that, but
+        # who knows whether they'll stub that too in a future libc.)  So,
+        # we'll just look for -pthreads and -lpthread first:
+
+        acx_pthread_flags="-pthreads pthread -mt -pthread $acx_pthread_flags"
+        ;;
+esac
+
+if test x"$acx_pthread_ok" = xno; then
+for flag in $acx_pthread_flags; do
+
+        case $flag in
+                none)
+                AC_MSG_CHECKING([whether pthreads work without any flags])
+                ;;
+
+                -*)
+                AC_MSG_CHECKING([whether pthreads work with $flag])
+                PTHREAD_CFLAGS="$flag"
+                ;;
+
+		pthread-config)
+		AC_CHECK_PROG(acx_pthread_config, pthread-config, yes, no)
+		if test x"$acx_pthread_config" = xno; then continue; fi
+		PTHREAD_CFLAGS="`pthread-config --cflags`"
+		PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+		;;
+
+                *)
+                AC_MSG_CHECKING([for the pthreads library -l$flag])
+                PTHREAD_LIBS="-l$flag"
+                ;;
+        esac
+
+        save_LIBS="$LIBS"
+        save_CFLAGS="$CFLAGS"
+        LIBS="$PTHREAD_LIBS $LIBS"
+        CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+        # Check for various functions.  We must include pthread.h,
+        # since some functions may be macros.  (On the Sequent, we
+        # need a special flag -Kthread to make this header compile.)
+        # We check for pthread_join because it is in -lpthread on IRIX
+        # while pthread_create is in libc.  We check for pthread_attr_init
+        # due to DEC craziness with -lpthreads.  We check for
+        # pthread_cleanup_push because it is one of the few pthread
+        # functions on Solaris that doesn't have a non-functional libc stub.
+        # We try pthread_create on general principles.
+        AC_TRY_LINK([#include <pthread.h>],
+                    [pthread_t th; pthread_join(th, 0);
+                     pthread_attr_init(0); pthread_cleanup_push(0, 0);
+                     pthread_create(0,0,0,0); pthread_cleanup_pop(0); ],
+                    [acx_pthread_ok=yes])
+
+        LIBS="$save_LIBS"
+        CFLAGS="$save_CFLAGS"
+
+        AC_MSG_RESULT($acx_pthread_ok)
+        if test "x$acx_pthread_ok" = xyes; then
+                break;
+        fi
+
+        PTHREAD_LIBS=""
+        PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$acx_pthread_ok" = xyes; then
+        save_LIBS="$LIBS"
+        LIBS="$PTHREAD_LIBS $LIBS"
+        save_CFLAGS="$CFLAGS"
+        CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+
+        # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+	AC_MSG_CHECKING([for joinable pthread attribute])
+	attr_name=unknown
+	for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+	    AC_TRY_LINK([#include <pthread.h>], [int attr=$attr; return attr;],
+                        [attr_name=$attr; break])
+	done
+        AC_MSG_RESULT($attr_name)
+        if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then
+            AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name,
+                               [Define to necessary symbol if this constant
+                                uses a non-standard name on your system.])
+        fi
+
+        AC_MSG_CHECKING([if more special flags are required for pthreads])
+        flag=no
+        case "${host_cpu}-${host_os}" in
+            *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";;
+            *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";;
+        esac
+        AC_MSG_RESULT(${flag})
+        if test "x$flag" != xno; then
+            PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS"
+        fi
+
+        LIBS="$save_LIBS"
+        CFLAGS="$save_CFLAGS"
+
+        # More AIX lossage: must compile with xlc_r or cc_r
+	if test x"$GCC" != xyes; then
+          AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC})
+        else
+          PTHREAD_CC=$CC
+	fi
+else
+        PTHREAD_CC="$CC"
+fi
+
+AC_SUBST(PTHREAD_LIBS)
+AC_SUBST(PTHREAD_CFLAGS)
+AC_SUBST(PTHREAD_CC)
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test x"$acx_pthread_ok" = xyes; then
+        ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1])
+        :
+else
+        acx_pthread_ok=no
+        $2
+fi
+AC_LANG_RESTORE
+])dnl ACX_PTHREAD
diff --git a/configure.ac b/configure.ac
index 6e98f37..7f1c901 100644
--- a/configure.ac
+++ b/configure.ac
@@ -9,6 +9,8 @@ AC_DISABLE_STATIC
 AC_SYS_LARGEFILE
 AC_PROG_LIBTOOL
 
+sinclude(acx_pthread.m4)
+
 dnl /* libudev version */
 LIBUDEV_LT_CURRENT=0
 LIBUDEV_LT_REVISION=3
@@ -25,6 +27,8 @@ AC_SUBST(VOLID_LT_CURRENT)
 AC_SUBST(VOLID_LT_REVISION)
 AC_SUBST(VOLID_LT_AGE)
 
+ACX_PTHREAD(AC_DEFINE(HAVE_PTHREAD), AC_MSG_FAILURE([pthreads unavailable]))
+
 AC_PATH_PROG([XSLTPROC], [xsltproc])
 
 AC_CHECK_LIB(c, inotify_init,
diff --git a/udev/Makefile.am b/udev/Makefile.am
index 2b35bd9..9368e9b 100644
--- a/udev/Makefile.am
+++ b/udev/Makefile.am
@@ -1,5 +1,9 @@
 include $(top_srcdir)/Makefile.am.inc
 
+LIBS += $(PTHREAD_LIBS)
+CFLAGS += $(PTHREAD_CFLAGS)
+CC=$(PTHREAD_CC)
+
 SUBDIRS = \
 	lib
 
diff --git a/udev/test-udev.c b/udev/test-udev.c
index a26f308..ce7e3af 100644
--- a/udev/test-udev.c
+++ b/udev/test-udev.c
@@ -107,6 +107,7 @@ int main(int argc, char *argv[])
 fail:
 	udev_rules_unref(rules);
 exit:
+	udev_exec_cleanup(udev);
 	udev_selinux_exit(udev);
 	udev_unref(udev);
 	if (err != 0)
diff --git a/udev/udev.h b/udev/udev.h
index 0bd6ca1..887f92d 100644
--- a/udev/udev.h
+++ b/udev/udev.h
@@ -67,7 +67,7 @@ struct udev_event {
 	uid_t uid;
 	gid_t gid;
 	struct udev_list_node run_list;
-	pid_t pid;
+	pthread_t thread;
 	int exitstatus;
 	time_t queue_time;
 	unsigned long long int delaying_seqnum;
@@ -80,6 +80,7 @@ struct udev_event {
 	unsigned int ignore_device:1;
 	unsigned int trace:1;
 	unsigned int test:1;
+	unsigned int running:1;
 };
 
 /* udev-rules.c */
diff --git a/udev/udevd.c b/udev/udevd.c
index d57006b..785e4c5 100644
--- a/udev/udevd.c
+++ b/udev/udevd.c
@@ -29,18 +29,17 @@
 #include <getopt.h>
 #include <dirent.h>
 #include <sys/select.h>
-#include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/resource.h>
 #ifdef HAVE_INOTIFY
 #include <sys/inotify.h>
 #endif
+#include <pthread.h>
 
 #include "udev.h"
 
-#define UDEVD_PRIORITY			-4
-#define UDEV_PRIORITY			-2
-
 /* maximum limit of forked childs */
 #define UDEVD_MAX_CHILDS		256
 
@@ -64,7 +63,7 @@ static struct udev_ctrl *udev_ctrl;
 static struct udev_monitor *kernel_monitor;
 static int inotify_fd = -1;
 static int signal_pipe[2] = {-1, -1};
-static volatile int sigchilds_waiting;
+static int event_done_pipe[2] = {-1, -1};
 static volatile int udev_exit;
 static volatile int reload_config;
 static int run_exec_q;
@@ -73,6 +72,8 @@ static int max_childs;
 static int childs;
 static struct udev_list_node event_list;
 
+pthread_attr_t thread_attr;
+
 enum event_state {
 	EVENT_QUEUED,
 	EVENT_FINISHED,
@@ -167,80 +168,71 @@ static void event_queue_delete(struct udev_event *event)
 	udev_event_unref(event);
 }
 
-static void asmlinkage event_sig_handler(int signum)
+static int event_run(struct udev_event *event)
 {
-	if (signum == SIGALRM)
-		exit(1);
+	int err;
+
+	/* apply rules, create node, symlinks */
+	err = udev_event_execute_rules(event, rules);
+
+	/* execute RUN= */
+	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);
+
+	return err;
 }
 
-static void event_fork(struct udev_event *event)
+static void *event_run_pthread(void *arg)
 {
-	pid_t pid;
-	struct sigaction act;
+	struct udev_event *event = arg;
 	int err;
 
-	if (debug_trace) {
-		event->trace = 1;
-		fprintf(stderr, "fork %s (%llu)\n",
-		       udev_device_get_syspath(event->dev),
-		       udev_device_get_seqnum(event->dev));
-	}
-
-	pid = fork();
-	switch (pid) {
-	case 0:
-		/* child */
-		udev_monitor_unref(kernel_monitor);
-		udev_ctrl_unref(udev_ctrl);
-		if (inotify_fd >= 0)
-			close(inotify_fd);
-		close(signal_pipe[READ_END]);
-		close(signal_pipe[WRITE_END]);
-		logging_close();
-		logging_init("udevd-event");
-		setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY);
-
-		/* set signal handlers */
-		memset(&act, 0x00, sizeof(act));
-		act.sa_handler = (void (*)(int)) event_sig_handler;
-		sigemptyset (&act.sa_mask);
-		act.sa_flags = 0;
-		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);
-
-		/* apply rules, create node, symlinks */
-		err = udev_event_execute_rules(event, rules);
-
-		/* execute RUN= */
-		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);
-		logging_close();
-		if (err != 0)
-			exit(1);
-		exit(0);
-	case -1:
-		err(event->udev, "fork of child failed: %m\n");
+	err = event_run(event);
+
+	/* write to pipe, which will wakeup select() in our mainloop */
+	write(event_done_pipe[WRITE_END], &event, sizeof(struct udev_event *));
+
+	return (void *) (uintptr_t) err;
+}
+
+static void event_thread_create(struct udev_event *event)
+{
+	if (pthread_create(&event->thread, &thread_attr, event_run_pthread, event) != 0) {
+		err(event->udev, "create thread 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;
-		childs++;
+		return;
+	}
+
+	event->running = 1;
+
+	info(event->udev, "seq %llu created, '%s' '%s', %ld seconds old\n",
+	     udev_device_get_seqnum(event->dev),
+	     udev_device_get_action(event->dev),
+	     udev_device_get_subsystem(event->dev),
+	     time(NULL) - event->queue_time);
+}
+
+static int event_thread_join(struct udev_event *event)
+{
+	void *result;
+	int err;
+
+	err = pthread_join(event->thread, &result);
+	if (err) {
+		err(event->udev, "failed to retrieve thread return value: %m\n");
+		return -1;
 	}
+
+	return (int) (uintptr_t) result;
+}
+
+static void event_thread_kill(struct udev_event *event)
+{
+	pthread_kill(event->thread, SIGTERM);
+
+	/* Make sure thread has died before we return */
+	event_thread_join(event);
 }
 
 static void event_queue_insert(struct udev_event *event)
@@ -272,7 +264,7 @@ static void event_queue_insert(struct udev_event *event)
 
 	/* run all events with a timeout set immediately */
 	if (udev_device_get_timeout(event->dev) > 0) {
-		event_fork(event);
+		event_thread_create(event);
 		return;
 	}
 }
@@ -413,7 +405,7 @@ static void event_queue_manager(struct udev *udev)
 			break;
 		}
 
-		if (loop_event->pid != 0)
+		if (loop_event->running)
 			continue;
 
 		/* do not start event if parent or child event is still running */
@@ -424,7 +416,7 @@ static void event_queue_manager(struct udev *udev)
 			continue;
 		}
 
-		event_fork(loop_event);
+		event_thread_create(loop_event);
 		dbg(udev, "moved seq %llu to running list\n", udev_device_get_seqnum(loop_event->dev));
 	}
 }
@@ -502,11 +494,8 @@ static void asmlinkage 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;
+			udev_exit = 1;
 			break;
 		case SIGHUP:
 			reload_config = 1;
@@ -517,50 +506,17 @@ static void asmlinkage sig_handler(int signum)
 	write(signal_pipe[WRITE_END], "", 1);
 }
 
-static void udev_done(int pid, int exitstatus)
+static void udev_done(struct udev_event *event)
 {
-	struct udev_list_node *loop;
+	event->exitstatus = event_thread_join(event);
+	info(event->udev, "seq %llu cleanup, status %i, %ld seconds old\n",
+	     udev_device_get_seqnum(event->dev),
+	     event->exitstatus, time(NULL) - event->queue_time);
 
-	/* 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;
+	event_queue_delete(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);
-	}
+	/* there may be events waiting with the same devpath */
+	run_exec_q = 1;
 }
 
 static void cleanup_queue_dir(struct udev *udev)
@@ -629,6 +585,8 @@ int main(int argc, char *argv[])
 	int err;
 	int fd;
 	struct sigaction act;
+	int pagesize;
+	int stacksize;
 	fd_set readfds;
 	const char *value;
 	int daemonize = 0;
@@ -736,6 +694,11 @@ int main(int argc, char *argv[])
 		err(udev, "error getting pipes: %m\n");
 		goto exit;
 	}
+	err = pipe(event_done_pipe);
+	if (err < 0) {
+		err(udev, "error getting pipes: %m\n");
+		goto exit;
+	}
 
 	err = fcntl(signal_pipe[READ_END], F_GETFL, 0);
 	if (err < 0) {
@@ -799,6 +762,29 @@ int main(int argc, char *argv[])
 	/* set scheduling priority for the daemon */
 	setpriority(PRIO_PROCESS, 0, UDEVD_PRIORITY);
 
+	pthread_attr_init(&thread_attr);
+
+	/* To create many threads on 32-bit, it may be necessary to explicitly limit the stack size.
+
+	   udevd doesn't use crazy recursion or deep call-chains.
+	   But it does allocate quite a few buffers and temporary strings on the stack.
+	   We may also need extra space for exotic NSS modules (user/group lookup, e.g. over LDAP).
+	   Make a generous guess, double it, and double it again!
+
+	   This comes to just over 200Kb.  The default on i386 is 2Mb.
+	 */
+	stacksize = PTHREAD_STACK_MIN;
+	stacksize += UTIL_PATH_SIZE * 8;
+	stacksize += UTIL_LINE_SIZE * 8;
+	stacksize += UTIL_NAME_SIZE * 8;
+	stacksize += 4096 * 8;
+	stacksize *= 4;
+
+	/* Round up to a whole number of pages */
+	pagesize = sysconf(_SC_PAGESIZE);
+	stacksize = ((stacksize / pagesize) + 1) * pagesize;
+	pthread_attr_setstacksize(&thread_attr, stacksize);
+
 	/* OOM_DISABLE == -17 */
 	fd = open("/proc/self/oom_adj", O_RDWR);
 	if (fd < 0)
@@ -883,12 +869,14 @@ int main(int argc, char *argv[])
 	maxfd = udev_ctrl_get_fd(udev_ctrl);
 	maxfd = UDEV_MAX(maxfd, udev_monitor_get_fd(kernel_monitor));
 	maxfd = UDEV_MAX(maxfd, signal_pipe[READ_END]);
+	maxfd = UDEV_MAX(maxfd, event_done_pipe[READ_END]);
 	maxfd = UDEV_MAX(maxfd, inotify_fd);
 	while (!udev_exit) {
 		int fdcount;
 
 		FD_ZERO(&readfds);
 		FD_SET(signal_pipe[READ_END], &readfds);
+		FD_SET(event_done_pipe[READ_END], &readfds);
 		FD_SET(udev_ctrl_get_fd(udev_ctrl), &readfds);
 		FD_SET(udev_monitor_get_fd(kernel_monitor), &readfds);
 		if (inotify_fd >= 0)
@@ -927,6 +915,21 @@ int main(int argc, char *argv[])
 			read(signal_pipe[READ_END], &buf, sizeof(buf));
 		}
 
+		/* processed events */
+		if (FD_ISSET(event_done_pipe[READ_END], &readfds)) {
+			struct udev_event *buf[32];
+			ssize_t count;
+			unsigned i;
+
+			count = read(event_done_pipe[READ_END], &buf, sizeof(buf));
+			if (count < 0) {
+				err(udev, "read error on thread pipe: %m\n");
+			} else {
+				for (i = 0; i < count / sizeof(buf[0]); i++)
+					udev_done(buf[i]);
+			}
+		}
+
 		/* rules directory inotify watch */
 		if ((inotify_fd >= 0) && FD_ISSET(inotify_fd, &readfds)) {
 			int nbytes;
@@ -959,20 +962,28 @@ int main(int argc, char *argv[])
 			}
 		}
 
-		if (sigchilds_waiting) {
-			sigchilds_waiting = 0;
-			reap_sigchilds();
-		}
-
 		if (run_exec_q) {
 			run_exec_q = 0;
 			if (!stop_exec_q)
 				event_queue_manager(udev);
 		}
 	}
+
+	// todo unified queue
+	if (!udev_list_is_empty(&event_list)) {
+		struct udev_list_node *loop;
+
+		udev_list_node_foreach(loop, &event_list) {
+			struct udev_event *loop_event = node_to_event(loop);
+
+			if (loop_event->running)
+				event_thread_kill(loop_event);
+		}
+	}
 	cleanup_queue_dir(udev);
 	rc = 0;
 exit:
+	pthread_attr_destroy(&thread_attr);
 	udev_rules_unref(rules);
 	if (signal_pipe[READ_END] >= 0)
 		close(signal_pipe[READ_END]);

[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