diff options
Diffstat (limited to 'openbsd-compat/bsd-pselect.c')
-rw-r--r-- | openbsd-compat/bsd-pselect.c | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/openbsd-compat/bsd-pselect.c b/openbsd-compat/bsd-pselect.c new file mode 100644 index 0000000..b363208 --- /dev/null +++ b/openbsd-compat/bsd-pselect.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2021 Darren Tucker (dtucker at dtucker net). + * + * 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 ``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 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. + */ + +#include "includes.h" +#ifndef HAVE_PSELECT + +#include <sys/types.h> +#include <sys/time.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "log.h" +#include "misc.h" /* for set_nonblock */ + +#ifndef HAVE_SIGHANDLER_T +typedef void (*sighandler_t)(int); +#endif + +static sighandler_t saved_sighandler[_NSIG]; + +/* + * Set up the descriptors. Because they are close-on-exec, in the case + * where sshd's re-exec fails notify_pipe will still point to a descriptor + * that was closed by the exec attempt but if that descriptor has been + * reopened then we'll attempt to use that. Ensure that notify_pipe is + * outside of the range used by sshd re-exec but within NFDBITS (so we don't + * need to expand the fd_sets). + */ +#define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) +static int +pselect_notify_setup_fd(int *fd) +{ + int r; + + if ((r = fcntl(*fd, F_DUPFD, REEXEC_MIN_FREE_FD)) < 0 || + fcntl(r, F_SETFD, FD_CLOEXEC) < 0 || r >= FD_SETSIZE) + return -1; + (void)close(*fd); + return (*fd = r); +} + +/* + * we write to this pipe if a SIGCHLD is caught in order to avoid + * the race between select() and child_terminated + */ +static pid_t notify_pid; +static int notify_pipe[2]; +static void +pselect_notify_setup(void) +{ + static int initialized; + + if (initialized && notify_pid == getpid()) + return; + if (notify_pid == 0) + debug3_f("initializing"); + else { + debug3_f("pid changed, reinitializing"); + if (notify_pipe[0] != -1) + close(notify_pipe[0]); + if (notify_pipe[1] != -1) + close(notify_pipe[1]); + } + if (pipe(notify_pipe) == -1) { + error("pipe(notify_pipe) failed %s", strerror(errno)); + } else if (pselect_notify_setup_fd(¬ify_pipe[0]) == -1 || + pselect_notify_setup_fd(¬ify_pipe[1]) == -1) { + error("fcntl(notify_pipe, ...) failed %s", strerror(errno)); + close(notify_pipe[0]); + close(notify_pipe[1]); + } else { + set_nonblock(notify_pipe[0]); + set_nonblock(notify_pipe[1]); + notify_pid = getpid(); + debug3_f("pid %d saved %d pipe0 %d pipe1 %d", getpid(), + notify_pid, notify_pipe[0], notify_pipe[1]); + initialized = 1; + return; + } + notify_pipe[0] = -1; /* read end */ + notify_pipe[1] = -1; /* write end */ +} +static void +pselect_notify_parent(void) +{ + if (notify_pipe[1] != -1) + (void)write(notify_pipe[1], "", 1); +} +static void +pselect_notify_prepare(fd_set *readset) +{ + if (notify_pipe[0] != -1) + FD_SET(notify_pipe[0], readset); +} +static void +pselect_notify_done(fd_set *readset) +{ + char c; + + if (notify_pipe[0] != -1 && FD_ISSET(notify_pipe[0], readset)) { + while (read(notify_pipe[0], &c, 1) != -1) + debug2_f("reading"); + FD_CLR(notify_pipe[0], readset); + } +} + +/*ARGSUSED*/ +static void +pselect_sig_handler(int sig) +{ + int save_errno = errno; + + pselect_notify_parent(); + if (saved_sighandler[sig] != NULL) + (*saved_sighandler[sig])(sig); /* call original handler */ + errno = save_errno; +} + +/* + * A minimal implementation of pselect(2), built on top of select(2). + */ + +int +pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + const struct timespec *timeout, const sigset_t *mask) +{ + int ret, sig, saved_errno, unmasked = 0; + sigset_t osig; + struct sigaction sa, osa; + struct timeval tv, *tvp = NULL; + + if (timeout != NULL) { + tv.tv_sec = timeout->tv_sec; + tv.tv_usec = timeout->tv_nsec / 1000; + tvp = &tv; + } + if (mask == NULL) /* no signal mask, just call select */ + return select(nfds, readfds, writefds, exceptfds, tvp); + + /* For each signal we're unmasking, install our handler if needed. */ + for (sig = 0; sig < _NSIG; sig++) { + if (sig == SIGKILL || sig == SIGSTOP || sigismember(mask, sig)) + continue; + if (sigaction(sig, NULL, &sa) == 0 && + sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) { + unmasked = 1; + if (sa.sa_handler == pselect_sig_handler) + continue; + sa.sa_handler = pselect_sig_handler; + if (sigaction(sig, &sa, &osa) == 0) { + debug3_f("installing signal handler for %s, " + "previous %p", strsignal(sig), + osa.sa_handler); + saved_sighandler[sig] = osa.sa_handler; + } + } + } + if (unmasked) { + pselect_notify_setup(); + pselect_notify_prepare(readfds); + nfds = MAX(nfds, notify_pipe[0] + 1); + } + + /* Unmask signals, call select then restore signal mask. */ + sigprocmask(SIG_SETMASK, mask, &osig); + ret = select(nfds, readfds, writefds, exceptfds, tvp); + saved_errno = errno; + sigprocmask(SIG_SETMASK, &osig, NULL); + + if (unmasked) + pselect_notify_done(readfds); + errno = saved_errno; + return ret; +} +#endif |