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