Page MenuHomeFreeBSD

D36804.diff
No OneTemporary

D36804.diff

diff --git a/tools/build/mk/OptionalObsoleteFiles.inc b/tools/build/mk/OptionalObsoleteFiles.inc
--- a/tools/build/mk/OptionalObsoleteFiles.inc
+++ b/tools/build/mk/OptionalObsoleteFiles.inc
@@ -470,6 +470,7 @@
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
diff --git a/usr.sbin/bsdinstall/Makefile b/usr.sbin/bsdinstall/Makefile
--- a/usr.sbin/bsdinstall/Makefile
+++ b/usr.sbin/bsdinstall/Makefile
@@ -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
diff --git a/usr.sbin/bsdinstall/runconsoles/Makefile b/usr.sbin/bsdinstall/runconsoles/Makefile
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bsdinstall/runconsoles/Makefile
@@ -0,0 +1,9 @@
+BINDIR= ${LIBEXECDIR}/bsdinstall
+PROG= runconsoles
+MAN=
+
+SRCS= child.c \
+ common.c \
+ runconsoles.c
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bsdinstall/runconsoles/child.h b/usr.sbin/bsdinstall/runconsoles/child.h
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bsdinstall/runconsoles/child.h
@@ -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;
diff --git a/usr.sbin/bsdinstall/runconsoles/child.c b/usr.sbin/bsdinstall/runconsoles/child.c
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bsdinstall/runconsoles/child.c
@@ -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);
+}
diff --git a/usr.sbin/bsdinstall/runconsoles/common.h b/usr.sbin/bsdinstall/runconsoles/common.h
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bsdinstall/runconsoles/common.h
@@ -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);
diff --git a/usr.sbin/bsdinstall/runconsoles/common.c b/usr.sbin/bsdinstall/runconsoles/common.c
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bsdinstall/runconsoles/common.c
@@ -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);
+}
+
diff --git a/usr.sbin/bsdinstall/runconsoles/runconsoles.c b/usr.sbin/bsdinstall/runconsoles/runconsoles.c
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bsdinstall/runconsoles/runconsoles.c
@@ -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);
+}

File Metadata

Mime Type
text/plain
Expires
Sat, Jan 11, 2:26 AM (2 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15749107
Default Alt Text
D36804.diff (37 KB)

Event Timeline