From mboxrd@z Thu Jan 1 00:00:00 1970 From: Alan Jenkins Date: Sat, 27 Dec 2008 19:07:33 +0000 Subject: Re: Revisiting threaded udevd Message-Id: <49567CF5.4070807@tuffmail.co.uk> MIME-Version: 1 Content-Type: multipart/mixed; boundary="------------050607090004090203040301" List-Id: References: <494F7942.6060702@tuffmail.co.uk> In-Reply-To: <494F7942.6060702@tuffmail.co.uk> To: linux-hotplug@vger.kernel.org This is a multi-part message in MIME format. --------------050607090004090203040301 Content-Type: text/plain; charset="windows-1252"; format="flowed" Content-Transfer-Encoding: quoted-printable Scott James Remnant wrote: > On Thu, 2008-12-25 at 17:36 +0000, Alan Jenkins wrote: > =20 > I think I got it right, maybe I missed something that was in your > original patch? > =20 Could be. Either way, I found your problem: udev-rules.c: In function =91udev_rules_apply_to_event=92: udev-rules.c:2201: warning: passing argument 1 of =91udev_exec=92 from=20 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=20 (attached). They're not quite as well tested as before, but they should=20 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. > =20 That's exactly how I test. Thanks Alan --------------050607090004090203040301 Content-Type: text/plain; name="udev-exec-daemon" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="udev-exec-daemon" 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 #include #include -#include #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(); --------------050607090004090203040301 Content-Type: text/plain; name="vfork" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="vfork" 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); --------------050607090004090203040301 Content-Type: text/plain; name="build_pthreads" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="build_pthreads" 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 +# +# 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 . +# +# 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_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 ], [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 --------------050607090004090203040301 Content-Type: text/plain; name="use_pthreads" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="use_pthreads" 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 #include #include -#include #include #include +#include +#include #ifdef HAVE_INOTIFY #include #endif +#include #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]); --------------050607090004090203040301--