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