Stuf
[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 DEBUG_INIT
45
46 #define CONSOLE         "/dev/console"  /* Logical system console */
47 #define VT_PRIMARY      "/dev/tty0"     /* Virtual console master */
48 #define VT_SECONDARY    "/dev/tty1"     /* Virtual console master */
49 #define VT_LOG          "/dev/tty2"     /* Virtual console master */
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 static char *console = CONSOLE;
55 static char *first_terminal = "/dev/tty1";
56 static char *second_terminal = "/dev/tty2";
57 static char *log = "/dev/tty3";
58
59
60
61 /* try to open up the specified device */
62 int device_open(char *device, int mode)
63 {
64     int m, f, fd = -1;
65
66     mode = m | O_NONBLOCK;
67
68     /* Retry up to 5 times */
69     for (f = 0; f < 5; f++)
70         if ((fd = open(device, m)) >= 0)
71             break;
72     if (fd < 0)
73         return fd;
74     /* Set original flags. */
75     if (m != mode)
76         fcntl(fd, F_SETFL, mode);
77     return fd;
78 }
79
80 /* print a message to the specified device */
81 void message(char *device, char *fmt, ...)
82 {
83     int fd;
84     va_list arguments;
85
86     if ((fd = device_open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0) {
87         va_start(arguments, fmt);
88         vdprintf(fd, fmt, arguments);
89         va_end(arguments);
90         close(fd);
91     } else {
92         fprintf(stderr, "Bummer, can't print: ");
93         va_start(arguments, fmt);
94         vfprintf(stderr, fmt, arguments);
95         fflush(stderr);
96         va_end(arguments);
97     }
98 }
99
100 /* Set terminal settings to reasonable defaults */
101 void set_term()
102 {
103     int fd;
104     struct termios tty;
105
106     if ((fd = device_open(console, O_RDWR | O_NOCTTY)) < 0) {
107         message(log, "can't open %s\n", console);
108         return;
109     }
110     ioctl(fd, TCGETS, &tty);
111     tty.c_cflag &= CBAUD | CBAUDEX | CSIZE | CSTOPB | PARENB | PARODD;
112     tty.c_cflag |= HUPCL | CLOCAL;
113
114     tty.c_cc[VINTR] = 3;
115     tty.c_cc[VQUIT] = 28;
116     tty.c_cc[VERASE] = 127;
117     tty.c_cc[VKILL] = 24;
118     tty.c_cc[VEOF] = 4;
119     tty.c_cc[VTIME] = 0;
120     tty.c_cc[VMIN] = 1;
121     tty.c_cc[VSTART] = 17;
122     tty.c_cc[VSTOP] = 19;
123     tty.c_cc[VSUSP] = 26;
124
125     /* Set pre and post processing */
126     tty.c_iflag = IGNPAR | ICRNL | IXON | IXANY;
127     tty.c_oflag = OPOST | ONLCR;
128     tty.c_lflag = ISIG | ICANON | ECHO | ECHOCTL | ECHOPRT | ECHOKE;
129
130     /* Now set the terminal line. */
131     ioctl(fd, TCSETS, &tty);
132     close(fd);
133 }
134
135 static int mem_total()
136 {
137     char s[80];
138     char *p;
139     FILE *f;
140     const char pattern[] = "MemTotal:";
141
142     f = fopen("/proc/meminfo", "r");
143     while (NULL != fgets(s, 79, f)) {
144         p = strstr(s, pattern);
145         if (NULL != p) {
146             fclose(f);
147             return (atoi(p + strlen(pattern)));
148         }
149     }
150     return -1;
151 }
152
153 static void set_free_pages()
154 {
155     char s[80];
156     FILE *f;
157
158     f = fopen("/proc/sys/vm/freepages", "r");
159     fgets(s, 79, f);
160     if (atoi(s) < 32) {
161         fclose(f);
162         f = fopen("/proc/sys/vm/freepages", "w");
163         fprintf(f, "30\t40\t50\n");
164         printf("\nIncreased /proc/sys/vm/freepages values to 30/40/50\n");
165     }
166     fclose(f);
167 }
168
169
170 static void console_init()
171 {
172     int fd;
173     struct stat statbuf;
174     int tried_devcons = 0;
175     int tried_vtmaster = 0;
176     char *s;
177
178     if ((s = getenv("CONSOLE")) != NULL)
179         console = s;
180     else {
181         console = CONSOLE;
182         tried_devcons++;
183     }
184
185     if ( stat(CONSOLE, &statbuf) && S_ISLNK(statbuf.st_mode)) {
186         fprintf(stderr, "/dev/console does not exist, or is a symlink.\n");
187     }
188     while ((fd = open(console, O_RDONLY | O_NONBLOCK)) < 0) {
189         if (!tried_devcons) {
190             tried_devcons++;
191             console = CONSOLE;
192             continue;
193         }
194         if (!tried_vtmaster) {
195             tried_vtmaster++;
196             console = VT_PRIMARY;
197             continue;
198         }
199         break;
200     }
201     if (fd < 0)
202         console = "/dev/null";
203     else
204         close(fd);
205     fprintf(stderr, "console=%s\n", console);
206 }
207
208 static int waitfor(int pid)
209 {
210     int status, wpid;
211
212     message(log, "Waiting for process %d.\n", pid);
213     while ((wpid = wait(&status)) != pid) {
214         if (wpid > 0)
215             message(log, "pid %d exited, status=%x.\n", wpid, status);
216     }
217     return wpid;
218 }
219
220
221 static int run(const char *command, char *terminal, int get_enter)
222 {
223     int f, pid;
224     char *args[16];
225     char buf[256];
226     char *ptr;
227     static const char press_enter[] =
228         "\nPlease press Enter to activate this console. ";
229
230
231     /* Make a proper command from the command string */
232     strcpy(buf, command);
233     ptr = buf;
234     for (f = 1; f < 15; f++) {
235         /* Skip white space */
236         while (*ptr == ' ' || *ptr == '\t')
237             ptr++;
238         args[f] = ptr;
239         /* May be trailing space.. */
240         if (*ptr == 0)
241             break;
242         /* Skip this `word' */
243         while (*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '#')
244             ptr++;
245         /* If end-of-line, break */
246         if (*ptr == '#' || *ptr == 0) {
247             f++;
248             *ptr = 0;
249             break;
250         }
251         /* End word with \0 and continue */
252         args[f] = NULL;
253     }
254     args[0] = args[1];
255
256
257     if ((pid = fork()) == 0) {
258         /* Clean up */
259         close(0);
260         close(1);
261         close(2);
262         setsid();
263
264 #if 1
265         //if ((f = device_open(terminal, O_RDWR | O_NOCTTY)) < 0) {
266         if ((f = device_open(terminal, O_RDWR )) < 0) {
267             message(log, "open(%s) failed: %s\n", 
268                     terminal, strerror(errno));
269             return -1;
270         }
271         dup(f);
272         dup(f);
273 #else
274         open(terminal, O_RDWR);
275         dup(0);
276         dup(0);
277         //tcsetpgrp(0, getpgrp());
278 #endif
279         set_term();
280
281         if (get_enter) {
282             /*
283              * Save memory by not exec-ing anything large (like a shell)
284              * before the user wants it. This is critical if swap is not
285              * enabled and the system has low memory. Generally this will
286              * be run on the second virtual console, and the first will
287              * be allowed to start a shell or whatever an init script 
288              * specifies.
289              */
290             char c;
291             write(1, press_enter, sizeof(press_enter) - 1);
292             read(0, &c, 1);
293             message(console, "Got an enter\r\n");
294         }
295
296         /* Log the process name and args */
297         message(console, "Executing '%s'\r\n", command);
298         message(log, "Executing '%s'\r\n", command);
299
300         /* Now run it.  This program should take over this PID, 
301          * so nothing further in init.c should be run. */
302         execvp(args[1], args + 1);
303
304         message(console, "Hmm.  Trying as a script.\r\n");
305         /* If shell scripts are not executed, force the issue */
306         if (errno == ENOEXEC) {
307             char buf[256];
308             args[1] = SHELL;
309             args[2] = "-c";
310             strcpy(buf, "exec ");
311             strcat(buf, command);
312             args[3] = buf;
313             args[4] = NULL;
314             execvp(args[1], args + 1);
315         }
316         message(console, "Could not execute '%s'\n", command);
317         exit(-1);
318     }
319     return pid;
320 }
321
322
323 #ifndef DEBUG_INIT
324 static void shutdown_system(void)
325 {
326
327     message(console, "The system is going down NOW !!\r\n");
328     sync();
329     /* Allow Ctrl-Alt-Del to reboot system. */
330     reboot(RB_ENABLE_CAD);
331
332     /* Send signals to every process _except_ pid 1 */
333     message(console, "Sending SIGHUP to all processes.\r\n");
334     kill(-1, SIGHUP);
335     sleep(2);
336     sync();
337     message(console, "Sending SIGKILL to all processes.\r\n");
338     kill(-1, SIGKILL);
339     sleep(1);
340     waitfor(run("/bin/swapoff -a", console, 0));
341     waitfor(run("/bin/umount -a -n", console, 0));
342     sync();
343     if (get_kernel_revision() <= 2 * 65536 + 2 * 256 + 11) {
344         /* Removed  bdflush call, kupdate in kernels >2.2.11 */
345         bdflush(1, 0);
346         sync();
347     }
348 }
349
350 static void halt_signal(int sig)
351 {
352     shutdown_system();
353     message(console,
354             "The system is halted. Press CTRL-ALT-DEL or turn off power\r\n");
355     reboot(RB_POWER_OFF);
356     exit(0);
357 }
358
359 static void reboot_signal(int sig)
360 {
361     shutdown_system();
362     message(console, "Please stand by while rebooting the system.\r\n");
363     reboot(RB_AUTOBOOT);
364     exit(0);
365 }
366
367 #endif
368
369 extern int init_main(int argc, char **argv)
370 {
371     int run_rc = TRUE;
372     int pid1 = 0;
373     int pid2 = 0;
374     struct stat statbuf;
375     const char *init_commands = SHELL " -c exec " INITSCRIPT;
376     const char *shell_commands = SHELL " -";
377     const char *tty0_commands = init_commands;
378     const char *tty1_commands = shell_commands;
379     char *hello_msg_format =
380         "init started:  BusyBox v%s (%s) multi-call binary\r\n";
381     const char *no_memory =
382         "Sorry, your computer does not have enough memory.\r\n";
383
384
385 #ifndef DEBUG_INIT
386     /* Set up sig handlers */
387     signal(SIGUSR1, halt_signal);
388     signal(SIGSEGV, halt_signal);
389     signal(SIGPWR, halt_signal);
390     signal(SIGALRM, halt_signal);
391     signal(SIGHUP, halt_signal);
392     signal(SIGUSR2, reboot_signal);
393     signal(SIGINT, reboot_signal);
394     signal(SIGTERM, reboot_signal);
395 #endif
396     /* Figure out where the default console should be */
397     console_init();
398
399     /* Turn off rebooting via CTL-ALT-DEL -- we get a 
400      * SIGINT on CAD so we can shut things down gracefully... */
401 #ifndef DEBUG_INIT
402     reboot(RB_DISABLE_CAD);
403 #endif
404
405     /* Close whatever files are open, and reset the console. */
406     close(0);
407     close(1);
408     close(2);
409     set_term();
410     setsid();
411
412     /* Make sure PATH is set to something sane */
413     if (getenv("PATH") == NULL)
414         putenv(PATH_DEFAULT);
415
416     /* Hello world */
417     message(log, hello_msg_format, BB_VER, BB_BT);
418     message(console, hello_msg_format, BB_VER, BB_BT);
419
420     /* Mount /proc */
421     if (mount("/proc", "/proc", "proc", 0, 0)) {
422         message(console, "Mounting /proc: failed!\r\n");
423         message(log, "Mounting /proc: failed!\n");
424     } else {
425         message(console, "Mounting /proc: done.\r\n");
426         message(log, "Mounting /proc: done.\n");
427     }
428
429     /* Make sure there is enough memory to do something useful */
430     set_free_pages();
431     if (mem_total() < 2000) {
432         int retval;
433         retval = stat("/etc/fstab", &statbuf);
434         if (retval) {
435             message(console, "%s", no_memory);
436             while (1) {
437                 sleep(1);
438             }
439         } else {
440             /* Try to turn on swap */
441             waitfor(run("/bin/swapon -a", console, 0));
442             if (mem_total() < 2000) {
443                 message(console, "%s", no_memory);
444                 while (1) {
445                     sleep(1);
446                 }
447             }
448         }
449     }
450
451     /* Check if we are supposed to be in single user mode */
452     if ( argc > 1 && (!strcmp(argv[1], "single") || 
453                 !strcmp(argv[1], "-s") || !strcmp(argv[1], "1"))) {
454         run_rc = FALSE;
455         tty0_commands = shell_commands;
456         tty1_commands = shell_commands;
457     }
458
459     /* Make sure an init script exists before trying to run it */
460     if (run_rc == TRUE && stat(INITSCRIPT, &statbuf)) {
461         tty0_commands = shell_commands;
462         tty1_commands = shell_commands;
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         int wpid;
470         int status;
471
472         if (pid1 == 0 && tty0_commands) {
473             pid1 = run(tty0_commands, first_terminal, 1);
474         }
475         if (pid2 == 0 && tty1_commands) {
476             pid2 = run(tty1_commands, second_terminal, 1);
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 an init script if it exits */
483         if (run_rc == FALSE && wpid == pid1) {
484             pid1 = 0;
485         }
486         if (wpid == pid2) {
487             pid2 = 0;
488         }
489         sleep(1);
490     }
491 }