From: Roy Marples Date: Sat, 10 Nov 2012 16:38:52 +0000 (+0000) Subject: Replace poll(2) with pselect(2) and vfork(2)+execve(2) with X-Git-Tag: v5.99.3~28 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5c08c0c4cad951fb3727efde4f1e314df68e7d91;p=thirdparty%2Fdhcpcd.git Replace poll(2) with pselect(2) and vfork(2)+execve(2) with posix_spawn(3). Now we block all our signals at startup and allow pselect to unblock them for the duration of the call. This allows us to manage interrupts in a fashion to guarantee a consistent internal state. I have added a posix_spawn compat shim for systems that lack that call. pselect(2) has been supported on target for some time so there is no need for a compat shim there. --- diff --git a/bind.c b/bind.c index 3a00465a..bf267e66 100644 --- a/bind.c +++ b/bind.c @@ -59,16 +59,12 @@ pid_t daemonise(void) { pid_t pid; - sigset_t full; - sigset_t old; char buf = '\0'; int sidpipe[2], fd; delete_timeout(handle_exit_timeout, NULL); if (options & DHCPCD_DAEMONISED || !(options & DHCPCD_DAEMONISE)) return 0; - sigfillset(&full); - sigprocmask(SIG_SETMASK, &full, &old); /* Setup a signal pipe so parent knows when to exit. */ if (pipe(sidpipe) == -1) { syslog(LOG_ERR, "pipe: %m"); @@ -96,7 +92,6 @@ daemonise(void) } break; default: - signal_reset(); /* Wait for child to detach */ close(sidpipe[1]); if (read(sidpipe[0], &buf, 1) == -1) @@ -114,7 +109,6 @@ daemonise(void) exit(EXIT_SUCCESS); } options |= DHCPCD_DAEMONISED; - sigprocmask(SIG_SETMASK, &old, NULL); return pid; } #endif diff --git a/compat/posix_spawn.c b/compat/posix_spawn.c new file mode 100644 index 00000000..eb0685f6 --- /dev/null +++ b/compat/posix_spawn.c @@ -0,0 +1,135 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* This implementation of posix_spawn is only suitable for the needs of dhcpcd + * but it could easily be extended to other applications. + * Also, it does rely on the system being able to modify signals safely within + * the vfork process which is undefined behaviour, but seems sane in testing. */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "../common.h" +#include "posix_spawn.h" + +extern char **environ; + +static int +posix_spawnattr_handle(const posix_spawnattr_t *attrp) +{ + struct sigaction sa; + int i; + + if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGMASK) + sigprocmask(SIG_SETMASK, &attrp->posix_attr_sigmask, NULL); + + if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGDEF) { + sa.sa_flags = 0; + sa.sa_handler = SIG_DFL; + for (i = 1; i <= _SIG_MAXSIG; i++) { + if (sigismember(&attrp->posix_attr_sigdefault, i)) { + if (sigaction(i, &sa, NULL) == -1) + return -1; + } + } + } + + return 0; +} + +int +posix_spawn(pid_t *pid, const char * path, + _unused void *arg, + const posix_spawnattr_t *attrp, + char *const argv[], char *const envp[]) +{ + pid_t p; + volatile int error; + + error = 0; + p = vfork(); + switch (p) { + case -1: + return errno; + case 0: + if (attrp) { + error = posix_spawnattr_handle(attrp); + if (error) + _exit(127); + } + execve(path, argv, envp); + error = errno; + _exit(127); + default: + if (error != 0) + waitpid(p, NULL, WNOHANG); + else if (pid != NULL) + *pid = p; + return error; + } +} + +int +posix_spawnattr_init(posix_spawnattr_t *attr) +{ + + memset(attr, 0, sizeof(*attr)); + attr->posix_attr_flags = 0; + sigprocmask(0, NULL, &attr->posix_attr_sigmask); + sigemptyset(&attr->posix_attr_sigdefault); + return 0; +} + +int +posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) +{ + + attr->posix_attr_flags = flags; + return 0; +} + +int +posix_spawnattr_setsigmask(posix_spawnattr_t *attr, const sigset_t *sigmask) +{ + + attr->posix_attr_sigmask = *sigmask; + return 0; +} + +int +posix_spawnattr_setsigdefault(posix_spawnattr_t *attr, const sigset_t *sigmask) +{ + + attr->posix_attr_sigdefault = *sigmask; + return 0; +} diff --git a/compat/posix_spawn.h b/compat/posix_spawn.h new file mode 100644 index 00000000..653f00a6 --- /dev/null +++ b/compat/posix_spawn.h @@ -0,0 +1,46 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef POSIX_SPAWN_H +#define POSIX_SPAWN_H + +typedef struct { + short posix_attr_flags; +#define POSIX_SPAWN_SETSIGDEF 0x10 +#define POSIX_SPAWN_SETSIGMASK 0x20 + sigset_t posix_attr_sigmask; + sigset_t posix_attr_sigdefault; +} posix_spawnattr_t; + +int posix_spawn(pid_t *, const char *, void *, const posix_spawnattr_t *, + char *const [], char *const []); +int posix_spawnattr_init(posix_spawnattr_t *); +int posix_spawnattr_setflags(posix_spawnattr_t *, short); +int posix_spawnattr_setsigmask(posix_spawnattr_t *, const sigset_t *); +int posix_spawnattr_setsigdefault(posix_spawnattr_t *, const sigset_t *); + +#endif diff --git a/configure b/configure index bca96cfc..233f23c1 100755 --- a/configure +++ b/configure @@ -412,6 +412,31 @@ if [ "$TAILQ_FOREACH_SAFE" = no ]; then EOF fi +if [ -z "$POSIX_SPAWN" ]; then + printf "Testing for posix_spawn ... " + cat <_posix_spawn.c +#include +#include +int main(void) { + posix_spawn(NULL, NULL, NULL, NULL, NULL, NULL); + return 0; +} +EOF + if $XCC _posix_spawn.c -o _posix_spawn 2>/dev/null; then + POSIX_SPAWN=yes + else + POSIX_SPAWN=no + fi + echo "$POSIX_SPAWN" + rm -f _posix_spawn.c _posix_spawn +fi +if [ "$POSIX_SPAWN" = no ]; then + echo "COMPAT_SRCS+= compat/posix_spawn.c" >>$CONFIG_MK + echo "#include \"compat/posix_spawn.h\"" >>$CONFIG_H +else + echo "#include " >>$CONFIG_H +fi + if [ -z "$SERVICECMD" ]; then printf "Checking for OpenRC ... " if [ -x /sbin/rc-service ]; then diff --git a/configure.c b/configure.c index f18e87bb..bbceaaae 100644 --- a/configure.c +++ b/configure.c @@ -35,6 +35,8 @@ #include #include #include +/* We can't include spawn.h here because it may not exist. + * config.h will pull it in, or our compat one. */ #include #include #include @@ -81,29 +83,25 @@ static int exec_script(char *const *argv, char *const *env) { pid_t pid; - sigset_t full; - sigset_t old; - - /* OK, we need to block signals */ - sigfillset(&full); - sigprocmask(SIG_SETMASK, &full, &old); - signal_reset(); - - switch (pid = vfork()) { - case -1: - syslog(LOG_ERR, "vfork: %m"); - break; - case 0: - sigprocmask(SIG_SETMASK, &old, NULL); - execve(argv[0], argv, env); - syslog(LOG_ERR, "%s: %m", argv[0]); - _exit(127); - /* NOTREACHED */ - } + posix_spawnattr_t attr; + sigset_t defsigs; + int i; - /* Restore our signals */ - signal_setup(); - sigprocmask(SIG_SETMASK, &old, NULL); + /* posix_spawn is a safe way of executing another image + * and changing signals back to how they should be. */ + if (posix_spawnattr_init(&attr) == -1) + return -1; + posix_spawnattr_setflags(&attr, + POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF); + sigemptyset(&defsigs); + for (i = 0; i < handle_sigs[i]; i++) + sigaddset(&defsigs, handle_sigs[i]); + posix_spawnattr_setsigdefault(&attr, &defsigs); + posix_spawnattr_setsigmask(&attr, &dhcpcd_sigset); + errno = 0; + i = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); + if (i) + return -1; return pid; } @@ -423,9 +421,10 @@ run_script_reason(const struct interface *iface, const char *reason) env[++elen] = '\0'; pid = exec_script(argv, env); - if (pid == -1) + if (pid == -1) { + syslog(LOG_ERR, "exec_script: %m"); status = -1; - else if (pid != 0) { + } else if (pid != 0) { /* Wait for the script to finish */ while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { diff --git a/dhcpcd.c b/dhcpcd.c index a75545c5..7bee966b 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -92,15 +92,17 @@ char **ifav = NULL; int ifdc = 0; char **ifdv = NULL; -static char **margv; -static int margc; -static struct if_options *if_options; -static char **ifv; -static int ifc; +sigset_t dhcpcd_sigset; + static char *cffile; +static struct if_options *if_options; static char *pidfile; static int linkfd = -1, ipv6rsfd = -1, ipv6nsfd = -1; static uint8_t *packet; +static char **ifv; +static int ifc; +static char **margv; +static int margc; struct dhcp_op { uint8_t value; @@ -1538,13 +1540,11 @@ reconf_reboot(int action, int argc, char **argv, int oi) sort_interfaces(); } -/* ARGSUSED */ -static void -handle_signal(_unused void *arg) +void +handle_signal(int sig) { struct interface *ifp; struct if_options *ifo; - int sig = signal_read(); int do_release, i; do_release = 0; @@ -1809,7 +1809,7 @@ main(int argc, char **argv) { struct interface *iface; uint16_t family = 0; - int opt, oi = 0, signal_fd, sig = 0, i, control_fd; + int opt, oi = 0, sig = 0, i, control_fd; size_t len; pid_t pid; struct timespec ts; @@ -2059,11 +2059,15 @@ main(int argc, char **argv) eloop_init(); #endif - if ((signal_fd = signal_init()) == -1) + /* This blocks all signals we're interested in. + * eloop uses pselect(2) so that the signals are unblocked + * when we're testing fd's. + * This allows us to ensure a consistent state is maintained + * regardless of when we are interrupted .*/ + if (signal_setup(handle_signal, &dhcpcd_sigset) == -1) { + syslog(LOG_ERR, "signal_setup: %m"); exit(EXIT_FAILURE); - if (signal_setup() == -1) - exit(EXIT_FAILURE); - add_event(signal_fd, handle_signal, NULL); + } if (options & DHCPCD_MASTER) { if (start_control() == -1) @@ -2196,6 +2200,6 @@ main(int argc, char **argv) for (iface = ifaces; iface; iface = iface->next) add_timeout_sec(0, start_interface, iface); - start_eloop(); + start_eloop(&dhcpcd_sigset); exit(EXIT_SUCCESS); } diff --git a/dhcpcd.h b/dhcpcd.h index a04b7a81..bfcba8c4 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -120,6 +120,7 @@ struct interface { }; extern char vendor[VENDORCLASSID_MAX_LEN]; +extern sigset_t dhcpcd_sigset; extern int pidfd; extern int ifac; extern char **ifav; @@ -135,6 +136,7 @@ void handle_hwaddr(const char *, unsigned char *, size_t); void handle_ifa(int, const char *, struct in_addr *, struct in_addr *, struct in_addr *); void handle_exit_timeout(void *); +void handle_signal(int); void start_interface(void *); void start_discover(void *); void start_request(void *); diff --git a/eloop.c b/eloop.c index 51fe0ac4..457e0d7e 100644 --- a/eloop.c +++ b/eloop.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2010 Roy Marples + * Copyright (c) 2006-2012 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -30,11 +30,13 @@ #include #include #include +#include #include #include #include #include "common.h" +#include "dhcpcd.h" #include "eloop.h" static struct timeval now; @@ -56,9 +58,6 @@ static struct timeout { } *timeouts; static struct timeout *free_timeouts; -static struct pollfd *fds; -static size_t fds_len; - void add_event(int fd, void (*callback)(void *), void *arg) { @@ -274,7 +273,6 @@ cleanup(void) free(free_timeouts); free_timeouts = t; } - free(fds); } void @@ -286,18 +284,20 @@ eloop_init(void) #endif _noreturn void -start_eloop(void) +start_eloop(const sigset_t *cursigs) { - int msecs, n; - nfds_t nfds, i; + int n, max_fd; + fd_set read_fds, error_fds; struct event *e; struct timeout *t; struct timeval tv; + struct timespec ts; + const struct timespec *tsp; for (;;) { - /* Run all timeouts first. - * When we have one that has not yet occured, - * calculate milliseconds until it does for use in poll. */ + get_monotonic(&now); + + /* Run all timeouts first */ if (timeouts) { if (timercmp(&now, &timeouts->when, >)) { t = timeouts; @@ -308,61 +308,40 @@ start_eloop(void) continue; } timersub(&timeouts->when, &now, &tv); - if (tv.tv_sec > INT_MAX / 1000 || - (tv.tv_sec == INT_MAX / 1000 && - (tv.tv_usec + 999) / 1000 > INT_MAX % 1000)) - msecs = INT_MAX; - else - msecs = tv.tv_sec * 1000 + - (tv.tv_usec + 999) / 1000; + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + tsp = &ts; } else - /* No timeouts, so wait forever. */ - msecs = -1; - - /* Allocate memory for our pollfds as and when needed. - * We don't bother shrinking it. */ - nfds = 0; - for (e = events; e; e = e->next) - nfds++; - if (msecs == -1 && nfds == 0) { + /* No timeouts, so wait forever */ + tsp = NULL; + + max_fd = -1; + FD_ZERO(&read_fds); + FD_ZERO(&error_fds); + for (e = events; e; e = e->next) { + FD_SET(e->fd, &read_fds); + if (e->fd > max_fd) + max_fd = e->fd; + } + if (tsp == NULL && max_fd == -1) { syslog(LOG_ERR, "nothing to do"); exit(EXIT_FAILURE); } - if (nfds > fds_len) { - free(fds); - /* Allocate 5 more than we need for future use */ - fds_len = nfds + 5; - fds = xmalloc(sizeof(*fds) * fds_len); - } - nfds = 0; - for (e = events; e; e = e->next) { - fds[nfds].fd = e->fd; - fds[nfds].events = POLLIN; - fds[nfds].revents = 0; - nfds++; - } - n = poll(fds, nfds, msecs); + + n = pselect(max_fd + 1, &read_fds, NULL, &error_fds, + tsp, cursigs); if (n == -1) { - if (errno == EAGAIN || errno == EINTR) { - get_monotonic(&now); + if (errno == EINTR) continue; - } - syslog(LOG_ERR, "poll: %m"); + syslog(LOG_ERR, "pselect: %m"); exit(EXIT_FAILURE); } - - /* Get the now time and process any triggered events. */ - get_monotonic(&now); - if (n == 0) - continue; - for (i = 0; i < nfds; i++) { - if (!(fds[i].revents & (POLLIN | POLLHUP))) - continue; + + /* Process any triggered events. */ + if (n) { for (e = events; e; e = e->next) { - if (e->fd == fds[i].fd) { + if (FD_ISSET(e->fd, &read_fds)) e->callback(e->arg); - break; - } } } } diff --git a/eloop.h b/eloop.h index 23c3626c..df06dce6 100644 --- a/eloop.h +++ b/eloop.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2010 Roy Marples + * Copyright (c) 2006-2012 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,7 @@ #ifndef ELOOP_H #define ELOOP_H +#include #include #ifndef ELOOP_QUEUE @@ -47,6 +48,6 @@ void add_q_timeout_tv(int queue, const struct timeval *, void (*)(void *), void delete_q_timeout(int, void (*)(void *), void *); void delete_q_timeouts(int, void *, void (*)(void *), ...); void eloop_init(void); -void start_eloop(void); +void start_eloop(const sigset_t *); #endif diff --git a/signals.c b/signals.c index fd3b0c3c..e1ce69cb 100644 --- a/signals.c +++ b/signals.c @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2009 Roy Marples + * Copyright (c) 2006-2012 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,9 +25,6 @@ * SUCH DAMAGE. */ -#include -#include - #include #include #include @@ -37,88 +34,48 @@ #include "common.h" #include "signals.h" -static int signal_pipe[2]; - -static const int handle_sigs[] = { +const int handle_sigs[] = { SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGTERM, SIGUSR1, + 0 }; -static void -signal_handler(int sig) -{ - int serrno = errno; - - if (write(signal_pipe[1], &sig, sizeof(sig)) != sizeof(sig)) - syslog(LOG_ERR, "failed to write signal %d: %m", sig); - /* Restore errno */ - errno = serrno; -} - -/* Read a signal from the signal pipe. Returns 0 if there is - * no signal, -1 on error (and sets errno appropriately), and - * your signal on success */ -int -signal_read(void) -{ - int sig = -1; - char buf[16]; - ssize_t bytes; - - memset(buf, 0, sizeof(buf)); - bytes = read(signal_pipe[0], buf, sizeof(buf)); - if (bytes >= 0 && (size_t)bytes >= sizeof(sig)) - memcpy(&sig, buf, sizeof(sig)); - return sig; -} - -/* Call this before doing anything else. Sets up the socket pair - * and installs the signal handler */ -int -signal_init(void) -{ - if (pipe(signal_pipe) == -1) - return -1; - /* Don't block on read */ - if (set_nonblock(signal_pipe[0]) == -1) - return -1; - /* Stop any scripts from inheriting us */ - if (set_cloexec(signal_pipe[0]) == -1) - return -1; - if (set_cloexec(signal_pipe[1]) == -1) - return -1; - return signal_pipe[0]; -} - static int -signal_handle(void (*func)(int)) +signal_handle(void (*func)(int), sigset_t *oldset) { unsigned int i; struct sigaction sa; + sigset_t newset; memset(&sa, 0, sizeof(sa)); sa.sa_handler = func; sigemptyset(&sa.sa_mask); - for (i = 0; i < sizeof(handle_sigs) / sizeof(handle_sigs[0]); i++) + for (i = 0; handle_sigs[i]; i++) { if (sigaction(handle_sigs[i], &sa, NULL) == -1) return -1; + if (oldset) + sigaddset(&newset, handle_sigs[i]); + } + if (oldset) + return sigprocmask(SIG_BLOCK, &newset, oldset); return 0; } int -signal_setup(void) +signal_setup(void (*func)(int), sigset_t *oldset) { - return signal_handle(signal_handler); + + return signal_handle(func, oldset); } int signal_reset(void) { - return signal_handle(SIG_DFL); -} + return signal_handle(SIG_DFL, NULL); +} diff --git a/signals.h b/signals.h index 7098cfb1..bd141329 100644 --- a/signals.h +++ b/signals.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2008 Roy Marples + * Copyright (c) 2006-2012 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,12 +25,15 @@ * SUCH DAMAGE. */ -#ifndef SIGNAL_H -#define SIGNAL_H +#ifndef SIGNALS_H +#define SIGNALS_H + +extern const int handle_sigs[]; int signal_init(void); -int signal_setup(void); +int signal_setup(void (*)(int), sigset_t *); int signal_reset(void); int signal_read(void); +int signal_block(int); #endif