Commit | Line | Data |
---|---|---|
284ed8f8 KM |
1 | /*- |
2 | * Copyright (c) 1991 The Regents of the University of California. | |
3 | * All rights reserved. | |
4 | * | |
5 | * This code is derived from software contributed to Berkeley by | |
6 | * Donn Seeley at UUNET Technologies, Inc. | |
7 | * | |
8 | * %sccs.include.redist.c% | |
528b0614 DF |
9 | */ |
10 | ||
37c640e2 | 11 | #ifndef lint |
284ed8f8 KM |
12 | char copyright[] = |
13 | "@(#) Copyright (c) 1991 The Regents of the University of California.\n\ | |
14 | All rights reserved.\n"; | |
15 | #endif /* not lint */ | |
16 | ||
17 | #ifndef lint | |
6f2ac96c | 18 | static char sccsid[] = "@(#)init.c 6.10 (Berkeley) %G%"; |
284ed8f8 | 19 | #endif /* not lint */ |
37c640e2 | 20 | |
7c8ab0e6 | 21 | #include <sys/types.h> |
284ed8f8 KM |
22 | #include <sys/wait.h> |
23 | #include <db.h> | |
f570e1ff | 24 | #include <errno.h> |
284ed8f8 KM |
25 | #include <fcntl.h> |
26 | #include <signal.h> | |
284ed8f8 KM |
27 | #include <syslog.h> |
28 | #include <time.h> | |
9479aa87 | 29 | #include <ttyent.h> |
284ed8f8 | 30 | #include <unistd.h> |
869bbd31 KB |
31 | #include <stdio.h> |
32 | #include <stdlib.h> | |
33 | #include <string.h> | |
284ed8f8 KM |
34 | |
35 | #ifdef __STDC__ | |
36 | #include <stdarg.h> | |
37 | #else | |
38 | #include <varargs.h> | |
39 | #endif | |
40 | ||
41 | #ifdef SECURE | |
42 | #include <pwd.h> | |
43 | #endif | |
44 | ||
9074d4b1 | 45 | #include "pathnames.h" |
7c8ab0e6 | 46 | |
284ed8f8 KM |
47 | /* |
48 | * Until the mythical util.h arrives... | |
49 | */ | |
50 | extern int login_tty __P((int)); | |
51 | extern int logout __P((const char *)); | |
52 | extern void logwtmp __P((const char *, const char *, const char *)); | |
7c8ab0e6 | 53 | |
284ed8f8 KM |
54 | /* |
55 | * Sleep times; used to prevent thrashing. | |
56 | */ | |
b5775271 | 57 | #define GETTY_SPACING 10 /* fork getty on a port every N secs */ |
284ed8f8 KM |
58 | #define WINDOW_WAIT 3 /* wait N secs after starting window */ |
59 | #define STALL_TIMEOUT 30 /* wait N secs after warning */ | |
b5775271 | 60 | #define DEATH_WATCH 10 /* wait N secs for procs to die */ |
7c8ab0e6 | 61 | |
284ed8f8 KM |
62 | void handle __P((sig_t, ...)); |
63 | void delset __P((sigset_t *, ...)); | |
64 | ||
65 | void stall __P((char *, ...)); | |
66 | void warning __P((char *, ...)); | |
67 | void emergency __P((char *, ...)); | |
68 | void disaster __P((int)); | |
69 | ||
70 | /* | |
71 | * We really need a recursive typedef... | |
72 | * The following at least guarantees that the return type of (*state_t)() | |
73 | * is sufficiently wide to hold a function pointer. | |
74 | */ | |
75 | typedef long (*state_func_t) __P((void)); | |
76 | typedef state_func_t (*state_t) __P((void)); | |
77 | ||
78 | state_func_t single_user __P((void)); | |
79 | state_func_t runcom __P((void)); | |
80 | state_func_t read_ttys __P((void)); | |
81 | state_func_t multi_user __P((void)); | |
82 | state_func_t clean_ttys __P((void)); | |
83 | state_func_t catatonia __P((void)); | |
84 | state_func_t death __P((void)); | |
85 | ||
86 | enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT; | |
87 | ||
88 | void transition __P((state_t)); | |
89 | state_t requested_transition = runcom; | |
d0ca48ff | 90 | |
284ed8f8 KM |
91 | void setctty __P((char *)); |
92 | ||
93 | typedef struct session { | |
94 | int se_index; /* index of entry in ttys file */ | |
95 | pid_t se_process; /* controlling process */ | |
96 | time_t se_started; /* used to avoid thrashing */ | |
97 | int se_flags; /* status of session */ | |
98 | #define SE_SHUTDOWN 0x1 /* session won't be restarted */ | |
99 | char *se_device; /* filename of port */ | |
100 | char *se_getty; /* what to run on that port */ | |
101 | char **se_getty_argv; /* pre-parsed argument array */ | |
102 | char *se_window; /* window system (started only once) */ | |
103 | char **se_window_argv; /* pre-parsed argument array */ | |
104 | struct session *se_prev; | |
105 | struct session *se_next; | |
106 | } session_t; | |
107 | ||
108 | void free_session __P((session_t *)); | |
109 | session_t *new_session __P((session_t *, int, struct ttyent *)); | |
110 | session_t *sessions; | |
111 | ||
112 | char **construct_argv __P((char *)); | |
113 | void start_window_system __P((session_t *)); | |
b5775271 | 114 | void collect_child __P((int)); |
284ed8f8 | 115 | pid_t start_getty __P((session_t *)); |
284ed8f8 KM |
116 | void transition_handler __P((int)); |
117 | void alrm_handler __P((int)); | |
118 | int clang; | |
119 | ||
120 | int start_logger __P((void)); | |
121 | void clear_session_logs __P((session_t *)); | |
122 | int logger_enable; | |
123 | ||
b5775271 | 124 | int start_session_db __P((void)); |
284ed8f8 KM |
125 | void add_session __P((session_t *)); |
126 | void del_session __P((session_t *)); | |
127 | session_t *find_session __P((pid_t)); | |
128 | DB *session_db; | |
129 | ||
b5775271 KM |
130 | /* |
131 | * The mother of all processes. | |
132 | */ | |
284ed8f8 | 133 | int |
fecfa7b3 | 134 | main(argc, argv) |
284ed8f8 | 135 | int argc; |
fecfa7b3 | 136 | char **argv; |
7c8ab0e6 | 137 | { |
284ed8f8 | 138 | int c; |
b5775271 | 139 | struct sigaction sa; |
284ed8f8 KM |
140 | sigset_t mask; |
141 | ||
869bbd31 KB |
142 | |
143 | /* Dispose of random users. */ | |
144 | if (getuid() != 0) { | |
145 | (void)fprintf(stderr, "init: %s\n", strerror(EPERM)); | |
146 | exit (1); | |
147 | } | |
148 | ||
149 | /* System V users like to reexec init. */ | |
150 | if (getpid() != 1) { | |
151 | (void)fprintf(stderr, "init: already running\n"); | |
152 | exit (1); | |
153 | } | |
4d6fe690 MK |
154 | |
155 | /* | |
284ed8f8 KM |
156 | * Note that this does NOT open a file... |
157 | * Does 'init' deserve its own facility number? | |
4d6fe690 | 158 | */ |
b5775271 KM |
159 | openlog("init", LOG_CONS|LOG_ODELAY, LOG_AUTH); |
160 | ||
161 | /* | |
162 | * Create an initial session. | |
163 | */ | |
164 | if (setsid() < 0) | |
165 | syslog(LOG_ERR, "setsid failed (initial) %m"); | |
8d3a6b89 | 166 | |
284ed8f8 KM |
167 | /* |
168 | * This code assumes that we always get arguments through flags, | |
169 | * never through bits set in some random machine register. | |
170 | */ | |
171 | while ((c = getopt(argc, argv, "sf")) != -1) | |
172 | switch (c) { | |
173 | case 's': | |
174 | requested_transition = single_user; | |
175 | break; | |
4d6fe690 | 176 | case 'f': |
284ed8f8 | 177 | runcom_mode = FASTBOOT; |
8d3a6b89 | 178 | break; |
284ed8f8 KM |
179 | default: |
180 | warning("unrecognized flag '-%c'", c); | |
8d3a6b89 TL |
181 | break; |
182 | } | |
284ed8f8 KM |
183 | |
184 | if (optind != argc) | |
185 | warning("ignoring excess arguments"); | |
186 | ||
187 | /* | |
188 | * We catch or block signals rather than ignore them, | |
189 | * so that they get reset on exec. | |
190 | */ | |
191 | handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV, | |
192 | SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ, 0); | |
193 | handle(transition_handler, SIGHUP, SIGTERM, SIGTSTP, 0); | |
194 | handle(alrm_handler, SIGALRM, 0); | |
284ed8f8 | 195 | sigfillset(&mask); |
b5775271 KM |
196 | delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS, |
197 | SIGXCPU, SIGXFSZ, SIGHUP, SIGTERM, SIGTSTP, SIGALRM, 0); | |
284ed8f8 | 198 | sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); |
b5775271 KM |
199 | sigemptyset(&sa.sa_mask); |
200 | sa.sa_flags = 0; | |
201 | sa.sa_handler = SIG_IGN; | |
202 | (void) sigaction(SIGTTIN, &sa, (struct sigaction *)0); | |
203 | (void) sigaction(SIGTTOU, &sa, (struct sigaction *)0); | |
284ed8f8 KM |
204 | |
205 | /* | |
206 | * Paranoia. | |
207 | */ | |
208 | close(0); | |
209 | close(1); | |
210 | close(2); | |
211 | ||
212 | /* | |
213 | * Start the state machine. | |
214 | */ | |
215 | transition(requested_transition); | |
216 | ||
217 | /* | |
218 | * Should never reach here. | |
219 | */ | |
220 | return 1; | |
221 | } | |
222 | ||
b5775271 KM |
223 | /* |
224 | * Associate a function with a signal handler. | |
225 | */ | |
284ed8f8 KM |
226 | void |
227 | #ifdef __STDC__ | |
228 | handle(sig_t handler, ...) | |
229 | #else | |
230 | handle(va_alist) | |
231 | va_dcl | |
232 | #endif | |
233 | { | |
234 | int sig; | |
235 | struct sigaction sa; | |
236 | int mask_everything; | |
237 | va_list ap; | |
238 | #ifndef __STDC__ | |
239 | sig_t handler; | |
240 | ||
241 | va_start(ap); | |
242 | handler = va_arg(ap, sig_t); | |
243 | #else | |
244 | va_start(ap, handler); | |
245 | #endif | |
246 | ||
247 | sa.sa_handler = handler; | |
248 | sigfillset(&mask_everything); | |
249 | ||
250 | while (sig = va_arg(ap, int)) { | |
251 | sa.sa_mask = mask_everything; | |
252 | /* XXX SA_RESTART? */ | |
253 | sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0; | |
254 | sigaction(sig, &sa, (struct sigaction *) 0); | |
7c8ab0e6 BJ |
255 | } |
256 | } | |
257 | ||
b5775271 KM |
258 | /* |
259 | * Delete a set of signals from a mask. | |
260 | */ | |
284ed8f8 KM |
261 | void |
262 | #ifdef __STDC__ | |
263 | delset(sigset_t *maskp, ...) | |
264 | #else | |
265 | delset(va_alist) | |
266 | va_dcl | |
267 | #endif | |
268 | { | |
269 | int sig; | |
270 | va_list ap; | |
271 | #ifndef __STDC__ | |
272 | sigset_t *maskp; | |
273 | ||
274 | va_start(ap); | |
275 | maskp = va_arg(ap, sigset_t *); | |
276 | #else | |
277 | va_start(ap, maskp); | |
278 | #endif | |
279 | ||
280 | while (sig = va_arg(ap, int)) | |
281 | sigdelset(maskp, sig); | |
282 | } | |
abf0db3c | 283 | |
284ed8f8 KM |
284 | /* |
285 | * Log a message and sleep for a while (to give someone an opportunity | |
286 | * to read it and to save log or hardcopy output if the problem is chronic). | |
bb56e935 | 287 | * NB: should send a message to the session logger to avoid blocking. |
284ed8f8 KM |
288 | */ |
289 | void | |
290 | #ifdef __STDC__ | |
291 | stall(char *message, ...) | |
292 | #else | |
293 | stall(va_alist) | |
294 | va_dcl | |
295 | #endif | |
7c8ab0e6 | 296 | { |
284ed8f8 KM |
297 | pid_t pid; |
298 | va_list ap; | |
299 | #ifndef __STDC__ | |
300 | char *message; | |
301 | ||
302 | va_start(ap); | |
303 | message = va_arg(ap, char *); | |
304 | #else | |
305 | va_start(ap, message); | |
306 | #endif | |
307 | ||
bb56e935 | 308 | vsyslog(LOG_ALERT, message, ap); |
284ed8f8 KM |
309 | va_end(ap); |
310 | sleep(STALL_TIMEOUT); | |
abf0db3c BJ |
311 | } |
312 | ||
284ed8f8 KM |
313 | /* |
314 | * Like stall(), but doesn't sleep. | |
315 | * If cpp had variadic macros, the two functions could be #defines for another. | |
bb56e935 | 316 | * NB: should send a message to the session logger to avoid blocking. |
284ed8f8 | 317 | */ |
caf94fd3 | 318 | void |
284ed8f8 KM |
319 | #ifdef __STDC__ |
320 | warning(char *message, ...) | |
321 | #else | |
322 | warning(va_alist) | |
323 | va_dcl | |
324 | #endif | |
abf0db3c | 325 | { |
284ed8f8 KM |
326 | va_list ap; |
327 | #ifndef __STDC__ | |
328 | char *message; | |
329 | ||
330 | va_start(ap); | |
331 | message = va_arg(ap, char *); | |
332 | #else | |
333 | va_start(ap, message); | |
334 | #endif | |
abf0db3c | 335 | |
bb56e935 | 336 | vsyslog(LOG_ALERT, message, ap); |
284ed8f8 | 337 | va_end(ap); |
abf0db3c BJ |
338 | } |
339 | ||
284ed8f8 | 340 | /* |
bb56e935 KM |
341 | * Log an emergency message. |
342 | * NB: should send a message to the session logger to avoid blocking. | |
284ed8f8 KM |
343 | */ |
344 | void | |
345 | #ifdef __STDC__ | |
346 | emergency(char *message, ...) | |
347 | #else | |
348 | emergency(va_alist) | |
349 | va_dcl | |
350 | #endif | |
abf0db3c | 351 | { |
284ed8f8 KM |
352 | va_list ap; |
353 | #ifndef __STDC__ | |
354 | char *message; | |
abf0db3c | 355 | |
284ed8f8 KM |
356 | va_start(ap); |
357 | message = va_arg(ap, char *); | |
358 | #else | |
359 | va_start(ap, message); | |
360 | #endif | |
361 | ||
362 | vsyslog(LOG_EMERG, message, ap); | |
363 | va_end(ap); | |
7c8ab0e6 BJ |
364 | } |
365 | ||
b5775271 KM |
366 | /* |
367 | * Catch an unexpected signal. | |
368 | */ | |
284ed8f8 KM |
369 | void |
370 | disaster(sig) | |
371 | int sig; | |
7c8ab0e6 | 372 | { |
284ed8f8 KM |
373 | emergency("fatal signal: %s", |
374 | sig < (unsigned) NSIG ? sys_siglist[sig] : "unknown signal"); | |
375 | ||
376 | sleep(STALL_TIMEOUT); | |
377 | _exit(sig); /* reboot */ | |
7c8ab0e6 BJ |
378 | } |
379 | ||
284ed8f8 KM |
380 | /* |
381 | * Change states in the finite state machine. | |
382 | * The initial state is passed as an argument. | |
383 | */ | |
384 | void | |
385 | transition(s) | |
386 | state_t s; | |
387 | { | |
388 | for (;;) | |
389 | s = (state_t) (*s)(); | |
390 | } | |
391 | ||
392 | /* | |
393 | * We send requests for session logging to another process for two reasons. | |
394 | * First, we don't want to block if the log files go away (e.g. because | |
395 | * one or more are on hard-mounted NFS systems whose server crashes). | |
396 | * Second, despite all the crud already contained in init, it still isn't | |
397 | * right that init should care about session logging record formats and files. | |
398 | * We could use explicit 'Unix' IPC for this, but let's try to be POSIX... | |
399 | */ | |
400 | int | |
401 | start_logger() | |
7c8ab0e6 | 402 | { |
284ed8f8 KM |
403 | static char *argv[] = { _PATH_SLOGGER, 0 }; |
404 | int fd, pfd[2]; | |
405 | pid_t pid; | |
406 | sigset_t mask; | |
407 | ||
408 | if (pipe(pfd) == -1) { | |
409 | warning("session logging disabled: can't make pipe to %s: %m", | |
410 | argv[0]); | |
411 | return -1; | |
412 | } | |
413 | if ((pid = fork()) == -1) { | |
414 | emergency("session logging disabled: can't fork for %s: %m", | |
415 | argv[0]); | |
416 | return -1; | |
417 | } | |
7c8ab0e6 | 418 | |
d0ca48ff | 419 | if (pid == 0) { |
284ed8f8 KM |
420 | close(pfd[1]); |
421 | if (pfd[0] != 0) { | |
422 | dup2(pfd[0], 0); | |
423 | close(pfd[0]); | |
424 | } | |
425 | if ((fd = open(_PATH_DEVNULL, O_WRONLY)) != -1) { | |
426 | if (fd != 1) | |
427 | dup2(fd, 1); | |
428 | if (fd != 2) | |
429 | dup2(fd, 2); | |
430 | if (fd != 1 && fd != 2) | |
c9d9fdd4 | 431 | close(fd); |
284ed8f8 KM |
432 | } else { |
433 | /* paranoid */ | |
434 | close(1); | |
435 | close(2); | |
c9d9fdd4 | 436 | } |
284ed8f8 KM |
437 | sigemptyset(&mask); |
438 | sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); | |
439 | execv(argv[0], argv); | |
440 | stall("can't exec %s: %m", argv[0]); | |
441 | _exit(1); | |
7c8ab0e6 | 442 | } |
284ed8f8 KM |
443 | |
444 | close(pfd[0]); | |
445 | fcntl(pfd[1], F_SETFD, FD_CLOEXEC); | |
446 | fcntl(pfd[1], F_SETFL, O_NONBLOCK); | |
447 | ||
448 | return pfd[1]; | |
7c8ab0e6 BJ |
449 | } |
450 | ||
b5775271 KM |
451 | /* |
452 | * Close out the accounting files for a login session. | |
bb56e935 | 453 | * NB: should send a message to the session logger to avoid blocking. |
b5775271 | 454 | */ |
284ed8f8 KM |
455 | void |
456 | clear_session_logs(sp) | |
457 | session_t *sp; | |
7c8ab0e6 | 458 | { |
6f2ac96c CT |
459 | char *line = sp->se_device + sizeof(_PATH_DEV) - 1; |
460 | ||
461 | if (logout(line)) | |
462 | logwtmp(line, "", ""); | |
7c8ab0e6 BJ |
463 | } |
464 | ||
d0ca48ff | 465 | /* |
284ed8f8 KM |
466 | * Start a session and allocate a controlling terminal. |
467 | * Only called by children of init after forking. | |
d0ca48ff | 468 | */ |
caf94fd3 | 469 | void |
284ed8f8 KM |
470 | setctty(name) |
471 | char *name; | |
d0ca48ff | 472 | { |
284ed8f8 KM |
473 | int fd; |
474 | ||
b5775271 | 475 | (void) revoke(name); |
284ed8f8 KM |
476 | if ((fd = open(name, O_RDWR)) == -1) { |
477 | stall("can't open %s: %m", name); | |
478 | _exit(1); | |
479 | } | |
480 | if (login_tty(fd) == -1) { | |
481 | stall("can't get %s for controlling terminal: %m", name); | |
482 | _exit(1); | |
483 | } | |
484 | } | |
485 | ||
b5775271 KM |
486 | /* |
487 | * Bring the system up single user. | |
488 | */ | |
284ed8f8 KM |
489 | state_func_t |
490 | single_user() | |
491 | { | |
492 | pid_t pid, wpid; | |
493 | int status; | |
494 | sigset_t mask; | |
495 | char *shell = _PATH_BSHELL; | |
496 | char *argv[2]; | |
497 | #ifdef SECURE | |
498 | struct ttyent *typ; | |
499 | struct passwd *pp; | |
500 | static const char banner[] = | |
501 | "Enter root password, or ^D to go multi-user\n"; | |
502 | char *password; | |
503 | #endif | |
504 | ||
505 | if ((pid = fork()) == 0) { | |
506 | /* | |
507 | * Start the single user session. | |
508 | */ | |
509 | setctty(_PATH_CONSOLE); | |
510 | ||
511 | #ifdef SECURE | |
512 | /* | |
513 | * Check the root password. | |
514 | * We don't care if the console is 'on' by default; | |
515 | * it's the only tty that can be 'off' and 'secure'. | |
516 | */ | |
517 | typ = getttynam("console"); | |
518 | pp = getpwnam("root"); | |
519 | if (typ && (typ->ty_status & TTY_SECURE) == 0 && pp) { | |
520 | write(2, banner, sizeof banner - 1); | |
521 | for (;;) { | |
522 | password = getpass("Password:"); | |
523 | if (password == 0 || *password == '\0') | |
524 | _exit(0); | |
f382c801 | 525 | password = crypt(password, pp->pw_passwd); |
284ed8f8 KM |
526 | if (strcmp(password, pp->pw_passwd) == 0) |
527 | break; | |
068e4187 | 528 | } |
d0ca48ff | 529 | } |
284ed8f8 KM |
530 | #if 0 |
531 | /* | |
532 | * Make the single-user shell be root's standard shell? | |
533 | */ | |
534 | if (pp && pp->pw_shell) | |
535 | shell = pp->pw_shell; | |
536 | #endif | |
537 | endttyent(); | |
538 | endpwent(); | |
539 | #endif | |
068e4187 | 540 | |
25efc242 | 541 | /* |
284ed8f8 KM |
542 | * Unblock signals. |
543 | * We catch all the interesting ones, | |
544 | * and those are reset to SIG_DFL on exec. | |
25efc242 | 545 | */ |
284ed8f8 KM |
546 | sigemptyset(&mask); |
547 | sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); | |
548 | ||
25efc242 | 549 | /* |
284ed8f8 KM |
550 | * Fire off a shell. |
551 | * If the default one doesn't work, try the Bourne shell. | |
25efc242 | 552 | */ |
284ed8f8 KM |
553 | argv[0] = "-sh"; |
554 | argv[1] = 0; | |
555 | execv(shell, argv); | |
556 | emergency("can't exec %s for single user: %m", shell); | |
557 | execv(_PATH_BSHELL, argv); | |
558 | emergency("can't exec %s for single user: %m", _PATH_BSHELL); | |
559 | sleep(STALL_TIMEOUT); | |
560 | _exit(1); | |
561 | } | |
562 | ||
563 | if (pid == -1) { | |
564 | /* | |
565 | * We are seriously hosed. Do our best. | |
566 | */ | |
567 | emergency("can't fork single-user shell, trying again"); | |
568 | while (waitpid(-1, (int *) 0, WNOHANG) > 0) | |
b249bb58 | 569 | continue; |
284ed8f8 KM |
570 | return (state_func_t) single_user; |
571 | } | |
572 | ||
fd1a77cb | 573 | requested_transition = 0; |
b5775271 KM |
574 | do { |
575 | if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) | |
576 | collect_child(wpid); | |
577 | if (wpid == -1) { | |
578 | if (errno == EINTR) | |
579 | continue; | |
284ed8f8 KM |
580 | warning("wait for single-user shell failed: %m; restarting"); |
581 | return (state_func_t) single_user; | |
582 | } | |
583 | if (wpid == pid && WIFSTOPPED(status)) { | |
584 | warning("init: shell stopped, restarting\n"); | |
585 | kill(pid, SIGCONT); | |
b5775271 | 586 | wpid = -1; |
25efc242 | 587 | } |
fd1a77cb TH |
588 | } while (wpid != pid && !requested_transition); |
589 | ||
590 | if (requested_transition) | |
591 | return (state_func_t) requested_transition; | |
284ed8f8 KM |
592 | |
593 | if (!WIFEXITED(status)) { | |
850e7b14 TH |
594 | if (WTERMSIG(status) == SIGKILL) { |
595 | /* | |
596 | * reboot(8) killed shell? | |
597 | */ | |
598 | warning("single user shell terminated."); | |
599 | sleep(STALL_TIMEOUT); | |
600 | _exit(0); | |
601 | } else { | |
602 | warning("single user shell terminated, restarting"); | |
603 | return (state_func_t) single_user; | |
604 | } | |
284ed8f8 KM |
605 | } |
606 | ||
607 | runcom_mode = FASTBOOT; | |
608 | return (state_func_t) runcom; | |
609 | } | |
610 | ||
b5775271 KM |
611 | /* |
612 | * Run the system startup script. | |
613 | */ | |
284ed8f8 KM |
614 | state_func_t |
615 | runcom() | |
616 | { | |
617 | pid_t pid, wpid; | |
618 | int status; | |
619 | char *argv[4]; | |
b5775271 | 620 | struct sigaction sa; |
284ed8f8 KM |
621 | |
622 | if ((pid = fork()) == 0) { | |
b5775271 KM |
623 | sigemptyset(&sa.sa_mask); |
624 | sa.sa_flags = 0; | |
625 | sa.sa_handler = SIG_IGN; | |
626 | (void) sigaction(SIGTSTP, &sa, (struct sigaction *)0); | |
627 | (void) sigaction(SIGHUP, &sa, (struct sigaction *)0); | |
628 | ||
284ed8f8 KM |
629 | setctty(_PATH_CONSOLE); |
630 | ||
631 | argv[0] = "sh"; | |
632 | argv[1] = _PATH_RUNCOM; | |
633 | argv[2] = runcom_mode == AUTOBOOT ? "autoboot" : 0; | |
634 | argv[3] = 0; | |
635 | ||
b5775271 | 636 | sigprocmask(SIG_SETMASK, &sa.sa_mask, (sigset_t *) 0); |
284ed8f8 KM |
637 | |
638 | execv(_PATH_BSHELL, argv); | |
639 | stall("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNCOM); | |
640 | _exit(1); /* force single user mode */ | |
641 | } | |
642 | ||
643 | if (pid == -1) { | |
644 | emergency("can't fork for %s on %s: %m", | |
645 | _PATH_BSHELL, _PATH_RUNCOM); | |
b5775271 | 646 | while (waitpid(-1, (int *) 0, WNOHANG) > 0) |
b249bb58 | 647 | continue; |
284ed8f8 KM |
648 | sleep(STALL_TIMEOUT); |
649 | return (state_func_t) single_user; | |
650 | } | |
651 | ||
652 | /* | |
b5775271 | 653 | * Copied from single_user(). This is a bit paranoid. |
284ed8f8 | 654 | */ |
b5775271 KM |
655 | do { |
656 | if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) | |
657 | collect_child(wpid); | |
658 | if (wpid == -1) { | |
659 | if (errno == EINTR) | |
660 | continue; | |
284ed8f8 KM |
661 | warning("wait for %s on %s failed: %m; going to single user mode", |
662 | _PATH_BSHELL, _PATH_RUNCOM); | |
663 | return (state_func_t) single_user; | |
664 | } | |
665 | if (wpid == pid && WIFSTOPPED(status)) { | |
666 | warning("init: %s on %s stopped, restarting\n", | |
667 | _PATH_BSHELL, _PATH_RUNCOM); | |
668 | kill(pid, SIGCONT); | |
b5775271 | 669 | wpid = -1; |
d0ca48ff | 670 | } |
b5775271 | 671 | } while (wpid != pid); |
284ed8f8 KM |
672 | |
673 | if (!WIFEXITED(status)) { | |
674 | warning("%s on %s terminated abnormally, going to single user mode", | |
675 | _PATH_BSHELL, _PATH_RUNCOM); | |
676 | return (state_func_t) single_user; | |
677 | } | |
678 | ||
679 | if (WEXITSTATUS(status)) | |
680 | return (state_func_t) single_user; | |
681 | ||
682 | runcom_mode = AUTOBOOT; /* the default */ | |
bb56e935 KM |
683 | /* NB: should send a message to the session logger to avoid blocking. */ |
684 | logwtmp("~", "reboot", ""); | |
284ed8f8 KM |
685 | return (state_func_t) read_ttys; |
686 | } | |
687 | ||
688 | /* | |
b5775271 KM |
689 | * Open the session database. |
690 | * | |
691 | * NB: We could pass in the size here; is it necessary? | |
284ed8f8 | 692 | */ |
b5775271 | 693 | int |
284ed8f8 KM |
694 | start_session_db() |
695 | { | |
b5775271 KM |
696 | if (session_db && (*session_db->close)(session_db)) |
697 | emergency("session database close: %s", strerror(errno)); | |
2bf636e6 | 698 | if ((session_db = dbopen(NULL, O_RDWR, 0, DB_HASH, NULL)) == 0) { |
b5775271 KM |
699 | emergency("session database open: %s", strerror(errno)); |
700 | return (1); | |
701 | } | |
702 | return (0); | |
703 | ||
284ed8f8 KM |
704 | } |
705 | ||
b5775271 KM |
706 | /* |
707 | * Add a new login session. | |
708 | */ | |
284ed8f8 KM |
709 | void |
710 | add_session(sp) | |
711 | session_t *sp; | |
712 | { | |
713 | DBT key; | |
714 | DBT data; | |
715 | ||
716 | key.data = &sp->se_process; | |
717 | key.size = sizeof sp->se_process; | |
718 | data.data = &sp; | |
719 | data.size = sizeof sp; | |
720 | ||
2bf636e6 | 721 | if ((*session_db->put)(session_db, &key, &data, 0)) |
b5775271 | 722 | emergency("insert %d: %s", sp->se_process, strerror(errno)); |
d0ca48ff SL |
723 | } |
724 | ||
b5775271 KM |
725 | /* |
726 | * Delete an old login session. | |
727 | */ | |
284ed8f8 KM |
728 | void |
729 | del_session(sp) | |
730 | session_t *sp; | |
731 | { | |
732 | DBT key; | |
733 | ||
734 | key.data = &sp->se_process; | |
735 | key.size = sizeof sp->se_process; | |
736 | ||
b5775271 KM |
737 | if ((*session_db->del)(session_db, &key, 0)) |
738 | emergency("delete %d: %s", sp->se_process, strerror(errno)); | |
284ed8f8 KM |
739 | } |
740 | ||
b5775271 KM |
741 | /* |
742 | * Look up a login session by pid. | |
743 | */ | |
284ed8f8 KM |
744 | session_t * |
745 | #ifdef __STDC__ | |
746 | find_session(pid_t pid) | |
747 | #else | |
748 | find_session(pid) | |
749 | pid_t pid; | |
750 | #endif | |
7c8ab0e6 | 751 | { |
284ed8f8 KM |
752 | DBT key; |
753 | DBT data; | |
b249bb58 | 754 | session_t *ret; |
7c8ab0e6 | 755 | |
284ed8f8 KM |
756 | key.data = &pid; |
757 | key.size = sizeof pid; | |
758 | if ((*session_db->get)(session_db, &key, &data, 0) != 0) | |
759 | return 0; | |
b249bb58 CT |
760 | bcopy(data.data, (char *)&ret, sizeof(ret)); |
761 | return ret; | |
284ed8f8 KM |
762 | } |
763 | ||
b5775271 KM |
764 | /* |
765 | * Construct an argument vector from a command line. | |
766 | */ | |
284ed8f8 KM |
767 | char ** |
768 | construct_argv(command) | |
769 | char *command; | |
770 | { | |
771 | register int argc = 0; | |
772 | register char **argv = (char **) malloc(((strlen(command) + 1) / 2 + 1) | |
773 | * sizeof (char *)); | |
774 | static const char separators[] = " \t"; | |
775 | ||
776 | if ((argv[argc++] = strtok(command, separators)) == 0) | |
777 | return 0; | |
778 | while (argv[argc++] = strtok((char *) 0, separators)) | |
b249bb58 | 779 | continue; |
284ed8f8 KM |
780 | return argv; |
781 | } | |
782 | ||
b5775271 KM |
783 | /* |
784 | * Deallocate a session descriptor. | |
785 | */ | |
284ed8f8 KM |
786 | void |
787 | free_session(sp) | |
788 | register session_t *sp; | |
789 | { | |
790 | free(sp->se_device); | |
791 | free(sp->se_getty); | |
792 | free(sp->se_getty_argv); | |
793 | if (sp->se_window) { | |
794 | free(sp->se_window); | |
795 | free(sp->se_window_argv); | |
7c8ab0e6 | 796 | } |
284ed8f8 | 797 | free(sp); |
7c8ab0e6 BJ |
798 | } |
799 | ||
b5775271 KM |
800 | /* |
801 | * Allocate a new session descriptor. | |
802 | */ | |
284ed8f8 KM |
803 | session_t * |
804 | new_session(sprev, session_index, typ) | |
805 | session_t *sprev; | |
806 | int session_index; | |
807 | register struct ttyent *typ; | |
7c8ab0e6 | 808 | { |
284ed8f8 KM |
809 | register session_t *sp; |
810 | ||
811 | if ((typ->ty_status & TTY_ON) == 0 || | |
812 | typ->ty_name == 0 || | |
813 | typ->ty_getty == 0) | |
814 | return 0; | |
815 | ||
816 | sp = (session_t *) malloc(sizeof (session_t)); | |
817 | ||
818 | sp->se_index = session_index; | |
819 | sp->se_process = 0; | |
820 | sp->se_started = 0; | |
821 | sp->se_flags = 0; | |
822 | sp->se_window = 0; | |
823 | ||
6f2ac96c CT |
824 | sp->se_device = malloc(sizeof(_PATH_DEV) + strlen(typ->ty_name)); |
825 | (void) sprintf(sp->se_device, "%s%s", _PATH_DEV, typ->ty_name); | |
284ed8f8 KM |
826 | |
827 | sp->se_getty = strdup(typ->ty_getty); | |
828 | sp->se_getty_argv = construct_argv(sp->se_getty); | |
829 | if (sp->se_getty_argv == 0) { | |
830 | warning("can't parse getty for port %s", | |
831 | sp->se_device); | |
832 | free_session(sp); | |
833 | return 0; | |
43236707 | 834 | } |
284ed8f8 KM |
835 | if (typ->ty_window) { |
836 | sp->se_window = strdup(typ->ty_window); | |
837 | sp->se_window_argv = construct_argv(sp->se_window); | |
838 | if (sp->se_window_argv == 0) { | |
839 | warning("can't parse window for port %s", | |
840 | sp->se_device); | |
841 | free_session(sp); | |
842 | return 0; | |
ef9abb5f | 843 | } |
7c8ab0e6 | 844 | } |
284ed8f8 KM |
845 | |
846 | sp->se_next = 0; | |
847 | if (sprev == 0) { | |
848 | sessions = sp; | |
849 | sp->se_prev = 0; | |
850 | } else { | |
851 | sprev->se_next = sp; | |
852 | sp->se_prev = sprev; | |
853 | } | |
854 | ||
855 | return sp; | |
7c8ab0e6 BJ |
856 | } |
857 | ||
b5775271 KM |
858 | /* |
859 | * Walk the list of ttys and create sessions for each active line. | |
860 | */ | |
284ed8f8 KM |
861 | state_func_t |
862 | read_ttys() | |
7c8ab0e6 | 863 | { |
284ed8f8 KM |
864 | int session_index = 0; |
865 | register session_t *sp, *snext; | |
866 | register struct ttyent *typ; | |
867 | ||
868 | /* | |
869 | * Destroy any previous session state. | |
870 | * There shouldn't be any, but just in case... | |
871 | */ | |
872 | for (sp = sessions; sp; sp = snext) { | |
873 | if (sp->se_process) | |
874 | clear_session_logs(sp); | |
875 | snext = sp->se_next; | |
876 | free_session(sp); | |
7c8ab0e6 | 877 | } |
284ed8f8 | 878 | sessions = 0; |
b5775271 KM |
879 | if (start_session_db()) |
880 | return (state_func_t) single_user; | |
284ed8f8 KM |
881 | |
882 | /* | |
883 | * Allocate a session entry for each active port. | |
884 | * Note that sp starts at 0. | |
885 | */ | |
886 | while (typ = getttyent()) | |
887 | if (snext = new_session(sp, ++session_index, typ)) | |
888 | sp = snext; | |
889 | ||
890 | endttyent(); | |
891 | ||
892 | logger_enable = 1; | |
893 | return (state_func_t) multi_user; | |
7c8ab0e6 BJ |
894 | } |
895 | ||
b5775271 KM |
896 | /* |
897 | * Start a window system running. | |
898 | */ | |
caf94fd3 | 899 | void |
284ed8f8 KM |
900 | start_window_system(sp) |
901 | session_t *sp; | |
7c8ab0e6 | 902 | { |
284ed8f8 KM |
903 | pid_t pid; |
904 | sigset_t mask; | |
f570e1ff | 905 | |
284ed8f8 KM |
906 | if ((pid = fork()) == -1) { |
907 | emergency("can't fork for window system on port %s: %m", | |
908 | sp->se_device); | |
909 | /* hope that getty fails and we can try again */ | |
910 | return; | |
911 | } | |
d0ca48ff | 912 | |
284ed8f8 KM |
913 | if (pid) |
914 | return; | |
915 | ||
916 | sigemptyset(&mask); | |
917 | sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); | |
918 | ||
b5775271 KM |
919 | if (setsid() < 0) |
920 | emergency("setsid failed (window) %m"); | |
921 | ||
284ed8f8 KM |
922 | execv(sp->se_window_argv[0], sp->se_window_argv); |
923 | stall("can't exec window system '%s' for port %s: %m", | |
924 | sp->se_window_argv[0], sp->se_device); | |
925 | _exit(1); | |
926 | } | |
927 | ||
b5775271 KM |
928 | /* |
929 | * Start a login session running. | |
930 | */ | |
284ed8f8 KM |
931 | pid_t |
932 | start_getty(sp) | |
933 | session_t *sp; | |
d0ca48ff | 934 | { |
284ed8f8 KM |
935 | pid_t pid; |
936 | sigset_t mask; | |
937 | time_t current_time = time((time_t *) 0); | |
938 | ||
939 | /* | |
940 | * fork(), not vfork() -- we can't afford to block. | |
941 | */ | |
942 | if ((pid = fork()) == -1) { | |
943 | emergency("can't fork for getty on port %s: %m", sp->se_device); | |
944 | return -1; | |
945 | } | |
946 | ||
947 | if (pid) | |
948 | return pid; | |
949 | ||
950 | if (current_time > sp->se_started && | |
951 | current_time - sp->se_started < GETTY_SPACING) { | |
952 | warning("getty repeating too quickly on port %s, sleeping", | |
953 | sp->se_device); | |
954 | sleep((unsigned) GETTY_SPACING - | |
955 | (current_time - sp->se_started)); | |
956 | } | |
957 | ||
958 | if (sp->se_window) { | |
959 | start_window_system(sp); | |
960 | sleep(WINDOW_WAIT); | |
961 | } | |
962 | ||
963 | setctty(sp->se_device); | |
964 | ||
965 | sigemptyset(&mask); | |
966 | sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); | |
967 | ||
968 | execv(sp->se_getty_argv[0], sp->se_getty_argv); | |
969 | stall("can't exec getty '%s' for port %s: %m", | |
970 | sp->se_getty_argv[0], sp->se_device); | |
971 | _exit(1); | |
d0ca48ff SL |
972 | } |
973 | ||
b5775271 KM |
974 | /* |
975 | * Collect exit status for a child. | |
976 | * If an exiting login, start a new login running. | |
977 | */ | |
caf94fd3 | 978 | void |
b5775271 | 979 | collect_child(pid) |
284ed8f8 | 980 | pid_t pid; |
b5775271 | 981 | { |
284ed8f8 | 982 | register session_t *sp, *sprev, *snext; |
f570e1ff | 983 | |
b5775271 KM |
984 | if (! sessions) |
985 | return; | |
284ed8f8 | 986 | |
b5775271 KM |
987 | if (! (sp = find_session(pid))) |
988 | return; | |
284ed8f8 | 989 | |
b5775271 KM |
990 | clear_session_logs(sp); |
991 | del_session(sp); | |
992 | sp->se_process = 0; | |
284ed8f8 | 993 | |
b5775271 KM |
994 | if (sp->se_flags & SE_SHUTDOWN) { |
995 | if (sprev = sp->se_prev) | |
996 | sprev->se_next = sp->se_next; | |
997 | else | |
998 | sessions = sp->se_next; | |
999 | if (snext = sp->se_next) | |
1000 | snext->se_prev = sp->se_prev; | |
1001 | free_session(sp); | |
1002 | return; | |
1003 | } | |
284ed8f8 | 1004 | |
b5775271 KM |
1005 | if ((pid = start_getty(sp)) == -1) { |
1006 | /* serious trouble */ | |
1007 | requested_transition = clean_ttys; | |
1008 | return; | |
068e4187 | 1009 | } |
284ed8f8 | 1010 | |
b5775271 KM |
1011 | sp->se_process = pid; |
1012 | sp->se_started = time((time_t *) 0); | |
1013 | add_session(sp); | |
068e4187 RC |
1014 | } |
1015 | ||
b5775271 KM |
1016 | /* |
1017 | * Catch a signal and request a state transition. | |
1018 | */ | |
284ed8f8 KM |
1019 | void |
1020 | transition_handler(sig) | |
1021 | int sig; | |
068e4187 | 1022 | { |
fd1a77cb | 1023 | |
284ed8f8 KM |
1024 | switch (sig) { |
1025 | case SIGHUP: | |
1026 | requested_transition = clean_ttys; | |
1027 | break; | |
1028 | case SIGTERM: | |
1029 | requested_transition = death; | |
1030 | break; | |
1031 | case SIGTSTP: | |
1032 | requested_transition = catatonia; | |
1033 | break; | |
1034 | default: | |
1035 | requested_transition = 0; | |
1036 | break; | |
068e4187 | 1037 | } |
068e4187 RC |
1038 | } |
1039 | ||
b5775271 KM |
1040 | /* |
1041 | * Take the system multiuser. | |
1042 | */ | |
284ed8f8 KM |
1043 | state_func_t |
1044 | multi_user() | |
068e4187 | 1045 | { |
284ed8f8 KM |
1046 | pid_t pid; |
1047 | register session_t *sp; | |
a936c74e | 1048 | |
284ed8f8 KM |
1049 | requested_transition = 0; |
1050 | logger_enable = 1; | |
a936c74e | 1051 | |
b5775271 KM |
1052 | for (sp = sessions; sp; sp = sp->se_next) { |
1053 | if (sp->se_process) | |
1054 | continue; | |
1055 | if ((pid = start_getty(sp)) == -1) { | |
1056 | /* serious trouble */ | |
1057 | requested_transition = clean_ttys; | |
1058 | break; | |
a936c74e | 1059 | } |
b5775271 KM |
1060 | sp->se_process = pid; |
1061 | sp->se_started = time((time_t *) 0); | |
1062 | add_session(sp); | |
1063 | } | |
068e4187 | 1064 | |
284ed8f8 | 1065 | while (!requested_transition) |
b5775271 KM |
1066 | if ((pid = waitpid(-1, (int *) 0, 0)) != -1) |
1067 | collect_child(pid); | |
284ed8f8 KM |
1068 | |
1069 | return (state_func_t) requested_transition; | |
1070 | } | |
068e4187 | 1071 | |
284ed8f8 KM |
1072 | /* |
1073 | * This is an n-squared algorithm. We hope it isn't run often... | |
1074 | */ | |
1075 | state_func_t | |
1076 | clean_ttys() | |
068e4187 | 1077 | { |
284ed8f8 KM |
1078 | register session_t *sp, *sprev; |
1079 | register struct ttyent *typ; | |
1080 | register int session_index = 0; | |
f382c801 | 1081 | register int devlen; |
068e4187 | 1082 | |
284ed8f8 KM |
1083 | if (! sessions) |
1084 | return (state_func_t) multi_user; | |
1085 | ||
6f2ac96c | 1086 | devlen = sizeof(_PATH_DEV) - 1; |
284ed8f8 KM |
1087 | while (typ = getttyent()) { |
1088 | ++session_index; | |
1089 | ||
1090 | for (sp = sessions; sp; sprev = sp, sp = sp->se_next) | |
6f2ac96c | 1091 | if (strcmp(typ->ty_name, sp->se_device + devlen) == 0) |
068e4187 | 1092 | break; |
284ed8f8 KM |
1093 | |
1094 | if (sp) { | |
1095 | if (sp->se_index != session_index) { | |
1096 | warning("port %s changed utmp index from %d to %d", | |
1097 | sp->se_device, sp->se_index, | |
1098 | session_index); | |
1099 | sp->se_index = session_index; | |
1100 | } | |
1101 | if (typ->ty_status & TTY_ON) | |
1102 | sp->se_flags &= ~SE_SHUTDOWN; | |
1103 | else { | |
1104 | sp->se_flags |= SE_SHUTDOWN; | |
1105 | kill(sp->se_process, SIGHUP); | |
068e4187 | 1106 | } |
284ed8f8 | 1107 | continue; |
068e4187 | 1108 | } |
284ed8f8 KM |
1109 | |
1110 | new_session(sprev, session_index, typ); | |
f570e1ff | 1111 | } |
284ed8f8 KM |
1112 | |
1113 | endttyent(); | |
1114 | ||
1115 | return (state_func_t) multi_user; | |
1116 | } | |
1117 | ||
b5775271 KM |
1118 | /* |
1119 | * Block further logins. | |
1120 | */ | |
284ed8f8 KM |
1121 | state_func_t |
1122 | catatonia() | |
1123 | { | |
1124 | register session_t *sp; | |
1125 | ||
1126 | for (sp = sessions; sp; sp = sp->se_next) | |
1127 | sp->se_flags |= SE_SHUTDOWN; | |
1128 | ||
1129 | return (state_func_t) multi_user; | |
1130 | } | |
1131 | ||
b5775271 KM |
1132 | /* |
1133 | * Note SIGALRM. | |
1134 | */ | |
284ed8f8 KM |
1135 | void |
1136 | alrm_handler(sig) | |
1137 | int sig; | |
1138 | { | |
1139 | clang = 1; | |
1140 | } | |
1141 | ||
b5775271 KM |
1142 | /* |
1143 | * Bring the system down to single user. | |
1144 | */ | |
284ed8f8 KM |
1145 | state_func_t |
1146 | death() | |
1147 | { | |
1148 | register session_t *sp; | |
1149 | register int i; | |
b5775271 | 1150 | pid_t pid; |
284ed8f8 KM |
1151 | static const int death_sigs[3] = { SIGHUP, SIGTERM, SIGKILL }; |
1152 | ||
1153 | for (sp = sessions; sp; sp = sp->se_next) | |
1154 | sp->se_flags |= SE_SHUTDOWN; | |
1155 | ||
bb56e935 KM |
1156 | /* NB: should send a message to the session logger to avoid blocking. */ |
1157 | logwtmp("~", "shutdown", ""); | |
284ed8f8 KM |
1158 | logger_enable = 0; |
1159 | ||
1160 | for (i = 0; i < 3; ++i) { | |
1161 | if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH) | |
1162 | return (state_func_t) single_user; | |
1163 | ||
1164 | clang = 0; | |
1165 | alarm(DEATH_WATCH); | |
1166 | do | |
b5775271 KM |
1167 | if ((pid = waitpid(-1, (int *)0, 0)) != -1) |
1168 | collect_child(pid); | |
1169 | while (clang == 0 && errno != ECHILD); | |
284ed8f8 | 1170 | |
b5775271 | 1171 | if (errno == ECHILD) |
284ed8f8 KM |
1172 | return (state_func_t) single_user; |
1173 | } | |
1174 | ||
1175 | warning("some processes wouldn't die"); | |
1176 | ||
1177 | return (state_func_t) single_user; | |
f570e1ff | 1178 | } |