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.
This commit is contained in:
Roy Marples 2012-11-10 16:38:52 +00:00
parent 599c9aeb6b
commit 5c08c0c4ca
11 changed files with 310 additions and 165 deletions

6
bind.c
View File

@ -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

135
compat/posix_spawn.c Normal file
View File

@ -0,0 +1,135 @@
/*
* 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;
}

46
compat/posix_spawn.h Normal file
View File

@ -0,0 +1,46 @@
/*
* 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

25
configure vendored
View File

@ -412,6 +412,31 @@ if [ "$TAILQ_FOREACH_SAFE" = no ]; then
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

View File

@ -35,6 +35,8 @@
#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>
@ -81,29 +83,25 @@ static int
exec_script(char *const *argv, char *const *env)
{
pid_t pid;
sigset_t full;
sigset_t old;
posix_spawnattr_t attr;
sigset_t defsigs;
int i;
/* 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 */
}
/* 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) {

View File

@ -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);
}

View File

@ -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 *);

89
eloop.c
View File

@ -1,6 +1,6 @@
/*
* 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
@ -30,11 +30,13 @@
#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;
@ -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;
/* No timeouts, so wait forever */
tsp = NULL;
/* 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) {
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;
}
}
}
}

View File

@ -1,6 +1,6 @@
/*
* 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
@ -28,6 +28,7 @@
#ifndef ELOOP_H
#define ELOOP_H
#include <signal.h>
#include <time.h>
#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

View File

@ -1,6 +1,6 @@
/*
* 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
@ -25,9 +25,6 @@
* SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
@ -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);
}

View File

@ -1,6 +1,6 @@
/*
* 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
@ -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