bsdinstall: Add a new runconsoles helper binary
This helper binary will run a given command on every on console, as defined by /etc/ttys (except for ttyv*, where only ttyv0 will be used). If one of the command processes exits, the rest will be killed. This will be used by a future change to start the installer on multiple consoles. Reviewed by: brooks, imp, gjb Differential Revision: https://reviews.freebsd.org/D36804
This commit is contained in:
parent
f334df7600
commit
a2464ee127
@ -470,6 +470,7 @@ OLD_FILES+=usr/libexec/bsdinstall/netconfig_ipv4
|
||||
OLD_FILES+=usr/libexec/bsdinstall/netconfig_ipv6
|
||||
OLD_FILES+=usr/libexec/bsdinstall/partedit
|
||||
OLD_FILES+=usr/libexec/bsdinstall/rootpass
|
||||
OLD_FILES+=usr/libexec/bsdinstall/runconsoles
|
||||
OLD_FILES+=usr/libexec/bsdinstall/script
|
||||
OLD_FILES+=usr/libexec/bsdinstall/scriptedpart
|
||||
OLD_FILES+=usr/libexec/bsdinstall/services
|
||||
|
@ -1,7 +1,7 @@
|
||||
# $FreeBSD$
|
||||
|
||||
OSNAME?= FreeBSD
|
||||
SUBDIR= distextract distfetch partedit scripts
|
||||
SUBDIR= distextract distfetch partedit runconsoles scripts
|
||||
SUBDIR_PARALLEL=
|
||||
SCRIPTS= bsdinstall
|
||||
MAN= bsdinstall.8
|
||||
|
9
usr.sbin/bsdinstall/runconsoles/Makefile
Normal file
9
usr.sbin/bsdinstall/runconsoles/Makefile
Normal file
@ -0,0 +1,9 @@
|
||||
BINDIR= ${LIBEXECDIR}/bsdinstall
|
||||
PROG= runconsoles
|
||||
MAN=
|
||||
|
||||
SRCS= child.c \
|
||||
common.c \
|
||||
runconsoles.c
|
||||
|
||||
.include <bsd.prog.mk>
|
386
usr.sbin/bsdinstall/runconsoles/child.c
Normal file
386
usr.sbin/bsdinstall/runconsoles/child.c
Normal file
@ -0,0 +1,386 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2022 Jessica Clarke <jrtc27@FreeBSD.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#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"
|
||||
|
||||
/* -1: not started, 0: reaped */
|
||||
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);
|
||||
|
||||
/*
|
||||
* We may be run in a context where SIGALRM is blocked; temporarily
|
||||
* unblock so we can SIGKILL. Similarly, SIGCHLD may be blocked, but if
|
||||
* we're waiting on the pipe we need to make sure it's not.
|
||||
*/
|
||||
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) {
|
||||
/* NB: No need to check grandchild_pid due to the pid checks */
|
||||
if (pid == grandchild_pid) {
|
||||
grandchild_status = status;
|
||||
grandchild_pid = 0;
|
||||
pipe_barrier_ready(&wait_grandchild_barrier);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Another process calling kill(..., SIGCHLD) could cause us to get
|
||||
* here before we've spawned the grandchild; only ready when we have no
|
||||
* children if the grandchild has been reaped.
|
||||
*/
|
||||
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 we get killed before we've started the grandchild then just exit
|
||||
* with that signal, otherwise kill all our descendants with that
|
||||
* signal and let the main program pick up the grandchild's death.
|
||||
*/
|
||||
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;
|
||||
|
||||
/* Restore signals */
|
||||
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");
|
||||
|
||||
/* Now safe to unmask signals */
|
||||
sigprocmask(SIG_SETMASK, oset, NULL);
|
||||
|
||||
/* Only run with stdin/stdout/stderr */
|
||||
closefrom(3);
|
||||
|
||||
/* Ready to execute the requested program */
|
||||
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);
|
||||
|
||||
/*
|
||||
* Once the grandchild itself has exited, kill any lingering
|
||||
* descendants and wait until we've reaped them all.
|
||||
*/
|
||||
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");
|
||||
|
||||
/*
|
||||
* Set up our own signal handlers for everything the parent overrides
|
||||
* other than SIGPIPE and SIGTTOU which we leave as ignored, since we
|
||||
* also use pipe-based synchronisation and want to be able to print
|
||||
* errors.
|
||||
*/
|
||||
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");
|
||||
|
||||
/*
|
||||
* Now safe to unmask signals. Note that creating the barriers used by
|
||||
* the SIGCHLD handler with signals unmasked is safe since they won't
|
||||
* be used if the grandchild hasn't been forked (and reaped), which
|
||||
* comes later.
|
||||
*/
|
||||
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");
|
||||
|
||||
/*
|
||||
* Create a new session if this is on a different terminal to
|
||||
* the current one, otherwise just create a new process group to keep
|
||||
* things as similar as possible between the two cases.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/* Wait until parent is ready for us to start */
|
||||
pipe_barrier_destroy_ready(start_children_barrier);
|
||||
pipe_barrier_wait(start_children_barrier);
|
||||
|
||||
/*
|
||||
* Use the console for stdin/stdout/stderr.
|
||||
*
|
||||
* NB: dup2(2) is a no-op if the two fds are equal, and the call to
|
||||
* closefrom(2) later in the grandchild will close the fd if it isn't
|
||||
* one of stdin/stdout/stderr already. This means we do not need to
|
||||
* handle that special case differently.
|
||||
*/
|
||||
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 we created a new session, make the console our controlling
|
||||
* terminal. Either way, also make this the foreground process group.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Temporarily block signals again; forking, setting grandchild_pid and
|
||||
* calling err_set_exit need to all be atomic for similar reasons as
|
||||
* the parent when forking us.
|
||||
*/
|
||||
sigfillset(&set);
|
||||
sigprocmask(SIG_BLOCK, &set, NULL);
|
||||
pid = fork();
|
||||
if (pid == -1)
|
||||
err(EX_OSERR, "could not fork");
|
||||
|
||||
if (pid == 0) {
|
||||
/*
|
||||
* We need to destroy the ready ends so we don't block these
|
||||
* child leader-only self-pipes, and might as well destroy the
|
||||
* wait ends too given we're not going to use them.
|
||||
*/
|
||||
pipe_barrier_destroy(&wait_grandchild_barrier);
|
||||
pipe_barrier_destroy(&wait_all_descendants_barrier);
|
||||
|
||||
/* Wait until the parent has put us in a new process group */
|
||||
pipe_barrier_destroy_ready(&start_grandchild_barrier);
|
||||
pipe_barrier_wait(&start_grandchild_barrier);
|
||||
grandchild_run(argv, oset);
|
||||
}
|
||||
|
||||
grandchild_pid = pid;
|
||||
|
||||
/*
|
||||
* Now the grandchild exists make sure to clean it up, and any of its
|
||||
* descendants, on exit.
|
||||
*/
|
||||
err_set_exit(kill_wait_all_descendants_err_exit);
|
||||
|
||||
sigprocmask(SIG_SETMASK, oset, NULL);
|
||||
|
||||
/* Start the grandchild and wait for it and its descendants to exit */
|
||||
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);
|
||||
}
|
30
usr.sbin/bsdinstall/runconsoles/child.h
Normal file
30
usr.sbin/bsdinstall/runconsoles/child.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2022 Jessica Clarke <jrtc27@FreeBSD.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
void child_leader_run(const char *name, int fd, bool new_session,
|
||||
const char **argv, const sigset_t *oset,
|
||||
struct pipe_barrier *start_barrier) __dead2;
|
56
usr.sbin/bsdinstall/runconsoles/common.c
Normal file
56
usr.sbin/bsdinstall/runconsoles/common.c
Normal file
@ -0,0 +1,56 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2022 Jessica Clarke <jrtc27@FreeBSD.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <sysexits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
void
|
||||
reproduce_signal_death(int sig)
|
||||
{
|
||||
struct rlimit rl;
|
||||
|
||||
if (signal(sig, SIG_DFL) == SIG_ERR)
|
||||
err(EX_OSERR,
|
||||
"cannot set action to reproduce signal %d",
|
||||
sig);
|
||||
rl.rlim_cur = 0;
|
||||
rl.rlim_max = 0;
|
||||
if (setrlimit(RLIMIT_CORE, &rl) == -1)
|
||||
err(EX_OSERR,
|
||||
"cannot disable core dumps to reproduce signal %d",
|
||||
sig);
|
||||
kill(getpid(), sig);
|
||||
}
|
||||
|
110
usr.sbin/bsdinstall/runconsoles/common.h
Normal file
110
usr.sbin/bsdinstall/runconsoles/common.h
Normal file
@ -0,0 +1,110 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2022 Jessica Clarke <jrtc27@FreeBSD.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define KILL_TIMEOUT 10
|
||||
|
||||
/*
|
||||
* NB: Most of these do not need to be volatile, but a handful are used in
|
||||
* signal handler contexts, so for simplicity we make them all volatile rather
|
||||
* than duplicate the implementation.
|
||||
*/
|
||||
struct pipe_barrier {
|
||||
volatile int fds[2];
|
||||
};
|
||||
|
||||
static __inline int
|
||||
pipe_barrier_init(struct pipe_barrier *p)
|
||||
{
|
||||
int error, fds[2], i;
|
||||
|
||||
error = pipe(fds);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
for (i = 0; i < 2; ++i)
|
||||
p->fds[i] = fds[i];
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static __inline void
|
||||
pipe_barrier_wait(struct pipe_barrier *p)
|
||||
{
|
||||
ssize_t ret;
|
||||
char temp;
|
||||
int fd;
|
||||
|
||||
fd = p->fds[0];
|
||||
p->fds[0] = -1;
|
||||
do {
|
||||
ret = read(fd, &temp, 1);
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static __inline void
|
||||
pipe_barrier_ready(struct pipe_barrier *p)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = p->fds[1];
|
||||
p->fds[1] = -1;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static __inline void
|
||||
pipe_barrier_destroy_impl(struct pipe_barrier *p, int i)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = p->fds[i];
|
||||
if (fd != -1) {
|
||||
p->fds[i] = -1;
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
static __inline void
|
||||
pipe_barrier_destroy_wait(struct pipe_barrier *p)
|
||||
{
|
||||
pipe_barrier_destroy_impl(p, 0);
|
||||
}
|
||||
|
||||
static __inline void
|
||||
pipe_barrier_destroy_ready(struct pipe_barrier *p)
|
||||
{
|
||||
pipe_barrier_destroy_impl(p, 1);
|
||||
}
|
||||
|
||||
static __inline void
|
||||
pipe_barrier_destroy(struct pipe_barrier *p)
|
||||
{
|
||||
pipe_barrier_destroy_wait(p);
|
||||
pipe_barrier_destroy_ready(p);
|
||||
}
|
||||
|
||||
void reproduce_signal_death(int sig);
|
647
usr.sbin/bsdinstall/runconsoles/runconsoles.c
Normal file
647
usr.sbin/bsdinstall/runconsoles/runconsoles.c
Normal file
@ -0,0 +1,647 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2022 Jessica Clarke <jrtc27@FreeBSD.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We create the following process hierarchy:
|
||||
*
|
||||
* runconsoles utility
|
||||
* |-- runconsoles [ttyX]
|
||||
* | `-- utility primary
|
||||
* |-- runconsoles [ttyY]
|
||||
* | `-- utility secondary
|
||||
* ...
|
||||
* `-- runconsoles [ttyZ]
|
||||
* `-- utility secondary
|
||||
*
|
||||
* Whilst the intermediate processes might seem unnecessary, they are important
|
||||
* so we can ensure the session leader stays around until the actual program
|
||||
* being run and all its children have exited when killing them (and, in the
|
||||
* case of our controlling terminal, that nothing in our current session goes
|
||||
* on to write to it before then), giving them a chance to clean up the
|
||||
* terminal (important if a dialog box is showing).
|
||||
*
|
||||
* Each of the intermediate processes acquires reaper status, allowing it to
|
||||
* kill its descendants, not just a single process group, and wait until all
|
||||
* have finished, not just its immediate child.
|
||||
*/
|
||||
|
||||
#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;
|
||||
/* -1: not started, 0: reaped */
|
||||
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;
|
||||
|
||||
/* Temporarily block signals so PID reading and killing are atomic */
|
||||
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);
|
||||
|
||||
/*
|
||||
* We may be run in a context where SIGALRM is blocked; temporarily
|
||||
* unblock so we can SIGKILL. Similarly, SIGCHLD may be blocked, but if
|
||||
* we're waiting on the pipe we need to make sure it's not.
|
||||
*/
|
||||
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 we haven't yet started a console, don't wait for them, since
|
||||
* we'll never get a SIGCHLD that will wake us up.
|
||||
*/
|
||||
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) {
|
||||
/*
|
||||
* NB: No need to check consinfo->pid as the caller is
|
||||
* responsible for passing a valid PID
|
||||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
* NB: Format is "cons,...cons,/cons,...cons,", with the list before
|
||||
* the / being the set of configured consoles, and the list after being
|
||||
* the list of available consoles.
|
||||
*/
|
||||
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");
|
||||
|
||||
/* Truncate at / to get just the configured consoles */
|
||||
p = strchr(buf, '/');
|
||||
if (p == NULL)
|
||||
errx(EX_OSERR, "kern.console malformed: no / found");
|
||||
*p = '\0';
|
||||
|
||||
/*
|
||||
* Truncate at , to get just the first configured console, the primary
|
||||
* ("high level") one.
|
||||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
* Only use the first VTY; starting on others is pointless as
|
||||
* they're multiplexed, and they get used to show the install
|
||||
* log and start a shell.
|
||||
*/
|
||||
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) {
|
||||
/*
|
||||
* No need to check controlling session is ours as
|
||||
* tcgetsid fails with ENOTTY if not.
|
||||
*/
|
||||
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) {
|
||||
/* Redundant for the first fork but not subsequent ones */
|
||||
err_set_exit(NULL);
|
||||
|
||||
/*
|
||||
* We need to destroy the ready ends so we don't block these
|
||||
* parent-only self-pipes, and might as well destroy the wait
|
||||
* ends too given we're not going to use them.
|
||||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
* We have at least one child now so make sure we kill children on
|
||||
* exit. We also must not do this until we have at least one since
|
||||
* otherwise we will never receive a SIGCHLD that will ready the pipe
|
||||
* barrier and thus we will wait forever.
|
||||
*/
|
||||
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");
|
||||
|
||||
/*
|
||||
* About to start children, so use our SIGCHLD handler to get notified
|
||||
* when we need to stop. Once the first child has started we will have
|
||||
* registered kill_wait_all_consoles_err_exit which needs our SIGALRM handler to
|
||||
* SIGKILL the children on timeout; do it up front so we can err if it
|
||||
* fails beforehand.
|
||||
*
|
||||
* Also set up our SIGTERM (and SIGINT and SIGQUIT if we're keeping
|
||||
* control of this terminal) handler before we start children so we can
|
||||
* clean them up when signalled.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore SIGINT/SIGQUIT in parent if a child leader will take control
|
||||
* of this terminal so only it gets them, and ignore SIGPIPE in parent,
|
||||
* and child until unblocked, since we're using pipes internally as
|
||||
* synchronisation barriers between parent and children.
|
||||
*
|
||||
* Also ignore SIGTTOU so we can print errors if needed after the child
|
||||
* has started.
|
||||
*/
|
||||
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");
|
||||
|
||||
/*
|
||||
* Create a fresh copy of the argument array and perform %-substitution;
|
||||
* a literal % will be replaced with primary_secondary, and any other
|
||||
* string that starts % will have the leading % removed (thus arguments
|
||||
* that should start with a % should be escaped with an additional %).
|
||||
*
|
||||
* Having all % arguments use primary_secondary means that copying
|
||||
* either "primary" or "secondary" to it will yield the final argument
|
||||
* array for the child in constant time, regardless of how many appear.
|
||||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
* Temporarily block signals. The parent needs forking, assigning
|
||||
* consinfo->pid and, for the first iteration, calling err_set_exit, to
|
||||
* be atomic, and the child leader shouldn't have signals re-enabled
|
||||
* until it has configured its signal handlers appropriately as the
|
||||
* current ones are for the parent's handling of children.
|
||||
*/
|
||||
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);
|
||||
|
||||
/* Now ready for children to start */
|
||||
pipe_barrier_ready(&start_barrier);
|
||||
}
|
||||
|
||||
static int
|
||||
wait_consoles(void)
|
||||
{
|
||||
pipe_barrier_wait(&wait_first_child_barrier);
|
||||
|
||||
/*
|
||||
* Once one of our children has exited, kill off the rest and wait for
|
||||
* them all to exit. This will also set the foreground process group of
|
||||
* the controlling terminal back to ours if it's one of the consoles.
|
||||
*/
|
||||
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();
|
||||
|
||||
/*
|
||||
* Gather the list of enabled consoles from /etc/ttys, ignoring VTYs
|
||||
* other than ttyv0 since they're used for other purposes when the
|
||||
* installer is running, and there would be no point having multiple
|
||||
* copies on each of the multiplexed virtual consoles anyway.
|
||||
*/
|
||||
read_consoles();
|
||||
|
||||
/*
|
||||
* Start the installer on all the consoles. Do not print after this
|
||||
* point until our process group is in the foreground again unless
|
||||
* necessary (we ignore SIGTTOU so we can print errors, but don't want
|
||||
* to garble a child's output).
|
||||
*/
|
||||
start_consoles(argc, argv);
|
||||
|
||||
/*
|
||||
* Wait for one of the installers to exit, kill the rest, become the
|
||||
* foreground process group again and get the exit code of the first
|
||||
* child to exit.
|
||||
*/
|
||||
status = wait_consoles();
|
||||
|
||||
/*
|
||||
* Reproduce the exit code of the first child to exit, including
|
||||
* whether it was a fatal signal or normal termination.
|
||||
*/
|
||||
if (WIFSIGNALED(status))
|
||||
reproduce_signal_death(WTERMSIG(status));
|
||||
|
||||
if (WIFEXITED(status))
|
||||
return (WEXITSTATUS(status));
|
||||
|
||||
return (EXIT_FAILURE);
|
||||
}
|
Loading…
Reference in New Issue
Block a user