Path: blob/main/usr.sbin/bsdinstall/runconsoles/child.c
105686 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2022 Jessica Clarke <[email protected]>4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <sys/param.h>28#include <sys/errno.h>29#include <sys/procctl.h>30#include <sys/queue.h>31#include <sys/resource.h>32#include <sys/sysctl.h>33#include <sys/wait.h>3435#include <err.h>36#include <errno.h>37#include <fcntl.h>38#include <signal.h>39#include <stdarg.h>40#include <stdbool.h>41#include <stdio.h>42#include <stdlib.h>43#include <string.h>44#include <sysexits.h>45#include <termios.h>46#include <ttyent.h>47#include <unistd.h>4849#include "common.h"50#include "child.h"5152/* -1: not started, 0: reaped */53static volatile pid_t grandchild_pid = -1;54static volatile int grandchild_status;5556static struct pipe_barrier wait_grandchild_barrier;57static struct pipe_barrier wait_all_descendants_barrier;5859static void60kill_descendants(int sig)61{62struct procctl_reaper_kill rk;6364rk.rk_sig = sig;65rk.rk_flags = 0;66procctl(P_PID, getpid(), PROC_REAP_KILL, &rk);67}6869static void70sigalrm_handler(int sig __unused)71{72int saved_errno;7374saved_errno = errno;75kill_descendants(SIGKILL);76errno = saved_errno;77}7879static void80wait_all_descendants(void)81{82sigset_t set, oset;8384err_set_exit(NULL);8586/*87* We may be run in a context where SIGALRM is blocked; temporarily88* unblock so we can SIGKILL. Similarly, SIGCHLD may be blocked, but if89* we're waiting on the pipe we need to make sure it's not.90*/91sigemptyset(&set);92sigaddset(&set, SIGALRM);93sigaddset(&set, SIGCHLD);94sigprocmask(SIG_UNBLOCK, &set, &oset);95alarm(KILL_TIMEOUT);96pipe_barrier_wait(&wait_all_descendants_barrier);97alarm(0);98sigprocmask(SIG_SETMASK, &oset, NULL);99}100101static void102sigchld_handler(int sig __unused)103{104int status, saved_errno;105pid_t pid;106107saved_errno = errno;108109while ((void)(pid = waitpid(-1, &status, WNOHANG)),110pid != -1 && pid != 0) {111/* NB: No need to check grandchild_pid due to the pid checks */112if (pid == grandchild_pid) {113grandchild_status = status;114grandchild_pid = 0;115pipe_barrier_ready(&wait_grandchild_barrier);116}117}118119/*120* Another process calling kill(..., SIGCHLD) could cause us to get121* here before we've spawned the grandchild; only ready when we have no122* children if the grandchild has been reaped.123*/124if (pid == -1 && errno == ECHILD && grandchild_pid == 0)125pipe_barrier_ready(&wait_all_descendants_barrier);126127errno = saved_errno;128}129130static void131exit_signal_handler(int sig)132{133int saved_errno;134135/*136* If we get killed before we've started the grandchild then just exit137* with that signal, otherwise kill all our descendants with that138* signal and let the main program pick up the grandchild's death.139*/140if (grandchild_pid == -1) {141reproduce_signal_death(sig);142exit(EXIT_FAILURE);143}144145saved_errno = errno;146kill_descendants(sig);147errno = saved_errno;148}149150static void151kill_wait_all_descendants(int sig)152{153kill_descendants(sig);154wait_all_descendants();155}156157static void158kill_wait_all_descendants_err_exit(int eval __unused)159{160kill_wait_all_descendants(SIGTERM);161}162163static void __dead2164grandchild_run(const char **argv, const sigset_t *oset)165{166sig_t orig;167168/* Restore signals */169orig = signal(SIGALRM, SIG_DFL);170if (orig == SIG_ERR)171err(EX_OSERR, "could not restore SIGALRM");172orig = signal(SIGCHLD, SIG_DFL);173if (orig == SIG_ERR)174err(EX_OSERR, "could not restore SIGCHLD");175orig = signal(SIGTERM, SIG_DFL);176if (orig == SIG_ERR)177err(EX_OSERR, "could not restore SIGTERM");178orig = signal(SIGINT, SIG_DFL);179if (orig == SIG_ERR)180err(EX_OSERR, "could not restore SIGINT");181orig = signal(SIGQUIT, SIG_DFL);182if (orig == SIG_ERR)183err(EX_OSERR, "could not restore SIGQUIT");184orig = signal(SIGPIPE, SIG_DFL);185if (orig == SIG_ERR)186err(EX_OSERR, "could not restore SIGPIPE");187orig = signal(SIGTTOU, SIG_DFL);188if (orig == SIG_ERR)189err(EX_OSERR, "could not restore SIGTTOU");190191/* Now safe to unmask signals */192sigprocmask(SIG_SETMASK, oset, NULL);193194/* Only run with stdin/stdout/stderr */195closefrom(3);196197/* Ready to execute the requested program */198execvp(argv[0], __DECONST(char * const *, argv));199err(EX_OSERR, "cannot execvp %s", argv[0]);200}201202static int203wait_grandchild_descendants(void)204{205pipe_barrier_wait(&wait_grandchild_barrier);206207/*208* Once the grandchild itself has exited, kill any lingering209* descendants and wait until we've reaped them all.210*/211kill_wait_all_descendants(SIGTERM);212213if (grandchild_pid != 0)214errx(EX_SOFTWARE, "failed to reap grandchild");215216return (grandchild_status);217}218219void220child_leader_run(const char *name, int fd, bool new_session, const char **argv,221const sigset_t *oset, struct pipe_barrier *start_children_barrier)222{223struct pipe_barrier start_grandchild_barrier;224pid_t pid, sid, pgid;225struct sigaction sa;226int error, status;227sigset_t set;228229setproctitle("%s [%s]", getprogname(), name);230231error = procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL);232if (error != 0)233err(EX_OSERR, "could not acquire reaper status");234235/*236* Set up our own signal handlers for everything the parent overrides237* other than SIGPIPE and SIGTTOU which we leave as ignored, since we238* also use pipe-based synchronisation and want to be able to print239* errors.240*/241sa.sa_flags = SA_RESTART;242sa.sa_handler = sigchld_handler;243sigfillset(&sa.sa_mask);244error = sigaction(SIGCHLD, &sa, NULL);245if (error != 0)246err(EX_OSERR, "could not enable SIGCHLD handler");247sa.sa_handler = sigalrm_handler;248error = sigaction(SIGALRM, &sa, NULL);249if (error != 0)250err(EX_OSERR, "could not enable SIGALRM handler");251sa.sa_handler = exit_signal_handler;252error = sigaction(SIGTERM, &sa, NULL);253if (error != 0)254err(EX_OSERR, "could not enable SIGTERM handler");255error = sigaction(SIGINT, &sa, NULL);256if (error != 0)257err(EX_OSERR, "could not enable SIGINT handler");258error = sigaction(SIGQUIT, &sa, NULL);259if (error != 0)260err(EX_OSERR, "could not enable SIGQUIT handler");261262/*263* Now safe to unmask signals. Note that creating the barriers used by264* the SIGCHLD handler with signals unmasked is safe since they won't265* be used if the grandchild hasn't been forked (and reaped), which266* comes later.267*/268sigprocmask(SIG_SETMASK, oset, NULL);269270error = pipe_barrier_init(&start_grandchild_barrier);271if (error != 0)272err(EX_OSERR, "could not create start grandchild barrier");273274error = pipe_barrier_init(&wait_grandchild_barrier);275if (error != 0)276err(EX_OSERR, "could not create wait grandchild barrier");277278error = pipe_barrier_init(&wait_all_descendants_barrier);279if (error != 0)280err(EX_OSERR, "could not create wait all descendants barrier");281282/*283* Create a new session if this is on a different terminal to284* the current one, otherwise just create a new process group to keep285* things as similar as possible between the two cases.286*/287if (new_session) {288sid = setsid();289pgid = sid;290if (sid == -1)291err(EX_OSERR, "could not create session");292} else {293sid = -1;294pgid = getpid();295error = setpgid(0, pgid);296if (error == -1)297err(EX_OSERR, "could not create process group");298}299300/* Wait until parent is ready for us to start */301pipe_barrier_destroy_ready(start_children_barrier);302pipe_barrier_wait(start_children_barrier);303304/*305* Use the console for stdin/stdout/stderr.306*307* NB: dup2(2) is a no-op if the two fds are equal, and the call to308* closefrom(2) later in the grandchild will close the fd if it isn't309* one of stdin/stdout/stderr already. This means we do not need to310* handle that special case differently.311*/312error = dup2(fd, STDIN_FILENO);313if (error == -1)314err(EX_IOERR, "could not dup %s to stdin", name);315error = dup2(fd, STDOUT_FILENO);316if (error == -1)317err(EX_IOERR, "could not dup %s to stdout", name);318error = dup2(fd, STDERR_FILENO);319if (error == -1)320err(EX_IOERR, "could not dup %s to stderr", name);321322/*323* If we created a new session, make the console our controlling324* terminal. Either way, also make this the foreground process group.325*/326if (new_session) {327error = tcsetsid(STDIN_FILENO, sid);328if (error != 0)329err(EX_IOERR, "could not set session for %s", name);330} else {331error = tcsetpgrp(STDIN_FILENO, pgid);332if (error != 0)333err(EX_IOERR, "could not set process group for %s",334name);335}336337/*338* Temporarily block signals again; forking, setting grandchild_pid and339* calling err_set_exit need to all be atomic for similar reasons as340* the parent when forking us.341*/342sigfillset(&set);343sigprocmask(SIG_BLOCK, &set, NULL);344pid = fork();345if (pid == -1)346err(EX_OSERR, "could not fork");347348if (pid == 0) {349/*350* We need to destroy the ready ends so we don't block these351* child leader-only self-pipes, and might as well destroy the352* wait ends too given we're not going to use them.353*/354pipe_barrier_destroy(&wait_grandchild_barrier);355pipe_barrier_destroy(&wait_all_descendants_barrier);356357/* Wait until the parent has put us in a new process group */358pipe_barrier_destroy_ready(&start_grandchild_barrier);359pipe_barrier_wait(&start_grandchild_barrier);360grandchild_run(argv, oset);361}362363grandchild_pid = pid;364365/*366* Now the grandchild exists make sure to clean it up, and any of its367* descendants, on exit.368*/369err_set_exit(kill_wait_all_descendants_err_exit);370371sigprocmask(SIG_SETMASK, oset, NULL);372373/* Start the grandchild and wait for it and its descendants to exit */374pipe_barrier_ready(&start_grandchild_barrier);375376status = wait_grandchild_descendants();377378if (WIFSIGNALED(status))379reproduce_signal_death(WTERMSIG(status));380381if (WIFEXITED(status))382exit(WEXITSTATUS(status));383384exit(EXIT_FAILURE);385}386387388