xref: /freebsd/usr.sbin/bsdinstall/runconsoles/runconsoles.c (revision 058ac3e8063366dafa634d9107642e12b038bf09)
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 
77 struct 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 
86 STAILQ_HEAD(consinfo_list, consinfo);
87 
88 static struct consinfo_list consinfos;
89 static struct consinfo *primary_consinfo;
90 static struct consinfo *controlling_consinfo;
91 
92 static struct consinfo * volatile first_sigchld_consinfo;
93 
94 static struct pipe_barrier wait_first_child_barrier;
95 static struct pipe_barrier wait_all_children_barrier;
96 
97 static const char primary[] = "primary";
98 static const char secondary[] = "secondary";
99 
100 static const struct option longopts[] = {
101 	{ "help",	no_argument,	NULL,	'h' },
102 	{ NULL,		0,		NULL,	0 }
103 };
104 
105 static void
106 kill_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 
121 static void
122 sigalrm_handler(int code __unused)
123 {
124 	int saved_errno;
125 
126 	saved_errno = errno;
127 	kill_consoles(SIGKILL);
128 	errno = saved_errno;
129 }
130 
131 static void
132 wait_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 
162 static void
163 kill_wait_all_consoles(int sig)
164 {
165 	kill_consoles(sig);
166 	wait_all_consoles();
167 }
168 
169 static void
170 kill_wait_all_consoles_err_exit(int eval __unused)
171 {
172 	kill_wait_all_consoles(SIGTERM);
173 }
174 
175 static void __dead2
176 exit_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 
200 static void
201 sigchld_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 
236 static void
237 sigchld_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 
249 static const char *
250 read_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 
295 static void
296 read_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 
386 static void
387 start_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 
433 static void
434 start_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 
569 static int
570 wait_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 
587 static void __dead2
588 usage(void)
589 {
590 	fprintf(stderr, "usage: %s utility [argument ...]", getprogname());
591 	exit(EX_USAGE);
592 }
593 
594 int
595 main(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