Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/bsdinstall/runconsoles/runconsoles.c
106843 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2022 Jessica Clarke <[email protected]>
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
649