xref: /freebsd/usr.sbin/bsdinstall/runconsoles/runconsoles.c (revision a2464ee12761660f50d0b6f59f233949ebcacc87)
1*a2464ee1SJessica Clarke /*-
2*a2464ee1SJessica Clarke  * SPDX-License-Identifier: BSD-2-Clause
3*a2464ee1SJessica Clarke  *
4*a2464ee1SJessica Clarke  * Copyright (c) 2022 Jessica Clarke <jrtc27@FreeBSD.org>
5*a2464ee1SJessica Clarke  *
6*a2464ee1SJessica Clarke  * Redistribution and use in source and binary forms, with or without
7*a2464ee1SJessica Clarke  * modification, are permitted provided that the following conditions
8*a2464ee1SJessica Clarke  * are met:
9*a2464ee1SJessica Clarke  * 1. Redistributions of source code must retain the above copyright
10*a2464ee1SJessica Clarke  *    notice, this list of conditions and the following disclaimer.
11*a2464ee1SJessica Clarke  * 2. Redistributions in binary form must reproduce the above copyright
12*a2464ee1SJessica Clarke  *    notice, this list of conditions and the following disclaimer in the
13*a2464ee1SJessica Clarke  *    documentation and/or other materials provided with the distribution.
14*a2464ee1SJessica Clarke  *
15*a2464ee1SJessica Clarke  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16*a2464ee1SJessica Clarke  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17*a2464ee1SJessica Clarke  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18*a2464ee1SJessica Clarke  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19*a2464ee1SJessica Clarke  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20*a2464ee1SJessica Clarke  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21*a2464ee1SJessica Clarke  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22*a2464ee1SJessica Clarke  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23*a2464ee1SJessica Clarke  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24*a2464ee1SJessica Clarke  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25*a2464ee1SJessica Clarke  * SUCH DAMAGE.
26*a2464ee1SJessica Clarke  */
27*a2464ee1SJessica Clarke 
28*a2464ee1SJessica Clarke /*
29*a2464ee1SJessica Clarke  * We create the following process hierarchy:
30*a2464ee1SJessica Clarke  *
31*a2464ee1SJessica Clarke  *   runconsoles utility
32*a2464ee1SJessica Clarke  *   |-- runconsoles [ttyX]
33*a2464ee1SJessica Clarke  *   |   `-- utility primary
34*a2464ee1SJessica Clarke  *   |-- runconsoles [ttyY]
35*a2464ee1SJessica Clarke  *   |   `-- utility secondary
36*a2464ee1SJessica Clarke  *   ...
37*a2464ee1SJessica Clarke  *   `-- runconsoles [ttyZ]
38*a2464ee1SJessica Clarke  *       `-- utility secondary
39*a2464ee1SJessica Clarke  *
40*a2464ee1SJessica Clarke  * Whilst the intermediate processes might seem unnecessary, they are important
41*a2464ee1SJessica Clarke  * so we can ensure the session leader stays around until the actual program
42*a2464ee1SJessica Clarke  * being run and all its children have exited when killing them (and, in the
43*a2464ee1SJessica Clarke  * case of our controlling terminal, that nothing in our current session goes
44*a2464ee1SJessica Clarke  * on to write to it before then), giving them a chance to clean up the
45*a2464ee1SJessica Clarke  * terminal (important if a dialog box is showing).
46*a2464ee1SJessica Clarke  *
47*a2464ee1SJessica Clarke  * Each of the intermediate processes acquires reaper status, allowing it to
48*a2464ee1SJessica Clarke  * kill its descendants, not just a single process group, and wait until all
49*a2464ee1SJessica Clarke  * have finished, not just its immediate child.
50*a2464ee1SJessica Clarke  */
51*a2464ee1SJessica Clarke 
52*a2464ee1SJessica Clarke #include <sys/param.h>
53*a2464ee1SJessica Clarke #include <sys/errno.h>
54*a2464ee1SJessica Clarke #include <sys/queue.h>
55*a2464ee1SJessica Clarke #include <sys/resource.h>
56*a2464ee1SJessica Clarke #include <sys/sysctl.h>
57*a2464ee1SJessica Clarke #include <sys/wait.h>
58*a2464ee1SJessica Clarke 
59*a2464ee1SJessica Clarke #include <err.h>
60*a2464ee1SJessica Clarke #include <errno.h>
61*a2464ee1SJessica Clarke #include <fcntl.h>
62*a2464ee1SJessica Clarke #include <getopt.h>
63*a2464ee1SJessica Clarke #include <signal.h>
64*a2464ee1SJessica Clarke #include <stdarg.h>
65*a2464ee1SJessica Clarke #include <stdbool.h>
66*a2464ee1SJessica Clarke #include <stdio.h>
67*a2464ee1SJessica Clarke #include <stdlib.h>
68*a2464ee1SJessica Clarke #include <string.h>
69*a2464ee1SJessica Clarke #include <sysexits.h>
70*a2464ee1SJessica Clarke #include <termios.h>
71*a2464ee1SJessica Clarke #include <ttyent.h>
72*a2464ee1SJessica Clarke #include <unistd.h>
73*a2464ee1SJessica Clarke 
74*a2464ee1SJessica Clarke #include "common.h"
75*a2464ee1SJessica Clarke #include "child.h"
76*a2464ee1SJessica Clarke 
77*a2464ee1SJessica Clarke struct consinfo {
78*a2464ee1SJessica Clarke 	const char		*name;
79*a2464ee1SJessica Clarke 	STAILQ_ENTRY(consinfo)	link;
80*a2464ee1SJessica Clarke 	int			fd;
81*a2464ee1SJessica Clarke 	/* -1: not started, 0: reaped */
82*a2464ee1SJessica Clarke 	volatile pid_t		pid;
83*a2464ee1SJessica Clarke 	volatile int		exitstatus;
84*a2464ee1SJessica Clarke };
85*a2464ee1SJessica Clarke 
86*a2464ee1SJessica Clarke STAILQ_HEAD(consinfo_list, consinfo);
87*a2464ee1SJessica Clarke 
88*a2464ee1SJessica Clarke static struct consinfo_list consinfos;
89*a2464ee1SJessica Clarke static struct consinfo *primary_consinfo;
90*a2464ee1SJessica Clarke static struct consinfo *controlling_consinfo;
91*a2464ee1SJessica Clarke 
92*a2464ee1SJessica Clarke static struct consinfo * volatile first_sigchld_consinfo;
93*a2464ee1SJessica Clarke 
94*a2464ee1SJessica Clarke static struct pipe_barrier wait_first_child_barrier;
95*a2464ee1SJessica Clarke static struct pipe_barrier wait_all_children_barrier;
96*a2464ee1SJessica Clarke 
97*a2464ee1SJessica Clarke static const char primary[] = "primary";
98*a2464ee1SJessica Clarke static const char secondary[] = "secondary";
99*a2464ee1SJessica Clarke 
100*a2464ee1SJessica Clarke static const struct option longopts[] = {
101*a2464ee1SJessica Clarke 	{ "help",	no_argument,	NULL,	'h' },
102*a2464ee1SJessica Clarke 	{ NULL,		0,		NULL,	0 }
103*a2464ee1SJessica Clarke };
104*a2464ee1SJessica Clarke 
105*a2464ee1SJessica Clarke static void
kill_consoles(int sig)106*a2464ee1SJessica Clarke kill_consoles(int sig)
107*a2464ee1SJessica Clarke {
108*a2464ee1SJessica Clarke 	struct consinfo *consinfo;
109*a2464ee1SJessica Clarke 	sigset_t set, oset;
110*a2464ee1SJessica Clarke 
111*a2464ee1SJessica Clarke 	/* Temporarily block signals so PID reading and killing are atomic */
112*a2464ee1SJessica Clarke 	sigfillset(&set);
113*a2464ee1SJessica Clarke 	sigprocmask(SIG_BLOCK, &set, &oset);
114*a2464ee1SJessica Clarke 	STAILQ_FOREACH(consinfo, &consinfos, link) {
115*a2464ee1SJessica Clarke 		if (consinfo->pid != -1 && consinfo->pid != 0)
116*a2464ee1SJessica Clarke 			kill(consinfo->pid, sig);
117*a2464ee1SJessica Clarke 	}
118*a2464ee1SJessica Clarke 	sigprocmask(SIG_SETMASK, &oset, NULL);
119*a2464ee1SJessica Clarke }
120*a2464ee1SJessica Clarke 
121*a2464ee1SJessica Clarke static void
sigalrm_handler(int code __unused)122*a2464ee1SJessica Clarke sigalrm_handler(int code __unused)
123*a2464ee1SJessica Clarke {
124*a2464ee1SJessica Clarke 	int saved_errno;
125*a2464ee1SJessica Clarke 
126*a2464ee1SJessica Clarke 	saved_errno = errno;
127*a2464ee1SJessica Clarke 	kill_consoles(SIGKILL);
128*a2464ee1SJessica Clarke 	errno = saved_errno;
129*a2464ee1SJessica Clarke }
130*a2464ee1SJessica Clarke 
131*a2464ee1SJessica Clarke static void
wait_all_consoles(void)132*a2464ee1SJessica Clarke wait_all_consoles(void)
133*a2464ee1SJessica Clarke {
134*a2464ee1SJessica Clarke 	sigset_t set, oset;
135*a2464ee1SJessica Clarke 	int error;
136*a2464ee1SJessica Clarke 
137*a2464ee1SJessica Clarke 	err_set_exit(NULL);
138*a2464ee1SJessica Clarke 
139*a2464ee1SJessica Clarke 	/*
140*a2464ee1SJessica Clarke 	 * We may be run in a context where SIGALRM is blocked; temporarily
141*a2464ee1SJessica Clarke 	 * unblock so we can SIGKILL. Similarly, SIGCHLD may be blocked, but if
142*a2464ee1SJessica Clarke 	 * we're waiting on the pipe we need to make sure it's not.
143*a2464ee1SJessica Clarke 	 */
144*a2464ee1SJessica Clarke 	sigemptyset(&set);
145*a2464ee1SJessica Clarke 	sigaddset(&set, SIGALRM);
146*a2464ee1SJessica Clarke 	sigaddset(&set, SIGCHLD);
147*a2464ee1SJessica Clarke 	sigprocmask(SIG_UNBLOCK, &set, &oset);
148*a2464ee1SJessica Clarke 	alarm(KILL_TIMEOUT);
149*a2464ee1SJessica Clarke 	pipe_barrier_wait(&wait_all_children_barrier);
150*a2464ee1SJessica Clarke 	alarm(0);
151*a2464ee1SJessica Clarke 	sigprocmask(SIG_SETMASK, &oset, NULL);
152*a2464ee1SJessica Clarke 
153*a2464ee1SJessica Clarke 	if (controlling_consinfo != NULL) {
154*a2464ee1SJessica Clarke 		error = tcsetpgrp(controlling_consinfo->fd,
155*a2464ee1SJessica Clarke 		    getpgrp());
156*a2464ee1SJessica Clarke 		if (error != 0)
157*a2464ee1SJessica Clarke 			err(EX_OSERR, "could not give up control of %s",
158*a2464ee1SJessica Clarke 			    controlling_consinfo->name);
159*a2464ee1SJessica Clarke 	}
160*a2464ee1SJessica Clarke }
161*a2464ee1SJessica Clarke 
162*a2464ee1SJessica Clarke static void
kill_wait_all_consoles(int sig)163*a2464ee1SJessica Clarke kill_wait_all_consoles(int sig)
164*a2464ee1SJessica Clarke {
165*a2464ee1SJessica Clarke 	kill_consoles(sig);
166*a2464ee1SJessica Clarke 	wait_all_consoles();
167*a2464ee1SJessica Clarke }
168*a2464ee1SJessica Clarke 
169*a2464ee1SJessica Clarke static void
kill_wait_all_consoles_err_exit(int eval __unused)170*a2464ee1SJessica Clarke kill_wait_all_consoles_err_exit(int eval __unused)
171*a2464ee1SJessica Clarke {
172*a2464ee1SJessica Clarke 	kill_wait_all_consoles(SIGTERM);
173*a2464ee1SJessica Clarke }
174*a2464ee1SJessica Clarke 
175*a2464ee1SJessica Clarke static void __dead2
exit_signal_handler(int code)176*a2464ee1SJessica Clarke exit_signal_handler(int code)
177*a2464ee1SJessica Clarke {
178*a2464ee1SJessica Clarke 	struct consinfo *consinfo;
179*a2464ee1SJessica Clarke 	bool started_console;
180*a2464ee1SJessica Clarke 
181*a2464ee1SJessica Clarke 	started_console = false;
182*a2464ee1SJessica Clarke 	STAILQ_FOREACH(consinfo, &consinfos, link) {
183*a2464ee1SJessica Clarke 		if (consinfo->pid != -1) {
184*a2464ee1SJessica Clarke 			started_console = true;
185*a2464ee1SJessica Clarke 			break;
186*a2464ee1SJessica Clarke 		}
187*a2464ee1SJessica Clarke 	}
188*a2464ee1SJessica Clarke 
189*a2464ee1SJessica Clarke 	/*
190*a2464ee1SJessica Clarke 	 * If we haven't yet started a console, don't wait for them, since
191*a2464ee1SJessica Clarke 	 * we'll never get a SIGCHLD that will wake us up.
192*a2464ee1SJessica Clarke 	 */
193*a2464ee1SJessica Clarke 	if (started_console)
194*a2464ee1SJessica Clarke 		kill_wait_all_consoles(SIGTERM);
195*a2464ee1SJessica Clarke 
196*a2464ee1SJessica Clarke 	reproduce_signal_death(code);
197*a2464ee1SJessica Clarke 	exit(EXIT_FAILURE);
198*a2464ee1SJessica Clarke }
199*a2464ee1SJessica Clarke 
200*a2464ee1SJessica Clarke static void
sigchld_handler_reaped_one(pid_t pid,int status)201*a2464ee1SJessica Clarke sigchld_handler_reaped_one(pid_t pid, int status)
202*a2464ee1SJessica Clarke {
203*a2464ee1SJessica Clarke 	struct consinfo *consinfo, *child_consinfo;
204*a2464ee1SJessica Clarke 	bool others;
205*a2464ee1SJessica Clarke 
206*a2464ee1SJessica Clarke 	child_consinfo = NULL;
207*a2464ee1SJessica Clarke 	others = false;
208*a2464ee1SJessica Clarke 	STAILQ_FOREACH(consinfo, &consinfos, link) {
209*a2464ee1SJessica Clarke 		/*
210*a2464ee1SJessica Clarke 		 * NB: No need to check consinfo->pid as the caller is
211*a2464ee1SJessica Clarke 		 * responsible for passing a valid PID
212*a2464ee1SJessica Clarke 		 */
213*a2464ee1SJessica Clarke 		if (consinfo->pid == pid)
214*a2464ee1SJessica Clarke 			child_consinfo = consinfo;
215*a2464ee1SJessica Clarke 		else if (consinfo->pid != -1 && consinfo->pid != 0)
216*a2464ee1SJessica Clarke 			others = true;
217*a2464ee1SJessica Clarke 	}
218*a2464ee1SJessica Clarke 
219*a2464ee1SJessica Clarke 	if (child_consinfo == NULL)
220*a2464ee1SJessica Clarke 		return;
221*a2464ee1SJessica Clarke 
222*a2464ee1SJessica Clarke 	child_consinfo->pid = 0;
223*a2464ee1SJessica Clarke 	child_consinfo->exitstatus = status;
224*a2464ee1SJessica Clarke 
225*a2464ee1SJessica Clarke 	if (first_sigchld_consinfo == NULL) {
226*a2464ee1SJessica Clarke 		first_sigchld_consinfo = child_consinfo;
227*a2464ee1SJessica Clarke 		pipe_barrier_ready(&wait_first_child_barrier);
228*a2464ee1SJessica Clarke 	}
229*a2464ee1SJessica Clarke 
230*a2464ee1SJessica Clarke 	if (others)
231*a2464ee1SJessica Clarke 		return;
232*a2464ee1SJessica Clarke 
233*a2464ee1SJessica Clarke 	pipe_barrier_ready(&wait_all_children_barrier);
234*a2464ee1SJessica Clarke }
235*a2464ee1SJessica Clarke 
236*a2464ee1SJessica Clarke static void
sigchld_handler(int code __unused)237*a2464ee1SJessica Clarke sigchld_handler(int code __unused)
238*a2464ee1SJessica Clarke {
239*a2464ee1SJessica Clarke 	int status, saved_errno;
240*a2464ee1SJessica Clarke 	pid_t pid;
241*a2464ee1SJessica Clarke 
242*a2464ee1SJessica Clarke 	saved_errno = errno;
243*a2464ee1SJessica Clarke 	while ((void)(pid = waitpid(-1, &status, WNOHANG)),
244*a2464ee1SJessica Clarke 	    pid != -1 && pid != 0)
245*a2464ee1SJessica Clarke 		sigchld_handler_reaped_one(pid, status);
246*a2464ee1SJessica Clarke 	errno = saved_errno;
247*a2464ee1SJessica Clarke }
248*a2464ee1SJessica Clarke 
249*a2464ee1SJessica Clarke static const char *
read_primary_console(void)250*a2464ee1SJessica Clarke read_primary_console(void)
251*a2464ee1SJessica Clarke {
252*a2464ee1SJessica Clarke 	char *buf, *p, *cons;
253*a2464ee1SJessica Clarke 	size_t len;
254*a2464ee1SJessica Clarke 	int error;
255*a2464ee1SJessica Clarke 
256*a2464ee1SJessica Clarke 	/*
257*a2464ee1SJessica Clarke 	 * NB: Format is "cons,...cons,/cons,...cons,", with the list before
258*a2464ee1SJessica Clarke 	 * the / being the set of configured consoles, and the list after being
259*a2464ee1SJessica Clarke 	 * the list of available consoles.
260*a2464ee1SJessica Clarke 	 */
261*a2464ee1SJessica Clarke 	error = sysctlbyname("kern.console", NULL, &len, NULL, 0);
262*a2464ee1SJessica Clarke 	if (error == -1)
263*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not read kern.console length");
264*a2464ee1SJessica Clarke 	buf = malloc(len);
265*a2464ee1SJessica Clarke 	if (buf == NULL)
266*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not allocate kern.console buffer");
267*a2464ee1SJessica Clarke 	error = sysctlbyname("kern.console", buf, &len, NULL, 0);
268*a2464ee1SJessica Clarke 	if (error == -1)
269*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not read kern.console");
270*a2464ee1SJessica Clarke 
271*a2464ee1SJessica Clarke 	/* Truncate at / to get just the configured consoles */
272*a2464ee1SJessica Clarke 	p = strchr(buf, '/');
273*a2464ee1SJessica Clarke 	if (p == NULL)
274*a2464ee1SJessica Clarke 		errx(EX_OSERR, "kern.console malformed: no / found");
275*a2464ee1SJessica Clarke 	*p = '\0';
276*a2464ee1SJessica Clarke 
277*a2464ee1SJessica Clarke 	/*
278*a2464ee1SJessica Clarke 	 * Truncate at , to get just the first configured console, the primary
279*a2464ee1SJessica Clarke 	 * ("high level") one.
280*a2464ee1SJessica Clarke 	 */
281*a2464ee1SJessica Clarke 	p = strchr(buf, ',');
282*a2464ee1SJessica Clarke 	if (p != NULL)
283*a2464ee1SJessica Clarke 		*p = '\0';
284*a2464ee1SJessica Clarke 
285*a2464ee1SJessica Clarke 	if (*buf != '\0')
286*a2464ee1SJessica Clarke 		cons = strdup(buf);
287*a2464ee1SJessica Clarke 	else
288*a2464ee1SJessica Clarke 		cons = NULL;
289*a2464ee1SJessica Clarke 
290*a2464ee1SJessica Clarke 	free(buf);
291*a2464ee1SJessica Clarke 
292*a2464ee1SJessica Clarke 	return (cons);
293*a2464ee1SJessica Clarke }
294*a2464ee1SJessica Clarke 
295*a2464ee1SJessica Clarke static void
read_consoles(void)296*a2464ee1SJessica Clarke read_consoles(void)
297*a2464ee1SJessica Clarke {
298*a2464ee1SJessica Clarke 	const char *primary_console;
299*a2464ee1SJessica Clarke 	struct consinfo *consinfo;
300*a2464ee1SJessica Clarke 	int fd, error, flags;
301*a2464ee1SJessica Clarke 	struct ttyent *tty;
302*a2464ee1SJessica Clarke 	char *dev, *name;
303*a2464ee1SJessica Clarke 	pid_t pgrp;
304*a2464ee1SJessica Clarke 
305*a2464ee1SJessica Clarke 	primary_console = read_primary_console();
306*a2464ee1SJessica Clarke 
307*a2464ee1SJessica Clarke 	STAILQ_INIT(&consinfos);
308*a2464ee1SJessica Clarke 	while ((tty = getttyent()) != NULL) {
309*a2464ee1SJessica Clarke 		if ((tty->ty_status & TTY_ON) == 0)
310*a2464ee1SJessica Clarke 			continue;
311*a2464ee1SJessica Clarke 
312*a2464ee1SJessica Clarke 		/*
313*a2464ee1SJessica Clarke 		 * Only use the first VTY; starting on others is pointless as
314*a2464ee1SJessica Clarke 		 * they're multiplexed, and they get used to show the install
315*a2464ee1SJessica Clarke 		 * log and start a shell.
316*a2464ee1SJessica Clarke 		 */
317*a2464ee1SJessica Clarke 		if (strncmp(tty->ty_name, "ttyv", 4) == 0 &&
318*a2464ee1SJessica Clarke 		    strcmp(tty->ty_name + 4, "0") != 0)
319*a2464ee1SJessica Clarke 			continue;
320*a2464ee1SJessica Clarke 
321*a2464ee1SJessica Clarke 		consinfo = malloc(sizeof(struct consinfo));
322*a2464ee1SJessica Clarke 		if (consinfo == NULL)
323*a2464ee1SJessica Clarke 			err(EX_OSERR, "could not allocate consinfo");
324*a2464ee1SJessica Clarke 
325*a2464ee1SJessica Clarke 		asprintf(&dev, "/dev/%s", tty->ty_name);
326*a2464ee1SJessica Clarke 		if (dev == NULL)
327*a2464ee1SJessica Clarke 			err(EX_OSERR, "could not allocate dev path");
328*a2464ee1SJessica Clarke 
329*a2464ee1SJessica Clarke 		name = dev + 5;
330*a2464ee1SJessica Clarke 		fd = open(dev, O_RDWR | O_NONBLOCK);
331*a2464ee1SJessica Clarke 		if (fd == -1)
332*a2464ee1SJessica Clarke 			err(EX_IOERR, "could not open %s", dev);
333*a2464ee1SJessica Clarke 
334*a2464ee1SJessica Clarke 		flags = fcntl(fd, F_GETFL);
335*a2464ee1SJessica Clarke 		if (flags == -1)
336*a2464ee1SJessica Clarke 			err(EX_IOERR, "could not get flags for %s", dev);
337*a2464ee1SJessica Clarke 
338*a2464ee1SJessica Clarke 		error = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
339*a2464ee1SJessica Clarke 		if (error == -1)
340*a2464ee1SJessica Clarke 			err(EX_IOERR, "could not set flags for %s", dev);
341*a2464ee1SJessica Clarke 
342*a2464ee1SJessica Clarke 		if (tcgetsid(fd) != -1) {
343*a2464ee1SJessica Clarke 			/*
344*a2464ee1SJessica Clarke 			 * No need to check controlling session is ours as
345*a2464ee1SJessica Clarke 			 * tcgetsid fails with ENOTTY if not.
346*a2464ee1SJessica Clarke 			 */
347*a2464ee1SJessica Clarke 			pgrp = tcgetpgrp(fd);
348*a2464ee1SJessica Clarke 			if (pgrp == -1)
349*a2464ee1SJessica Clarke 				err(EX_IOERR, "could not get pgrp of %s",
350*a2464ee1SJessica Clarke 				    dev);
351*a2464ee1SJessica Clarke 			else if (pgrp != getpgrp())
352*a2464ee1SJessica Clarke 				errx(EX_IOERR, "%s controlled by another group",
353*a2464ee1SJessica Clarke 				    dev);
354*a2464ee1SJessica Clarke 
355*a2464ee1SJessica Clarke 			if (controlling_consinfo != NULL)
356*a2464ee1SJessica Clarke 				errx(EX_OSERR,
357*a2464ee1SJessica Clarke 				    "multiple controlling terminals %s and %s",
358*a2464ee1SJessica Clarke 				    controlling_consinfo->name, name);
359*a2464ee1SJessica Clarke 
360*a2464ee1SJessica Clarke 			controlling_consinfo = consinfo;
361*a2464ee1SJessica Clarke 		}
362*a2464ee1SJessica Clarke 
363*a2464ee1SJessica Clarke 		consinfo->name = name;
364*a2464ee1SJessica Clarke 		consinfo->pid = -1;
365*a2464ee1SJessica Clarke 		consinfo->fd = fd;
366*a2464ee1SJessica Clarke 		consinfo->exitstatus = -1;
367*a2464ee1SJessica Clarke 		STAILQ_INSERT_TAIL(&consinfos, consinfo, link);
368*a2464ee1SJessica Clarke 
369*a2464ee1SJessica Clarke 		if (primary_console != NULL &&
370*a2464ee1SJessica Clarke 		    strcmp(consinfo->name, primary_console) == 0)
371*a2464ee1SJessica Clarke 			primary_consinfo = consinfo;
372*a2464ee1SJessica Clarke 	}
373*a2464ee1SJessica Clarke 
374*a2464ee1SJessica Clarke 	endttyent();
375*a2464ee1SJessica Clarke 	free(__DECONST(char *, primary_console));
376*a2464ee1SJessica Clarke 
377*a2464ee1SJessica Clarke 	if (STAILQ_EMPTY(&consinfos))
378*a2464ee1SJessica Clarke 		errx(EX_OSERR, "no consoles found");
379*a2464ee1SJessica Clarke 
380*a2464ee1SJessica Clarke 	if (primary_consinfo == NULL) {
381*a2464ee1SJessica Clarke 		warnx("no primary console found, using first");
382*a2464ee1SJessica Clarke 		primary_consinfo = STAILQ_FIRST(&consinfos);
383*a2464ee1SJessica Clarke 	}
384*a2464ee1SJessica Clarke }
385*a2464ee1SJessica Clarke 
386*a2464ee1SJessica Clarke static void
start_console(struct consinfo * consinfo,const char ** argv,char * primary_secondary,struct pipe_barrier * start_barrier,const sigset_t * oset)387*a2464ee1SJessica Clarke start_console(struct consinfo *consinfo, const char **argv,
388*a2464ee1SJessica Clarke     char *primary_secondary, struct pipe_barrier *start_barrier,
389*a2464ee1SJessica Clarke     const sigset_t *oset)
390*a2464ee1SJessica Clarke {
391*a2464ee1SJessica Clarke 	pid_t pid;
392*a2464ee1SJessica Clarke 
393*a2464ee1SJessica Clarke 	if (consinfo == primary_consinfo)
394*a2464ee1SJessica Clarke 		strcpy(primary_secondary, primary);
395*a2464ee1SJessica Clarke 	else
396*a2464ee1SJessica Clarke 		strcpy(primary_secondary, secondary);
397*a2464ee1SJessica Clarke 
398*a2464ee1SJessica Clarke 	fprintf(stderr, "Starting %s installer on %s\n", primary_secondary,
399*a2464ee1SJessica Clarke 	    consinfo->name);
400*a2464ee1SJessica Clarke 
401*a2464ee1SJessica Clarke 	pid = fork();
402*a2464ee1SJessica Clarke 	if (pid == -1)
403*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not fork");
404*a2464ee1SJessica Clarke 
405*a2464ee1SJessica Clarke 	if (pid == 0) {
406*a2464ee1SJessica Clarke 		/* Redundant for the first fork but not subsequent ones */
407*a2464ee1SJessica Clarke 		err_set_exit(NULL);
408*a2464ee1SJessica Clarke 
409*a2464ee1SJessica Clarke 		/*
410*a2464ee1SJessica Clarke 		 * We need to destroy the ready ends so we don't block these
411*a2464ee1SJessica Clarke 		 * parent-only self-pipes, and might as well destroy the wait
412*a2464ee1SJessica Clarke 		 * ends too given we're not going to use them.
413*a2464ee1SJessica Clarke 		 */
414*a2464ee1SJessica Clarke 		pipe_barrier_destroy(&wait_first_child_barrier);
415*a2464ee1SJessica Clarke 		pipe_barrier_destroy(&wait_all_children_barrier);
416*a2464ee1SJessica Clarke 
417*a2464ee1SJessica Clarke 		child_leader_run(consinfo->name, consinfo->fd,
418*a2464ee1SJessica Clarke 		    consinfo != controlling_consinfo, argv, oset,
419*a2464ee1SJessica Clarke 		    start_barrier);
420*a2464ee1SJessica Clarke 	}
421*a2464ee1SJessica Clarke 
422*a2464ee1SJessica Clarke 	consinfo->pid = pid;
423*a2464ee1SJessica Clarke 
424*a2464ee1SJessica Clarke 	/*
425*a2464ee1SJessica Clarke 	 * We have at least one child now so make sure we kill children on
426*a2464ee1SJessica Clarke 	 * exit. We also must not do this until we have at least one since
427*a2464ee1SJessica Clarke 	 * otherwise we will never receive a SIGCHLD that will ready the pipe
428*a2464ee1SJessica Clarke 	 * barrier and thus we will wait forever.
429*a2464ee1SJessica Clarke 	 */
430*a2464ee1SJessica Clarke 	err_set_exit(kill_wait_all_consoles_err_exit);
431*a2464ee1SJessica Clarke }
432*a2464ee1SJessica Clarke 
433*a2464ee1SJessica Clarke static void
start_consoles(int argc,char ** argv)434*a2464ee1SJessica Clarke start_consoles(int argc, char **argv)
435*a2464ee1SJessica Clarke {
436*a2464ee1SJessica Clarke 	struct pipe_barrier start_barrier;
437*a2464ee1SJessica Clarke 	struct consinfo *consinfo;
438*a2464ee1SJessica Clarke 	char *primary_secondary;
439*a2464ee1SJessica Clarke 	const char **newargv;
440*a2464ee1SJessica Clarke 	struct sigaction sa;
441*a2464ee1SJessica Clarke 	sigset_t set, oset;
442*a2464ee1SJessica Clarke 	int error, i;
443*a2464ee1SJessica Clarke 
444*a2464ee1SJessica Clarke 	error = pipe_barrier_init(&start_barrier);
445*a2464ee1SJessica Clarke 	if (error != 0)
446*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not create start children barrier");
447*a2464ee1SJessica Clarke 
448*a2464ee1SJessica Clarke 	error = pipe_barrier_init(&wait_first_child_barrier);
449*a2464ee1SJessica Clarke 	if (error != 0)
450*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not create wait first child barrier");
451*a2464ee1SJessica Clarke 
452*a2464ee1SJessica Clarke 	error = pipe_barrier_init(&wait_all_children_barrier);
453*a2464ee1SJessica Clarke 	if (error != 0)
454*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not create wait all children barrier");
455*a2464ee1SJessica Clarke 
456*a2464ee1SJessica Clarke 	/*
457*a2464ee1SJessica Clarke 	 * About to start children, so use our SIGCHLD handler to get notified
458*a2464ee1SJessica Clarke 	 * when we need to stop. Once the first child has started we will have
459*a2464ee1SJessica Clarke 	 * registered kill_wait_all_consoles_err_exit which needs our SIGALRM handler to
460*a2464ee1SJessica Clarke 	 * SIGKILL the children on timeout; do it up front so we can err if it
461*a2464ee1SJessica Clarke 	 * fails beforehand.
462*a2464ee1SJessica Clarke 	 *
463*a2464ee1SJessica Clarke 	 * Also set up our SIGTERM (and SIGINT and SIGQUIT if we're keeping
464*a2464ee1SJessica Clarke 	 * control of this terminal) handler before we start children so we can
465*a2464ee1SJessica Clarke 	 * clean them up when signalled.
466*a2464ee1SJessica Clarke 	 */
467*a2464ee1SJessica Clarke 	sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
468*a2464ee1SJessica Clarke 	sa.sa_handler = sigchld_handler;
469*a2464ee1SJessica Clarke 	sigfillset(&sa.sa_mask);
470*a2464ee1SJessica Clarke 	error = sigaction(SIGCHLD, &sa, NULL);
471*a2464ee1SJessica Clarke 	if (error != 0)
472*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not enable SIGCHLD handler");
473*a2464ee1SJessica Clarke 	sa.sa_flags = SA_RESTART;
474*a2464ee1SJessica Clarke 	sa.sa_handler = sigalrm_handler;
475*a2464ee1SJessica Clarke 	error = sigaction(SIGALRM, &sa, NULL);
476*a2464ee1SJessica Clarke 	if (error != 0)
477*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not enable SIGALRM handler");
478*a2464ee1SJessica Clarke 	sa.sa_handler = exit_signal_handler;
479*a2464ee1SJessica Clarke 	error = sigaction(SIGTERM, &sa, NULL);
480*a2464ee1SJessica Clarke 	if (error != 0)
481*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not enable SIGTERM handler");
482*a2464ee1SJessica Clarke 	if (controlling_consinfo == NULL) {
483*a2464ee1SJessica Clarke 		error = sigaction(SIGINT, &sa, NULL);
484*a2464ee1SJessica Clarke 		if (error != 0)
485*a2464ee1SJessica Clarke 			err(EX_OSERR, "could not enable SIGINT handler");
486*a2464ee1SJessica Clarke 		error = sigaction(SIGQUIT, &sa, NULL);
487*a2464ee1SJessica Clarke 		if (error != 0)
488*a2464ee1SJessica Clarke 			err(EX_OSERR, "could not enable SIGQUIT handler");
489*a2464ee1SJessica Clarke 	}
490*a2464ee1SJessica Clarke 
491*a2464ee1SJessica Clarke 	/*
492*a2464ee1SJessica Clarke 	 * Ignore SIGINT/SIGQUIT in parent if a child leader will take control
493*a2464ee1SJessica Clarke 	 * of this terminal so only it gets them, and ignore SIGPIPE in parent,
494*a2464ee1SJessica Clarke 	 * and child until unblocked, since we're using pipes internally as
495*a2464ee1SJessica Clarke 	 * synchronisation barriers between parent and children.
496*a2464ee1SJessica Clarke 	 *
497*a2464ee1SJessica Clarke 	 * Also ignore SIGTTOU so we can print errors if needed after the child
498*a2464ee1SJessica Clarke 	 * has started.
499*a2464ee1SJessica Clarke 	 */
500*a2464ee1SJessica Clarke 	sa.sa_flags = SA_RESTART;
501*a2464ee1SJessica Clarke 	sa.sa_handler = SIG_IGN;
502*a2464ee1SJessica Clarke 	if (controlling_consinfo != NULL) {
503*a2464ee1SJessica Clarke 		error = sigaction(SIGINT, &sa, NULL);
504*a2464ee1SJessica Clarke 		if (error != 0)
505*a2464ee1SJessica Clarke 			err(EX_OSERR, "could not ignore SIGINT");
506*a2464ee1SJessica Clarke 		error = sigaction(SIGQUIT, &sa, NULL);
507*a2464ee1SJessica Clarke 		if (error != 0)
508*a2464ee1SJessica Clarke 			err(EX_OSERR, "could not ignore SIGQUIT");
509*a2464ee1SJessica Clarke 	}
510*a2464ee1SJessica Clarke 	error = sigaction(SIGPIPE, &sa, NULL);
511*a2464ee1SJessica Clarke 	if (error != 0)
512*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not ignore SIGPIPE");
513*a2464ee1SJessica Clarke 	error = sigaction(SIGTTOU, &sa, NULL);
514*a2464ee1SJessica Clarke 	if (error != 0)
515*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not ignore SIGTTOU");
516*a2464ee1SJessica Clarke 
517*a2464ee1SJessica Clarke 	/*
518*a2464ee1SJessica Clarke 	 * Create a fresh copy of the argument array and perform %-substitution;
519*a2464ee1SJessica Clarke 	 * a literal % will be replaced with primary_secondary, and any other
520*a2464ee1SJessica Clarke 	 * string that starts % will have the leading % removed (thus arguments
521*a2464ee1SJessica Clarke 	 * that should start with a % should be escaped with an additional %).
522*a2464ee1SJessica Clarke 	 *
523*a2464ee1SJessica Clarke 	 * Having all % arguments use primary_secondary means that copying
524*a2464ee1SJessica Clarke 	 * either "primary" or "secondary" to it will yield the final argument
525*a2464ee1SJessica Clarke 	 * array for the child in constant time, regardless of how many appear.
526*a2464ee1SJessica Clarke 	 */
527*a2464ee1SJessica Clarke 	newargv = malloc(((size_t)argc + 1) * sizeof(char *));
528*a2464ee1SJessica Clarke 	if (newargv == NULL)
529*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not allocate newargv");
530*a2464ee1SJessica Clarke 
531*a2464ee1SJessica Clarke 	primary_secondary = malloc(MAX(sizeof(primary), sizeof(secondary)));
532*a2464ee1SJessica Clarke 	if (primary_secondary == NULL)
533*a2464ee1SJessica Clarke 		err(EX_OSERR, "could not allocate primary_secondary");
534*a2464ee1SJessica Clarke 
535*a2464ee1SJessica Clarke 	newargv[0] = argv[0];
536*a2464ee1SJessica Clarke 	for (i = 1; i < argc; ++i) {
537*a2464ee1SJessica Clarke 		switch (argv[i][0]) {
538*a2464ee1SJessica Clarke 		case '%':
539*a2464ee1SJessica Clarke 			if (argv[i][1] == '\0')
540*a2464ee1SJessica Clarke 				newargv[i] = primary_secondary;
541*a2464ee1SJessica Clarke 			else
542*a2464ee1SJessica Clarke 				newargv[i] = argv[i] + 1;
543*a2464ee1SJessica Clarke 			break;
544*a2464ee1SJessica Clarke 		default:
545*a2464ee1SJessica Clarke 			newargv[i] = argv[i];
546*a2464ee1SJessica Clarke 			break;
547*a2464ee1SJessica Clarke 		}
548*a2464ee1SJessica Clarke 	}
549*a2464ee1SJessica Clarke 	newargv[argc] = NULL;
550*a2464ee1SJessica Clarke 
551*a2464ee1SJessica Clarke 	/*
552*a2464ee1SJessica Clarke 	 * Temporarily block signals. The parent needs forking, assigning
553*a2464ee1SJessica Clarke 	 * consinfo->pid and, for the first iteration, calling err_set_exit, to
554*a2464ee1SJessica Clarke 	 * be atomic, and the child leader shouldn't have signals re-enabled
555*a2464ee1SJessica Clarke 	 * until it has configured its signal handlers appropriately as the
556*a2464ee1SJessica Clarke 	 * current ones are for the parent's handling of children.
557*a2464ee1SJessica Clarke 	 */
558*a2464ee1SJessica Clarke 	sigfillset(&set);
559*a2464ee1SJessica Clarke 	sigprocmask(SIG_BLOCK, &set, &oset);
560*a2464ee1SJessica Clarke 	STAILQ_FOREACH(consinfo, &consinfos, link)
561*a2464ee1SJessica Clarke 		start_console(consinfo, newargv, primary_secondary,
562*a2464ee1SJessica Clarke 		    &start_barrier, &oset);
563*a2464ee1SJessica Clarke 	sigprocmask(SIG_SETMASK, &oset, NULL);
564*a2464ee1SJessica Clarke 
565*a2464ee1SJessica Clarke 	/* Now ready for children to start */
566*a2464ee1SJessica Clarke 	pipe_barrier_ready(&start_barrier);
567*a2464ee1SJessica Clarke }
568*a2464ee1SJessica Clarke 
569*a2464ee1SJessica Clarke static int
wait_consoles(void)570*a2464ee1SJessica Clarke wait_consoles(void)
571*a2464ee1SJessica Clarke {
572*a2464ee1SJessica Clarke 	pipe_barrier_wait(&wait_first_child_barrier);
573*a2464ee1SJessica Clarke 
574*a2464ee1SJessica Clarke 	/*
575*a2464ee1SJessica Clarke 	 * Once one of our children has exited, kill off the rest and wait for
576*a2464ee1SJessica Clarke 	 * them all to exit. This will also set the foreground process group of
577*a2464ee1SJessica Clarke 	 * the controlling terminal back to ours if it's one of the consoles.
578*a2464ee1SJessica Clarke 	 */
579*a2464ee1SJessica Clarke 	kill_wait_all_consoles(SIGTERM);
580*a2464ee1SJessica Clarke 
581*a2464ee1SJessica Clarke 	if (first_sigchld_consinfo == NULL)
582*a2464ee1SJessica Clarke 		errx(EX_SOFTWARE, "failed to find first child that exited");
583*a2464ee1SJessica Clarke 
584*a2464ee1SJessica Clarke 	return (first_sigchld_consinfo->exitstatus);
585*a2464ee1SJessica Clarke }
586*a2464ee1SJessica Clarke 
587*a2464ee1SJessica Clarke static void __dead2
usage(void)588*a2464ee1SJessica Clarke usage(void)
589*a2464ee1SJessica Clarke {
590*a2464ee1SJessica Clarke 	fprintf(stderr, "usage: %s utility [argument ...]", getprogname());
591*a2464ee1SJessica Clarke 	exit(EX_USAGE);
592*a2464ee1SJessica Clarke }
593*a2464ee1SJessica Clarke 
594*a2464ee1SJessica Clarke int
main(int argc,char ** argv)595*a2464ee1SJessica Clarke main(int argc, char **argv)
596*a2464ee1SJessica Clarke {
597*a2464ee1SJessica Clarke 	int ch, status;
598*a2464ee1SJessica Clarke 
599*a2464ee1SJessica Clarke 	while ((ch = getopt_long(argc, argv, "+h", longopts, NULL)) != -1) {
600*a2464ee1SJessica Clarke 		switch (ch) {
601*a2464ee1SJessica Clarke 		case 'h':
602*a2464ee1SJessica Clarke 		default:
603*a2464ee1SJessica Clarke 			usage();
604*a2464ee1SJessica Clarke 		}
605*a2464ee1SJessica Clarke 	}
606*a2464ee1SJessica Clarke 
607*a2464ee1SJessica Clarke 	argc -= optind;
608*a2464ee1SJessica Clarke 	argv += optind;
609*a2464ee1SJessica Clarke 
610*a2464ee1SJessica Clarke 	if (argc < 2)
611*a2464ee1SJessica Clarke 		usage();
612*a2464ee1SJessica Clarke 
613*a2464ee1SJessica Clarke 	/*
614*a2464ee1SJessica Clarke 	 * Gather the list of enabled consoles from /etc/ttys, ignoring VTYs
615*a2464ee1SJessica Clarke 	 * other than ttyv0 since they're used for other purposes when the
616*a2464ee1SJessica Clarke 	 * installer is running, and there would be no point having multiple
617*a2464ee1SJessica Clarke 	 * copies on each of the multiplexed virtual consoles anyway.
618*a2464ee1SJessica Clarke 	 */
619*a2464ee1SJessica Clarke 	read_consoles();
620*a2464ee1SJessica Clarke 
621*a2464ee1SJessica Clarke 	/*
622*a2464ee1SJessica Clarke 	 * Start the installer on all the consoles. Do not print after this
623*a2464ee1SJessica Clarke 	 * point until our process group is in the foreground again unless
624*a2464ee1SJessica Clarke 	 * necessary (we ignore SIGTTOU so we can print errors, but don't want
625*a2464ee1SJessica Clarke 	 * to garble a child's output).
626*a2464ee1SJessica Clarke 	 */
627*a2464ee1SJessica Clarke 	start_consoles(argc, argv);
628*a2464ee1SJessica Clarke 
629*a2464ee1SJessica Clarke 	/*
630*a2464ee1SJessica Clarke 	 * Wait for one of the installers to exit, kill the rest, become the
631*a2464ee1SJessica Clarke 	 * foreground process group again and get the exit code of the first
632*a2464ee1SJessica Clarke 	 * child to exit.
633*a2464ee1SJessica Clarke 	 */
634*a2464ee1SJessica Clarke 	status = wait_consoles();
635*a2464ee1SJessica Clarke 
636*a2464ee1SJessica Clarke 	/*
637*a2464ee1SJessica Clarke 	 * Reproduce the exit code of the first child to exit, including
638*a2464ee1SJessica Clarke 	 * whether it was a fatal signal or normal termination.
639*a2464ee1SJessica Clarke 	 */
640*a2464ee1SJessica Clarke 	if (WIFSIGNALED(status))
641*a2464ee1SJessica Clarke 		reproduce_signal_death(WTERMSIG(status));
642*a2464ee1SJessica Clarke 
643*a2464ee1SJessica Clarke 	if (WIFEXITED(status))
644*a2464ee1SJessica Clarke 		return (WEXITSTATUS(status));
645*a2464ee1SJessica Clarke 
646*a2464ee1SJessica Clarke 	return (EXIT_FAILURE);
647*a2464ee1SJessica Clarke }
648