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.
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");
}
break;
default:
- signal_reset();
/* Wait for child to detach */
close(sidpipe[1]);
if (read(sidpipe[0], &buf, 1) == -1)
exit(EXIT_SUCCESS);
}
options |= DHCPCD_DAEMONISED;
- sigprocmask(SIG_SETMASK, &old, NULL);
return pid;
}
#endif
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2012 Roy Marples <roy@marples.name>
+ * 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 <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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;
+}
--- /dev/null
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2012 Roy Marples <roy@marples.name>
+ * 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
EOF
fi
+if [ -z "$POSIX_SPAWN" ]; then
+ printf "Testing for posix_spawn ... "
+ cat <<EOF >_posix_spawn.c
+#include <spawn.h>
+#include <stdlib.h>
+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 <spawn.h>" >>$CONFIG_H
+fi
+
if [ -z "$SERVICECMD" ]; then
printf "Checking for OpenRC ... "
if [ -x /sbin/rc-service ]; then
#include <ctype.h>
#include <errno.h>
#include <signal.h>
+/* We can't include spawn.h here because it may not exist.
+ * config.h will pull it in, or our compat one. */
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
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;
}
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) {
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;
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;
{
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;
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)
for (iface = ifaces; iface; iface = iface->next)
add_timeout_sec(0, start_interface, iface);
- start_eloop();
+ start_eloop(&dhcpcd_sigset);
exit(EXIT_SUCCESS);
}
};
extern char vendor[VENDORCLASSID_MAX_LEN];
+extern sigset_t dhcpcd_sigset;
extern int pidfd;
extern int ifac;
extern char **ifav;
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 *);
/*
* dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2010 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2012 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
#include <errno.h>
#include <limits.h>
#include <poll.h>
+#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <syslog.h>
#include "common.h"
+#include "dhcpcd.h"
#include "eloop.h"
static struct timeval now;
} *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)
{
free(free_timeouts);
free_timeouts = t;
}
- free(fds);
}
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;
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;
- }
}
}
}
/*
* dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2010 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2012 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
#ifndef ELOOP_H
#define ELOOP_H
+#include <signal.h>
#include <time.h>
#ifndef ELOOP_QUEUE
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
/*
* dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2009 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2012 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
* SUCH DAMAGE.
*/
-#include <sys/types.h>
-#include <sys/socket.h>
-
#include <errno.h>
#include <signal.h>
#include <string.h>
#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);
+}
/*
* dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2008 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2012 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
* 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