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