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
kill_consoles(int sig)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
sigalrm_handler(int code __unused)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
wait_all_consoles(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
kill_wait_all_consoles(int sig)163 kill_wait_all_consoles(int sig)
164 {
165 kill_consoles(sig);
166 wait_all_consoles();
167 }
168
169 static void
kill_wait_all_consoles_err_exit(int eval __unused)170 kill_wait_all_consoles_err_exit(int eval __unused)
171 {
172 kill_wait_all_consoles(SIGTERM);
173 }
174
175 static void __dead2
exit_signal_handler(int code)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
sigchld_handler_reaped_one(pid_t pid,int status)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
sigchld_handler(int code __unused)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 *
read_primary_console(void)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
read_consoles(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
start_console(struct consinfo * consinfo,const char ** argv,char * primary_secondary,struct pipe_barrier * start_barrier,const sigset_t * oset)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
start_consoles(int argc,char ** argv)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
wait_consoles(void)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
usage(void)588 usage(void)
589 {
590 fprintf(stderr, "usage: %s utility [argument ...]", getprogname());
591 exit(EX_USAGE);
592 }
593
594 int
main(int argc,char ** argv)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