Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F107098634
D36804.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
37 KB
Referenced Files
None
Subscribers
None
D36804.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D36804: bsdinstall: Add a new runconsoles helper binary
Attached
Detach File
Event Timeline
Log In to Comment