make includes fix from trunk
[minix.git] / servers / init / init.c
blobaf3cb21596bf9ed12511ce148d1c025e44b8e40e
1 /* This process is the father (mother) of all Minix user processes. When
2 * Minix comes up, this is process number 2, and has a pid of 1. It
3 * executes the /etc/rc shell file, and then reads the /etc/ttytab file to
4 * determine which terminals need a login process.
6 * If the files /usr/adm/wtmp and /etc/utmp exist and are writable, init
7 * (with help from login) will maintain login accounting. Sending a
8 * signal 1 (SIGHUP) to init will cause it to rescan /etc/ttytab and start
9 * up new shell processes if necessary. It will not, however, kill off
10 * login processes for lines that have been turned off; do this manually.
11 * Signal 15 (SIGTERM) makes init stop spawning new processes, this is
12 * used by shutdown and friends when they are about to close the system
13 * down.
16 #include <minix/type.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <sys/stat.h>
20 #include <sys/svrctl.h>
21 #include <ttyent.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <signal.h>
26 #include <string.h>
27 #include <time.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <utmp.h>
32 /* Command to execute as a response to the three finger salute. */
33 char *REBOOT_CMD[] = { "shutdown", "now", "CTRL-ALT-DEL", NULL };
35 /* Associated fake ttytab entry. */
36 struct ttyent TT_REBOOT = { "console", "-", REBOOT_CMD, NULL };
38 char PATH_UTMP[] = "/etc/utmp"; /* current logins */
39 char PATH_WTMP[] = "/usr/adm/wtmp"; /* login/logout history */
41 #define PIDSLOTS 32 /* first this many ttys can be on */
43 struct slotent {
44 int errct; /* error count */
45 pid_t pid; /* pid of login process for this tty line */
48 #define ERRCT_DISABLE 10 /* disable after this many errors */
49 #define NO_PID 0 /* pid value indicating no process */
51 struct slotent slots[PIDSLOTS]; /* init table of ttys and pids */
53 int gothup = 0; /* flag, showing signal 1 was received */
54 int gotabrt = 0; /* flag, showing signal 6 was received */
55 int spawn = 1; /* flag, spawn processes only when set */
57 void tell(int fd, char *s);
58 void report(int fd, char *label);
59 void wtmp(int type, int linenr, char *line, pid_t pid);
60 void startup(int linenr, struct ttyent *ttyp);
61 int execute(char **cmd);
62 void onhup(int sig);
63 void onterm(int sig);
64 void onabrt(int sig);
66 int main(void)
68 pid_t pid; /* pid of child process */
69 int fd; /* generally useful */
70 int linenr; /* loop variable */
71 int check; /* check if a new process must be spawned */
72 int sn; /* signal number */
73 struct slotent *slotp; /* slots[] pointer */
74 struct ttyent *ttyp; /* ttytab entry */
75 struct sigaction sa;
76 struct stat stb;
78 #define OPENFDS \
79 if (fstat(0, &stb) < 0) { \
80 /* Open standard input, output & error. */ \
81 (void) open("/dev/null", O_RDONLY); \
82 (void) open("/dev/log", O_WRONLY); \
83 dup(1); \
86 sigemptyset(&sa.sa_mask);
87 sa.sa_flags = 0;
89 /* Default: Ignore every signal (except those that follow). */
90 sa.sa_handler = SIG_IGN;
91 for (sn = 1; sn < _NSIG; sn++) {
92 sigaction(sn, &sa, NULL);
95 /* Hangup: Reexamine /etc/ttytab for newly enabled terminal lines. */
96 sa.sa_handler = onhup;
97 sigaction(SIGHUP, &sa, NULL);
99 /* Terminate: Stop spawning login processes, shutdown is near. */
100 sa.sa_handler = onterm;
101 sigaction(SIGTERM, &sa, NULL);
103 /* Abort: Sent by the kernel on CTRL-ALT-DEL; shut the system down. */
104 sa.sa_handler = onabrt;
105 sigaction(SIGABRT, &sa, NULL);
107 /* Execute the /etc/rc file. */
108 if ((pid = fork()) != 0) {
109 /* Parent just waits. */
110 while (wait(NULL) != pid) {
111 if (gotabrt) reboot(RBT_HALT);
113 } else {
114 #if ! SYS_GETKENV
115 struct sysgetenv sysgetenv;
116 #endif
117 char bootopts[16];
118 static char *rc_command[] = { "sh", "/etc/rc", NULL, NULL, NULL };
119 char **rcp = rc_command + 2;
121 /* Get the boot options from the boot environment. */
122 sysgetenv.key = "bootopts";
123 sysgetenv.keylen = 8+1;
124 sysgetenv.val = bootopts;
125 sysgetenv.vallen = sizeof(bootopts);
126 if (svrctl(MMGETPARAM, &sysgetenv) == 0) *rcp++ = bootopts;
127 *rcp = "start";
129 execute(rc_command);
130 report(2, "sh /etc/rc");
131 _exit(1); /* impossible, we hope */
134 OPENFDS;
136 /* Clear /etc/utmp if it exists. */
137 if ((fd = open(PATH_UTMP, O_WRONLY | O_TRUNC)) >= 0) close(fd);
139 /* Log system reboot. */
140 wtmp(BOOT_TIME, 0, NULL, 0);
142 /* Main loop. If login processes have already been started up, wait for one
143 * to terminate, or for a HUP signal to arrive. Start up new login processes
144 * for all ttys which don't have them. Note that wait() also returns when
145 * somebody's orphan dies, in which case ignore it. If the TERM signal is
146 * sent then stop spawning processes, shutdown time is near.
149 check = 1;
150 while (1) {
151 while ((pid = waitpid(-1, NULL, check ? WNOHANG : 0)) > 0) {
152 /* Search to see which line terminated. */
153 for (linenr = 0; linenr < PIDSLOTS; linenr++) {
154 slotp = &slots[linenr];
155 if (slotp->pid == pid) {
156 /* Record process exiting. */
157 wtmp(DEAD_PROCESS, linenr, NULL, pid);
158 slotp->pid = NO_PID;
159 check = 1;
164 /* If a signal 1 (SIGHUP) is received, simply reset error counts. */
165 if (gothup) {
166 gothup = 0;
167 for (linenr = 0; linenr < PIDSLOTS; linenr++) {
168 slots[linenr].errct = 0;
170 check = 1;
173 /* Shut down on signal 6 (SIGABRT). */
174 if (gotabrt) {
175 gotabrt = 0;
176 startup(0, &TT_REBOOT);
179 if (spawn && check) {
180 /* See which lines need a login process started up. */
181 for (linenr = 0; linenr < PIDSLOTS; linenr++) {
182 slotp = &slots[linenr];
183 if ((ttyp = getttyent()) == NULL) break;
185 if (ttyp->ty_getty != NULL
186 && ttyp->ty_getty[0] != NULL
187 && slotp->pid == NO_PID
188 && slotp->errct < ERRCT_DISABLE)
190 startup(linenr, ttyp);
193 endttyent();
195 check = 0;
199 void onhup(int sig)
201 gothup = 1;
202 spawn = 1;
205 void onterm(int sig)
207 spawn = 0;
210 void onabrt(int sig)
212 static int count = 0;
214 if (++count == 2) reboot(RBT_HALT);
215 gotabrt = 1;
218 void startup(int linenr, struct ttyent *ttyp)
220 /* Fork off a process for the indicated line. */
222 struct slotent *slotp; /* pointer to ttyslot */
223 pid_t pid; /* new pid */
224 int err[2]; /* error reporting pipe */
225 char line[32]; /* tty device name */
226 int status;
228 slotp = &slots[linenr];
230 /* Error channel for between fork and exec. */
231 if (pipe(err) < 0) err[0] = err[1] = -1;
233 if ((pid = fork()) == -1 ) {
234 report(2, "fork()");
235 sleep(10);
236 return;
239 if (pid == 0) {
240 /* Child */
241 close(err[0]);
242 fcntl(err[1], F_SETFD, fcntl(err[1], F_GETFD) | FD_CLOEXEC);
244 /* A new session. */
245 setsid();
247 /* Construct device name. */
248 strcpy(line, "/dev/");
249 strncat(line, ttyp->ty_name, sizeof(line) - 6);
251 /* Open the line for standard input and output. */
252 close(0);
253 close(1);
254 if (open(line, O_RDWR) < 0 || dup(0) < 0) {
255 write(err[1], &errno, sizeof(errno));
256 _exit(1);
259 if (ttyp->ty_init != NULL && ttyp->ty_init[0] != NULL) {
260 /* Execute a command to initialize the terminal line. */
262 if ((pid = fork()) == -1) {
263 report(2, "fork()");
264 errno= 0;
265 write(err[1], &errno, sizeof(errno));
266 _exit(1);
269 if (pid == 0) {
270 alarm(10);
271 execute(ttyp->ty_init);
272 report(2, ttyp->ty_init[0]);
273 _exit(1);
276 while (waitpid(pid, &status, 0) != pid) {}
277 if (status != 0) {
278 tell(2, "init: ");
279 tell(2, ttyp->ty_name);
280 tell(2, ": ");
281 tell(2, ttyp->ty_init[0]);
282 tell(2, ": bad exit status\n");
283 errno = 0;
284 write(err[1], &errno, sizeof(errno));
285 _exit(1);
289 /* Redirect standard error too. */
290 dup2(0, 2);
292 /* Execute the getty process. */
293 execute(ttyp->ty_getty);
295 /* Oops, disaster strikes. */
296 fcntl(2, F_SETFL, fcntl(2, F_GETFL) | O_NONBLOCK);
297 if (linenr != 0) report(2, ttyp->ty_getty[0]);
298 write(err[1], &errno, sizeof(errno));
299 _exit(1);
302 /* Parent */
303 if (ttyp != &TT_REBOOT) slotp->pid = pid;
305 close(err[1]);
306 if (read(err[0], &errno, sizeof(errno)) != 0) {
307 /* If an errno value goes down the error pipe: Problems. */
309 switch (errno) {
310 case ENOENT:
311 case ENODEV:
312 case ENXIO:
313 /* Device nonexistent, no driver, or no minor device. */
314 slotp->errct = ERRCT_DISABLE;
315 close(err[0]);
316 return;
317 case 0:
318 /* Error already reported. */
319 break;
320 default:
321 /* Any other error on the line. */
322 report(2, ttyp->ty_name);
324 close(err[0]);
326 if (++slotp->errct >= ERRCT_DISABLE) {
327 tell(2, "init: ");
328 tell(2, ttyp->ty_name);
329 tell(2, ": excessive errors, shutting down\n");
330 } else {
331 sleep(5);
333 return;
335 close(err[0]);
337 if (ttyp != &TT_REBOOT) wtmp(LOGIN_PROCESS, linenr, ttyp->ty_name, pid);
338 slotp->errct = 0;
341 int execute(char **cmd)
343 /* Execute a command with a path search along /sbin:/bin:/usr/sbin:/usr/bin.
345 static char *nullenv[] = { NULL };
346 char command[128];
347 char *path[] = { "/sbin", "/bin", "/usr/sbin", "/usr/bin" };
348 int i;
350 if (cmd[0][0] == '/') {
351 /* A full path. */
352 return execve(cmd[0], cmd, nullenv);
355 /* Path search. */
356 for (i = 0; i < 4; i++) {
357 if (strlen(path[i]) + 1 + strlen(cmd[0]) + 1 > sizeof(command)) {
358 errno= ENAMETOOLONG;
359 return -1;
361 strcpy(command, path[i]);
362 strcat(command, "/");
363 strcat(command, cmd[0]);
364 execve(command, cmd, nullenv);
365 if (errno != ENOENT) break;
367 return -1;
370 void wtmp(type, linenr, line, pid)
371 int type; /* type of entry */
372 int linenr; /* line number in ttytab */
373 char *line; /* tty name (only good on login) */
374 pid_t pid; /* pid of process */
376 /* Log an event into the UTMP and WTMP files. */
378 struct utmp utmp; /* UTMP/WTMP User Accounting */
379 int fd;
381 /* Clear the utmp record. */
382 memset((void *) &utmp, 0, sizeof(utmp));
384 /* Fill in utmp. */
385 switch (type) {
386 case BOOT_TIME:
387 /* Make a special reboot record. */
388 strcpy(utmp.ut_name, "reboot");
389 strcpy(utmp.ut_line, "~");
390 break;
392 case LOGIN_PROCESS:
393 /* A new login, fill in line name. */
394 strncpy(utmp.ut_line, line, sizeof(utmp.ut_line));
395 break;
397 case DEAD_PROCESS:
398 /* A logout. Use the current utmp entry, but make sure it is a
399 * user process exiting, and not getty or login giving up.
401 if ((fd = open(PATH_UTMP, O_RDONLY)) < 0) {
402 if (errno != ENOENT) report(2, PATH_UTMP);
403 return;
405 if (lseek(fd, (off_t) (linenr+1) * sizeof(utmp), SEEK_SET) == -1
406 || read(fd, &utmp, sizeof(utmp)) == -1
408 report(2, PATH_UTMP);
409 close(fd);
410 return;
412 close(fd);
413 if (utmp.ut_type != USER_PROCESS) return;
414 strncpy(utmp.ut_name, "", sizeof(utmp.ut_name));
415 break;
418 /* Finish new utmp entry. */
419 utmp.ut_pid = pid;
420 utmp.ut_type = type;
421 utmp.ut_time = time((time_t *) 0);
423 switch (type) {
424 case LOGIN_PROCESS:
425 case DEAD_PROCESS:
426 /* Write new entry to utmp. */
427 if ((fd = open(PATH_UTMP, O_WRONLY)) < 0
428 || lseek(fd, (off_t) (linenr+1) * sizeof(utmp), SEEK_SET) == -1
429 || write(fd, &utmp, sizeof(utmp)) == -1
431 if (errno != ENOENT) report(2, PATH_UTMP);
433 if (fd != -1) close(fd);
434 break;
437 switch (type) {
438 case BOOT_TIME:
439 case DEAD_PROCESS:
440 /* Add new wtmp entry. */
441 if ((fd = open(PATH_WTMP, O_WRONLY | O_APPEND)) < 0
442 || write(fd, &utmp, sizeof(utmp)) == -1
444 if (errno != ENOENT) report(2, PATH_WTMP);
446 if (fd != -1) close(fd);
447 break;
451 void tell(fd, s)
452 int fd;
453 char *s;
455 write(fd, s, strlen(s));
458 void report(fd, label)
459 int fd;
460 char *label;
462 int err = errno;
464 tell(fd, "init: ");
465 tell(fd, label);
466 tell(fd, ": ");
467 tell(fd, strerror(err));
468 tell(fd, "\n");
469 errno= err;