Fixed init so it won't hang on reboot...
[oweals/busybox.git] / init.c
1 /*
2  * Mini init implementation for busybox
3  *
4  *
5  * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
6  * Adjusted by so many folks, it's impossible to keep track.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21  *
22  */
23
24 #include "internal.h"
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <unistd.h>
29 #include <errno.h>
30 #include <signal.h>
31 #include <termios.h>
32 #include <sys/types.h>
33 #include <sys/fcntl.h>
34 #include <sys/wait.h>
35 #include <string.h>
36 #include <sys/mount.h>
37 #include <sys/reboot.h>
38 #include <sys/kdaemon.h>
39 #include <sys/sysmacros.h>
40 #include <linux/serial.h>       /* for serial_struct */
41 #include <sys/vt.h>             /* for vt_stat */
42 #include <sys/ioctl.h>
43
44 #define VT_CONSOLE      "/dev/console"  /* Logical system console */
45 #define VT_PRIMARY      "/dev/tty1"     /* Primary virtual console */
46 #define VT_SECONDARY    "/dev/tty2"     /* Virtual console */
47 #define VT_LOG          "/dev/tty3"     /* Virtual console */
48 #define SERIAL_CON0     "/dev/ttyS0"    /* Primary serial console */
49 #define SERIAL_CON1     "/dev/ttyS1"    /* Serial console */
50 #define SHELL           "/bin/sh"       /* Default shell */
51 #define INITSCRIPT      "/etc/init.d/rcS"       /* Initscript. */
52 #define PATH_DEFAULT    "PATH=/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin"
53
54 #define LOG             0x1
55 #define CONSOLE         0x2
56 static char *console = VT_CONSOLE;
57 static char *second_console = VT_SECONDARY;
58 static char *log = VT_LOG;
59 static int kernel_version = 0;
60
61
62 /* try to open up the specified device */
63 int device_open(char *device, int mode)
64 {
65     int m, f, fd = -1;
66
67     m = mode | O_NONBLOCK;
68
69     /* Retry up to 5 times */
70     for (f = 0; f < 5; f++)
71         if ((fd = open(device, m)) >= 0)
72             break;
73     if (fd < 0)
74         return fd;
75     /* Reset original flags. */
76     if (m != mode)
77         fcntl(fd, F_SETFL, mode);
78     return fd;
79 }
80
81 /* print a message to the specified device:
82  * device may be bitwise-or'd from LOG | CONSOLE */
83 void message(int device, char *fmt, ...)
84 {
85     int fd;
86     static int log_fd=-1;
87     va_list arguments;
88
89     /* Take full control of the log tty, and never close it.
90      * It's mine, all mine!  Muhahahaha! */
91     if (log_fd==-1) {
92         if ((log_fd = device_open(log, O_RDWR|O_NDELAY)) < 0) {
93             log_fd=-1;
94             fprintf(stderr, "Bummer, can't write to log on %s!\r\n", log);
95             fflush(stderr);
96             return;
97         }
98     }
99
100     if ( (device & LOG) && (log_fd != -1) ) {
101         va_start(arguments, fmt);
102         vdprintf(log_fd, fmt, arguments);
103         va_end(arguments);
104     }
105     if (device & CONSOLE) {
106         if ((fd = device_open(console, O_WRONLY|O_NOCTTY|O_NDELAY)) >= 0) {
107             va_start(arguments, fmt);
108             vdprintf(fd, fmt, arguments);
109             va_end(arguments);
110             close(fd);
111         } else {
112             fprintf(stderr, "Bummer, can't print: ");
113             va_start(arguments, fmt);
114             vfprintf(stderr, fmt, arguments);
115             fflush(stderr);
116             va_end(arguments);
117         }
118     }
119 }
120
121
122 /* Set terminal settings to reasonable defaults */
123 void set_term( int fd)
124 {
125     struct termios tty;
126     static const char control_characters[] = {
127         '\003', '\034', '\177', '\030', '\004', '\0',
128         '\1', '\0', '\021', '\023', '\032', '\0', '\022',
129         '\017', '\027', '\026', '\0'
130         };
131
132     tcgetattr(fd, &tty);
133
134     /* Make it be sane */
135     tty.c_cflag &= CBAUD|CBAUDEX|CSIZE|CSTOPB|PARENB|PARODD;
136     tty.c_cflag |= HUPCL|CLOCAL;
137
138     /* input modes */
139     tty.c_iflag = IGNPAR|ICRNL|IXON|IXOFF|IXANY;
140
141     /* use line dicipline 0 */
142     tty.c_line = 0;
143
144     /* output modes */
145     tty.c_oflag = OPOST|ONLCR;
146
147     /* local modes */
148     tty.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOPRT|ECHOKE|IEXTEN;
149
150     /* control chars */
151     memcpy(tty.c_cc, control_characters, sizeof(control_characters));
152
153     tcsetattr(fd, TCSANOW, &tty);
154 }
155
156 /* How much memory does this machine have? */
157 static int mem_total()
158 {
159     char s[80];
160     char *p = "/proc/meminfo";
161     FILE *f;
162     const char pattern[] = "MemTotal:";
163
164     if ((f = fopen(p, "r")) < 0) {
165         message(LOG, "Error opening %s: %s\n", p, strerror( errno));
166         return -1;
167     }
168     while (NULL != fgets(s, 79, f)) {
169         p = strstr(s, pattern);
170         if (NULL != p) {
171             fclose(f);
172             return (atoi(p + strlen(pattern)));
173         }
174     }
175     return -1;
176 }
177
178 static void console_init()
179 {
180     int fd;
181     int tried_devcons = 0;
182     int tried_vtprimary = 0;
183     char *s;
184
185     if ((s = getenv("CONSOLE")) != NULL) {
186         console = s;
187 /* Apparently the sparc does wierd things... */
188 #if defined (__sparc__)
189         if (strncmp( s, "/dev/tty", 8 )==0) {
190             switch( s[8]) {
191                 case 'a':
192                     s=SERIAL_CON0;
193                     break;
194                 case 'b':
195                     s=SERIAL_CON1;
196             }
197         }
198 #endif
199     } else {
200         console = VT_CONSOLE;
201         tried_devcons++;
202     }
203
204     while ((fd = open(console, O_RDONLY | O_NONBLOCK)) < 0) {
205         /* Can't open selected console -- try /dev/console */
206         if (!tried_devcons) {
207             tried_devcons++;
208             console = VT_CONSOLE;
209             continue;
210         }
211         /* Can't open selected console -- try vt1 */
212         if (!tried_vtprimary) {
213             tried_vtprimary++;
214             console = VT_PRIMARY;
215             continue;
216         }
217         break;
218     }
219     if (fd < 0)
220         /* Perhaps we should panic here? */
221         console = "/dev/null";
222     else
223         close(fd);
224     message(LOG, "console=%s\n", console );
225 }
226
227 static int waitfor(int pid)
228 {
229     int status, wpid;
230
231     message(LOG, "Waiting for process %d.\n", pid);
232     while ((wpid = wait(&status)) != pid) {
233         if (wpid > 0)
234             message(LOG, "pid %d exited, status=0x%x.\n", wpid, status);
235     }
236     return wpid;
237 }
238
239
240 static pid_t run(const char * const* command, 
241         char *terminal, int get_enter)
242 {
243     int fd;
244     pid_t pid;
245     const char * const* cmd = command+1;
246     static const char press_enter[] =
247         "\nPlease press Enter to activate this console. ";
248
249     if ((pid = fork()) == 0) {
250         /* Clean up */
251         close(0);
252         close(1);
253         close(2);
254         setsid();
255
256         /* Reset signal handlers set for parent process */
257         signal(SIGUSR1, SIG_DFL);
258         signal(SIGUSR2, SIG_DFL);
259         signal(SIGINT, SIG_DFL);
260         signal(SIGTERM, SIG_DFL);
261
262         if ((fd = device_open(terminal, O_RDWR)) < 0) {
263             message(LOG, "Bummer, can't open %s\r\n", terminal);
264             exit(-1);
265         }
266         dup(fd);
267         dup(fd);
268         tcsetpgrp(0, getpgrp());
269         set_term(0);
270
271         if (get_enter==TRUE) {
272             /*
273              * Save memory by not exec-ing anything large (like a shell)
274              * before the user wants it. This is critical if swap is not
275              * enabled and the system has low memory. Generally this will
276              * be run on the second virtual console, and the first will
277              * be allowed to start a shell or whatever an init script 
278              * specifies.
279              */
280             char c;
281             message(LOG, "Waiting for enter to start '%s' (pid %d, console %s)\r\n", 
282                     *cmd, getpid(), terminal );
283             write(1, press_enter, sizeof(press_enter) - 1);
284             read(0, &c, 1);
285         }
286
287         /* Log the process name and args */
288         message(LOG, "Starting pid %d, console %s: '", getpid(), terminal);
289         while ( *cmd) message(LOG, "%s ", *cmd++);
290         message(LOG, "'\r\n");
291         
292         /* Now run it.  The new program will take over this PID, 
293          * so nothing further in init.c should be run. */
294         execvp(*command, (char**)command+1);
295
296         message(LOG, "Bummer, could not run '%s'\n", command);
297         exit(-1);
298     }
299     return pid;
300 }
301
302 /* Make sure there is enough memory to do something useful. *
303  * Calls swapon if needed so be sure /proc is mounted. */
304 static void check_memory()
305 {
306     struct stat statbuf;
307     const char* const swap_on_cmd[] = 
308             { "/bin/swapon", "swapon", "-a", 0};
309
310     if (mem_total() > 3500)
311         return;
312
313     if (stat("/etc/fstab", &statbuf) == 0) {
314         /* Try to turn on swap */
315         waitfor(run(swap_on_cmd, log, FALSE));
316         if (mem_total() < 3500)
317             goto goodnight;
318     } else
319         goto goodnight;
320     return;
321
322 goodnight:
323         message(CONSOLE, "Sorry, your computer does not have enough memory.\r\n");
324         while (1) sleep(1);
325 }
326
327 static void shutdown_system(void)
328 {
329     const char* const swap_off_cmd[] = { "swapoff", "swapoff", "-a", 0};
330     const char* const umount_cmd[] = { "umount", "umount", "-a", "-n", 0};
331
332 #ifndef DEBUG_INIT
333     /* Allow Ctrl-Alt-Del to reboot system. */
334     reboot(RB_ENABLE_CAD);
335 #endif
336     message(CONSOLE, "The system is going down NOW !!\r\n");
337     sync();
338     /* Send signals to every process _except_ pid 1 */
339     message(CONSOLE, "Sending SIGHUP to all processes.\r\n");
340 #ifndef DEBUG_INIT
341     kill(-1, SIGHUP);
342 #endif
343     sleep(2);
344     sync();
345     message(CONSOLE, "Sending SIGKILL to all processes.\r\n");
346 #ifndef DEBUG_INIT
347     kill(-1, SIGKILL);
348 #endif
349     sleep(1);
350     waitfor(run( swap_off_cmd, console, FALSE));
351     waitfor(run( umount_cmd, console, FALSE));
352     sync();
353     if (kernel_version > 0 && kernel_version <= 2 * 65536 + 2 * 256 + 11) {
354         /* bdflush, kupdate not needed for kernels >2.2.11 */
355         message(CONSOLE, "Flushing buffers.\r\n");
356         bdflush(1, 0);
357         sync();
358     }
359 }
360
361 static void halt_signal(int sig)
362 {
363     shutdown_system();
364     message(CONSOLE,
365             "The system is halted. Press CTRL-ALT-DEL or turn off power\r\n");
366     sync();
367 #ifndef DEBUG_INIT
368     reboot(RB_HALT_SYSTEM);
369     //reboot(RB_POWER_OFF);
370 #endif
371     exit(0);
372 }
373
374 static void reboot_signal(int sig)
375 {
376     shutdown_system();
377     message(CONSOLE, "Please stand by while rebooting the system.\r\n");
378     sync();
379 #ifndef DEBUG_INIT
380     reboot(RB_AUTOBOOT);
381 #endif
382     exit(0);
383 }
384
385 extern int init_main(int argc, char **argv)
386 {
387     int run_rc = TRUE;
388     int wait_for_enter = TRUE;
389     pid_t pid1 = 0;
390     pid_t pid2 = 0;
391     struct stat statbuf;
392     const char* const init_commands[] = { INITSCRIPT, INITSCRIPT, 0};
393     const char* const shell_commands[] = { SHELL, "-" SHELL, 0};
394     const char* const* tty0_commands = shell_commands;
395     const char* const* tty1_commands = shell_commands;
396 #ifdef DEBUG_INIT
397     char *hello_msg_format =
398         "init(%d) started:  BusyBox v%s (%s) multi-call binary\r\n";
399 #else
400     char *hello_msg_format =
401         "init started:  BusyBox v%s (%s) multi-call binary\r\n";
402 #endif
403
404     
405     /* Check if we are supposed to be in single user mode */
406     if ( argc > 1 && (!strcmp(argv[1], "single") || 
407                 !strcmp(argv[1], "-s") || !strcmp(argv[1], "1"))) {
408         run_rc = FALSE;
409     }
410
411     
412     /* Set up sig handlers  -- be sure to
413      * clear all of these in run() */
414     signal(SIGUSR1, halt_signal);
415     signal(SIGUSR2, reboot_signal);
416     signal(SIGINT, reboot_signal);
417     signal(SIGTERM, reboot_signal);
418
419     /* Turn off rebooting via CTL-ALT-DEL -- we get a 
420      * SIGINT on CAD so we can shut things down gracefully... */
421 #ifndef DEBUG_INIT
422     reboot(RB_DISABLE_CAD);
423 #endif 
424
425     /* Figure out where the default console should be */
426     console_init();
427
428     /* Close whatever files are open, and reset the console. */
429     close(0);
430     close(1);
431     close(2);
432     set_term(0);
433     setsid();
434
435     /* Make sure PATH is set to something sane */
436     putenv(PATH_DEFAULT);
437
438    
439     /* Hello world */
440 #ifndef DEBUG_INIT
441     message(CONSOLE|LOG, hello_msg_format, BB_VER, BB_BT);
442 #else
443     message(CONSOLE|LOG, hello_msg_format, getpid(), BB_VER, BB_BT);
444 #endif
445
446     
447     /* Mount /proc */
448     if (mount ("proc", "/proc", "proc", 0, 0) == 0) {
449         message(CONSOLE|LOG, "Mounting /proc: done.\n");
450         kernel_version = get_kernel_revision();
451     } else
452         message(CONSOLE|LOG, "Mounting /proc: failed!\n");
453
454     /* Make sure there is enough memory to do something useful. */
455     check_memory();
456
457
458     /* Make sure an init script exists before trying to run it */
459     if (run_rc == TRUE && stat(INITSCRIPT, &statbuf)==0) {
460         wait_for_enter = FALSE;
461         tty0_commands = init_commands;
462     }
463
464
465     /* Ok, now launch the rc script and/or prepare to 
466      * start up some VTs if somebody hits enter... 
467      */
468     for (;;) {
469         pid_t wpid;
470         int status;
471
472         if (pid1 == 0 && tty0_commands) {
473             pid1 = run(tty0_commands, console, wait_for_enter);
474         }
475         if (pid2 == 0 && tty1_commands) {
476             pid2 = run(tty1_commands, second_console, TRUE);
477         }
478         wpid = wait(&status);
479         if (wpid > 0 ) {
480             message(LOG, "pid %d exited, status=%x.\n", wpid, status);
481         }
482         /* Don't respawn init script if it exits */
483         if (wpid == pid1) {
484             if (run_rc == FALSE) {
485                 pid1 = 0;
486             } 
487 #if 0
488 /* Turn this on to start a shell on the console if the init script exits.... */
489             else {
490                 run_rc=FALSE;
491                 wait_for_enter=TRUE;
492                 tty0_commands=shell_commands;
493             }
494 #endif
495         }
496         if (wpid == pid2) {
497             pid2 = 0;
498         }
499         sleep(1);
500     }
501 }