#include <sys/param.h>
#include <sys/errno.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 <getopt.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"
struct consinfo {
const char *name;
STAILQ_ENTRY(consinfo) link;
int fd;
volatile pid_t pid;
volatile int exitstatus;
};
STAILQ_HEAD(consinfo_list, consinfo);
static struct consinfo_list consinfos;
static struct consinfo *primary_consinfo;
static struct consinfo *controlling_consinfo;
static struct consinfo * volatile first_sigchld_consinfo;
static struct pipe_barrier wait_first_child_barrier;
static struct pipe_barrier wait_all_children_barrier;
static const char primary[] = "primary";
static const char secondary[] = "secondary";
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
static void
kill_consoles(int sig)
{
struct consinfo *consinfo;
sigset_t set, oset;
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, &oset);
STAILQ_FOREACH(consinfo, &consinfos, link) {
if (consinfo->pid != -1 && consinfo->pid != 0)
kill(consinfo->pid, sig);
}
sigprocmask(SIG_SETMASK, &oset, NULL);
}
static void
sigalrm_handler(int code __unused)
{
int saved_errno;
saved_errno = errno;
kill_consoles(SIGKILL);
errno = saved_errno;
}
static void
wait_all_consoles(void)
{
sigset_t set, oset;
int error;
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_children_barrier);
alarm(0);
sigprocmask(SIG_SETMASK, &oset, NULL);
if (controlling_consinfo != NULL) {
error = tcsetpgrp(controlling_consinfo->fd,
getpgrp());
if (error != 0)
err(EX_OSERR, "could not give up control of %s",
controlling_consinfo->name);
}
}
static void
kill_wait_all_consoles(int sig)
{
kill_consoles(sig);
wait_all_consoles();
}
static void
kill_wait_all_consoles_err_exit(int eval __unused)
{
kill_wait_all_consoles(SIGTERM);
}
static void __dead2
exit_signal_handler(int code)
{
struct consinfo *consinfo;
bool started_console;
started_console = false;
STAILQ_FOREACH(consinfo, &consinfos, link) {
if (consinfo->pid != -1) {
started_console = true;
break;
}
}
if (started_console)
kill_wait_all_consoles(SIGTERM);
reproduce_signal_death(code);
exit(EXIT_FAILURE);
}
static void
sigchld_handler_reaped_one(pid_t pid, int status)
{
struct consinfo *consinfo, *child_consinfo;
bool others;
child_consinfo = NULL;
others = false;
STAILQ_FOREACH(consinfo, &consinfos, link) {
if (consinfo->pid == pid)
child_consinfo = consinfo;
else if (consinfo->pid != -1 && consinfo->pid != 0)
others = true;
}
if (child_consinfo == NULL)
return;
child_consinfo->pid = 0;
child_consinfo->exitstatus = status;
if (first_sigchld_consinfo == NULL) {
first_sigchld_consinfo = child_consinfo;
pipe_barrier_ready(&wait_first_child_barrier);
}
if (others)
return;
pipe_barrier_ready(&wait_all_children_barrier);
}
static void
sigchld_handler(int code __unused)
{
int status, saved_errno;
pid_t pid;
saved_errno = errno;
while ((void)(pid = waitpid(-1, &status, WNOHANG)),
pid != -1 && pid != 0)
sigchld_handler_reaped_one(pid, status);
errno = saved_errno;
}
static const char *
read_primary_console(void)
{
char *buf, *p, *cons;
size_t len;
int error;
error = sysctlbyname("kern.console", NULL, &len, NULL, 0);
if (error == -1)
err(EX_OSERR, "could not read kern.console length");
buf = malloc(len);
if (buf == NULL)
err(EX_OSERR, "could not allocate kern.console buffer");
error = sysctlbyname("kern.console", buf, &len, NULL, 0);
if (error == -1)
err(EX_OSERR, "could not read kern.console");
p = strchr(buf, '/');
if (p == NULL)
errx(EX_OSERR, "kern.console malformed: no / found");
*p = '\0';
p = strchr(buf, ',');
if (p != NULL)
*p = '\0';
if (*buf != '\0')
cons = strdup(buf);
else
cons = NULL;
free(buf);
return (cons);
}
static void
read_consoles(void)
{
const char *primary_console;
struct consinfo *consinfo;
int fd, error, flags;
struct ttyent *tty;
char *dev, *name;
pid_t pgrp;
primary_console = read_primary_console();
STAILQ_INIT(&consinfos);
while ((tty = getttyent()) != NULL) {
if ((tty->ty_status & TTY_ON) == 0)
continue;
if (strncmp(tty->ty_name, "ttyv", 4) == 0 &&
strcmp(tty->ty_name + 4, "0") != 0)
continue;
consinfo = malloc(sizeof(struct consinfo));
if (consinfo == NULL)
err(EX_OSERR, "could not allocate consinfo");
asprintf(&dev, "/dev/%s", tty->ty_name);
if (dev == NULL)
err(EX_OSERR, "could not allocate dev path");
name = dev + 5;
fd = open(dev, O_RDWR | O_NONBLOCK);
if (fd == -1)
err(EX_IOERR, "could not open %s", dev);
flags = fcntl(fd, F_GETFL);
if (flags == -1)
err(EX_IOERR, "could not get flags for %s", dev);
error = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
if (error == -1)
err(EX_IOERR, "could not set flags for %s", dev);
if (tcgetsid(fd) != -1) {
pgrp = tcgetpgrp(fd);
if (pgrp == -1)
err(EX_IOERR, "could not get pgrp of %s",
dev);
else if (pgrp != getpgrp())
errx(EX_IOERR, "%s controlled by another group",
dev);
if (controlling_consinfo != NULL)
errx(EX_OSERR,
"multiple controlling terminals %s and %s",
controlling_consinfo->name, name);
controlling_consinfo = consinfo;
}
consinfo->name = name;
consinfo->pid = -1;
consinfo->fd = fd;
consinfo->exitstatus = -1;
STAILQ_INSERT_TAIL(&consinfos, consinfo, link);
if (primary_console != NULL &&
strcmp(consinfo->name, primary_console) == 0)
primary_consinfo = consinfo;
}
endttyent();
free(__DECONST(char *, primary_console));
if (STAILQ_EMPTY(&consinfos))
errx(EX_OSERR, "no consoles found");
if (primary_consinfo == NULL) {
warnx("no primary console found, using first");
primary_consinfo = STAILQ_FIRST(&consinfos);
}
}
static void
start_console(struct consinfo *consinfo, const char **argv,
char *primary_secondary, struct pipe_barrier *start_barrier,
const sigset_t *oset)
{
pid_t pid;
if (consinfo == primary_consinfo)
strcpy(primary_secondary, primary);
else
strcpy(primary_secondary, secondary);
fprintf(stderr, "Starting %s installer on %s\n", primary_secondary,
consinfo->name);
pid = fork();
if (pid == -1)
err(EX_OSERR, "could not fork");
if (pid == 0) {
err_set_exit(NULL);
pipe_barrier_destroy(&wait_first_child_barrier);
pipe_barrier_destroy(&wait_all_children_barrier);
child_leader_run(consinfo->name, consinfo->fd,
consinfo != controlling_consinfo, argv, oset,
start_barrier);
}
consinfo->pid = pid;
err_set_exit(kill_wait_all_consoles_err_exit);
}
static void
start_consoles(int argc, char **argv)
{
struct pipe_barrier start_barrier;
struct consinfo *consinfo;
char *primary_secondary;
const char **newargv;
struct sigaction sa;
sigset_t set, oset;
int error, i;
error = pipe_barrier_init(&start_barrier);
if (error != 0)
err(EX_OSERR, "could not create start children barrier");
error = pipe_barrier_init(&wait_first_child_barrier);
if (error != 0)
err(EX_OSERR, "could not create wait first child barrier");
error = pipe_barrier_init(&wait_all_children_barrier);
if (error != 0)
err(EX_OSERR, "could not create wait all children barrier");
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
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_flags = SA_RESTART;
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");
if (controlling_consinfo == NULL) {
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");
}
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_IGN;
if (controlling_consinfo != NULL) {
error = sigaction(SIGINT, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not ignore SIGINT");
error = sigaction(SIGQUIT, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not ignore SIGQUIT");
}
error = sigaction(SIGPIPE, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not ignore SIGPIPE");
error = sigaction(SIGTTOU, &sa, NULL);
if (error != 0)
err(EX_OSERR, "could not ignore SIGTTOU");
newargv = malloc(((size_t)argc + 1) * sizeof(char *));
if (newargv == NULL)
err(EX_OSERR, "could not allocate newargv");
primary_secondary = malloc(MAX(sizeof(primary), sizeof(secondary)));
if (primary_secondary == NULL)
err(EX_OSERR, "could not allocate primary_secondary");
newargv[0] = argv[0];
for (i = 1; i < argc; ++i) {
switch (argv[i][0]) {
case '%':
if (argv[i][1] == '\0')
newargv[i] = primary_secondary;
else
newargv[i] = argv[i] + 1;
break;
default:
newargv[i] = argv[i];
break;
}
}
newargv[argc] = NULL;
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set, &oset);
STAILQ_FOREACH(consinfo, &consinfos, link)
start_console(consinfo, newargv, primary_secondary,
&start_barrier, &oset);
sigprocmask(SIG_SETMASK, &oset, NULL);
pipe_barrier_ready(&start_barrier);
}
static int
wait_consoles(void)
{
pipe_barrier_wait(&wait_first_child_barrier);
kill_wait_all_consoles(SIGTERM);
if (first_sigchld_consinfo == NULL)
errx(EX_SOFTWARE, "failed to find first child that exited");
return (first_sigchld_consinfo->exitstatus);
}
static void __dead2
usage(void)
{
fprintf(stderr, "usage: %s utility [argument ...]", getprogname());
exit(EX_USAGE);
}
int
main(int argc, char **argv)
{
int ch, status;
while ((ch = getopt_long(argc, argv, "+h", longopts, NULL)) != -1) {
switch (ch) {
case 'h':
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 2)
usage();
read_consoles();
start_consoles(argc, argv);
status = wait_consoles();
if (WIFSIGNALED(status))
reproduce_signal_death(WTERMSIG(status));
if (WIFEXITED(status))
return (WEXITSTATUS(status));
return (EXIT_FAILURE);
}