#include <sys/param.h>
#include <sys/errno.h>
#include <sys/procctl.h>
#include <sys/queue.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <termios.h>
#include <ttyent.h>
#include <unistd.h>
#include "common.h"
#include "child.h"
static volatile pid_t grandchild_pid = -1;
static volatile int grandchild_status;
static struct pipe_barrier wait_grandchild_barrier;
static struct pipe_barrier wait_all_descendants_barrier;
static void
kill_descendants(int sig)
{
struct procctl_reaper_kill rk;
rk.rk_sig = sig;
rk.rk_flags = 0;
procctl(P_PID, getpid(), PROC_REAP_KILL, &rk);
}
static void
sigalrm_handler(int sig __unused)
{
int saved_errno;
saved_errno = errno;
kill_descendants(SIGKILL);
errno = saved_errno;
}
static void
wait_all_descendants(void)
{
sigset_t set, oset;
err_set_exit(NULL);
sigemptyset(&set);
sigaddset(&set, SIGALRM);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_UNBLOCK, &set, &oset);
alarm(KILL_TIMEOUT);
pipe_barrier_wait(&wait_all_descendants_barrier);
alarm(0);
sigprocmask(SIG_SETMASK, &oset, NULL);
}
static void
sigchld_handler(int sig __unused)
{
int status, saved_errno;
pid_t pid;
saved_errno = errno;
while ((void)(pid = waitpid(-1, &status, WNOHANG)),
pid != -1 && pid != 0) {
if (pid == grandchild_pid) {
grandchild_status = status;
grandchild_pid = 0;
pipe_barrier_ready(&wait_grandchild_barrier);
}
}
if (pid == -1 && errno == ECHILD && grandchild_pid == 0)
pipe_barrier_ready(&wait_all_descendants_barrier);
errno = saved_errno;
}
static void
exit_signal_handler(int sig)
{
int saved_errno;
if (grandchild_pid == -1) {
reproduce_signal_death(sig);
exit(EXIT_FAILURE);
}
saved_errno = errno;
kill_descendants(sig);
errno = saved_errno;
}
static void
kill_wait_all_descendants(int sig)
{
kill_descendants(sig);
wait_all_descendants();
}
static void
kill_wait_all_descendants_err_exit(int eval __unused)
{
kill_wait_all_descendants(SIGTERM);
}
static void __dead2
grandchild_run(const char **argv, const sigset_t *oset)
{
sig_t orig;
orig = signal(SIGALRM, SIG_DFL);
if (orig == SIG_ERR)
err(EX_OSERR, "could not restore SIGALRM");
orig = signal(SIGCHLD, SIG_DFL);
if (orig == SIG_ERR)
err(EX_OSERR, "could not restore SIGCHLD");
orig = signal(SIGTERM, SIG_DFL);
if (orig == SIG_ERR)
err(EX_OSERR, "could not restore SIGTERM");
orig = signal(SIGINT, SIG_DFL);
if (orig == SIG_ERR)
err(EX_OSERR, "could not restore SIGINT");
orig = signal(SIGQUIT, SIG_DFL);
if (orig == SIG_ERR)
err(EX_OSERR, "could not restore SIGQUIT");
orig = signal(SIGPIPE, SIG_DFL);
if (orig == SIG_ERR)
err(EX_OSERR, "could not restore SIGPIPE");
orig = signal(SIGTTOU, SIG_DFL);
if (orig == SIG_ERR)
err(EX_OSERR, "could not restore SIGTTOU");
sigprocmask(SIG_SETMASK, oset, NULL);
closefrom(3);
execvp(argv[0], __DECONST(char * const *, argv));
err(EX_OSERR, "cannot execvp %s", argv[0]);
}
static int
wait_grandchild_descendants(void)
{
pipe_barrier_wait(&wait_grandchild_barrier);
kill_wait_all_descendants(SIGTERM);
if (grandchild_pid != 0)
errx(EX_SOFTWARE, "failed to reap grandchild");
return (grandchild_status);
}
void
child_leader_run(const char *name, int fd, bool new_session, const char **argv,
const sigset_t *oset, struct pipe_barrier *start_children_barrier)
{
struct pipe_barrier start_grandchild_barrier;
pid_t pid, sid, pgid;
struct sigaction sa;
int error, status;
sigset_t set;
setproctitle("%s [%s]", getprogname(), name);
error = procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL);
if (error != 0)
err(EX_OSERR, "could not acquire reaper status");
sa.sa_flags = SA_RESTART;
sa.sa_handler = sigchld_handler;
sigfillset(&sa.sa_mask);
error = sigaction(SIGCHLD, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not enable SIGCHLD handler");
sa.sa_handler = sigalrm_handler;
error = sigaction(SIGALRM, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not enable SIGALRM handler");
sa.sa_handler = exit_signal_handler;
error = sigaction(SIGTERM, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not enable SIGTERM handler");
error = sigaction(SIGINT, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not enable SIGINT handler");
error = sigaction(SIGQUIT, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not enable SIGQUIT handler");
sigprocmask(SIG_SETMASK, oset, NULL);
error = pipe_barrier_init(&start_grandchild_barrier);
if (error != 0)
err(EX_OSERR, "could not create start grandchild barrier");
error = pipe_barrier_init(&wait_grandchild_barrier);
if (error != 0)
err(EX_OSERR, "could not create wait grandchild barrier");
error = pipe_barrier_init(&wait_all_descendants_barrier);
if (error != 0)
err(EX_OSERR, "could not create wait all descendants barrier");
if (new_session) {
sid = setsid();
pgid = sid;
if (sid == -1)
err(EX_OSERR, "could not create session");
} else {
sid = -1;
pgid = getpid();
error = setpgid(0, pgid);
if (error == -1)
err(EX_OSERR, "could not create process group");
}
pipe_barrier_destroy_ready(start_children_barrier);
pipe_barrier_wait(start_children_barrier);
error = dup2(fd, STDIN_FILENO);
if (error == -1)
err(EX_IOERR, "could not dup %s to stdin", name);
error = dup2(fd, STDOUT_FILENO);
if (error == -1)
err(EX_IOERR, "could not dup %s to stdout", name);
error = dup2(fd, STDERR_FILENO);
if (error == -1)
err(EX_IOERR, "could not dup %s to stderr", name);
if (new_session) {
error = tcsetsid(STDIN_FILENO, sid);
if (error != 0)
err(EX_IOERR, "could not set session for %s", name);
} else {
error = tcsetpgrp(STDIN_FILENO, pgid);
if (error != 0)
err(EX_IOERR, "could not set process group for %s",
name);
}
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, NULL);
pid = fork();
if (pid == -1)
err(EX_OSERR, "could not fork");
if (pid == 0) {
pipe_barrier_destroy(&wait_grandchild_barrier);
pipe_barrier_destroy(&wait_all_descendants_barrier);
pipe_barrier_destroy_ready(&start_grandchild_barrier);
pipe_barrier_wait(&start_grandchild_barrier);
grandchild_run(argv, oset);
}
grandchild_pid = pid;
err_set_exit(kill_wait_all_descendants_err_exit);
sigprocmask(SIG_SETMASK, oset, NULL);
pipe_barrier_ready(&start_grandchild_barrier);
status = wait_grandchild_descendants();
if (WIFSIGNALED(status))
reproduce_signal_death(WTERMSIG(status));
if (WIFEXITED(status))
exit(WEXITSTATUS(status));
exit(EXIT_FAILURE);
}