More stuff
[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 static const char  init_usage[] = "Used internally by the system.";
45 static char        console[16] = "";
46 static const char* default_console = "/dev/tty2";
47 static char*       first_terminal = NULL;
48 static const char* second_terminal = "/dev/tty2";
49 static const char* log = "/dev/tty3";
50 static char* term_ptr = NULL;
51
52 static void
53 message(const char* terminal, const char * pattern, ...)
54 {
55         int     fd;
56         FILE *  con = 0;
57         va_list arguments;
58
59         /*
60          * Open the console device each time a message is printed. If the user
61          * has switched consoles, the open will get the new console. If we kept
62          * the console open, we'd always print to the same one.
63          */
64         if ( !terminal
65          ||  ((fd = open(terminal, O_WRONLY|O_NOCTTY)) < 0)
66          ||  ((con = fdopen(fd, "w")) == NULL) )
67                 return;
68
69         va_start(arguments, pattern);
70         vfprintf(con, pattern, arguments);
71         va_end(arguments);
72         fclose(con);
73 }
74
75 static int
76 waitfor(int pid)
77 {
78         int     status;
79         int     wpid;
80         
81         message(log, "Waiting for process %d.\n", pid);
82         while ( (wpid = wait(&status)) != pid ) {
83                 if ( wpid > 0 ) {
84                         message(
85                          log
86                         ,"pid %d exited, status=%x.\n"
87                         ,wpid
88                         ,status);
89                 }
90         }
91         return wpid;
92 }
93
94 static int
95 run(const char* program, const char* const* arguments, 
96         const char* terminal, int get_enter)
97 {
98         static const char       control_characters[] = {
99                 '\003',
100                 '\034',
101                 '\177',
102                 '\025',
103                 '\004',
104                 '\0',
105                 '\1',
106                 '\0',
107                 '\021',
108                 '\023',
109                 '\032',
110                 '\0',
111                 '\022',
112                 '\017',
113                 '\027',
114                 '\026',
115                 '\0'
116         };
117
118         static char * environment[] = {
119                 "HOME=/",
120                 "PATH=/bin:/sbin:/usr/bin:/usr/sbin",
121                 "SHELL=/bin/sh",
122                 0,
123                 "USER=root",
124                 0
125         };
126
127         static const char       press_enter[] =
128  "\nPlease press Enter to activate this console. ";
129
130         int     pid;
131
132         environment[3]=term_ptr;
133
134         pid = fork();
135         if ( pid == 0 ) {
136                 struct termios  t;
137                 const char * const * arg;
138
139                 close(0);
140                 close(1);
141                 close(2);
142                 setsid();
143
144                 open(terminal, O_RDWR);
145                 dup(0);
146                 dup(0);
147                 tcsetpgrp(0, getpgrp());
148
149                 tcgetattr(0, &t);
150                 memcpy(t.c_cc, control_characters, sizeof(control_characters));
151                 t.c_line = 0;
152                 t.c_iflag = ICRNL|IXON|IXOFF;
153                 t.c_oflag = OPOST|ONLCR;
154                 t.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN;
155                 tcsetattr(0, TCSANOW, &t);
156
157                 if ( get_enter ) {
158                         /*
159                          * Save memory by not exec-ing anything large (like a shell)
160                          * before the user wants it. This is critical if swap is not
161                          * enabled and the system has low memory. Generally this will
162                          * be run on the second virtual console, and the first will
163                          * be allowed to start a shell or whatever an init script 
164                          * specifies.
165                          */
166                         char    c;
167                         write(1, press_enter, sizeof(press_enter) - 1);
168                         read(0, &c, 1);
169                 }
170                 
171                 message(log, "Executing ");
172                 arg = arguments;
173                 while ( *arg != 0 )
174                         message(log, "%s ", *arg++);
175                 message(log, "\n");
176
177                 execve(program, (char * *)arguments, (char * *)environment);
178                 message(log, "%s: could not execute: %s.\r\n", program, strerror(errno));
179                 exit(-1);
180         }
181         return pid;
182 }
183
184 static int
185 mem_total()
186 {
187     char s[80];
188     char *p;
189     FILE *f;
190     const char pattern[]="MemTotal:";
191
192     f=fopen("/proc/meminfo","r");
193     while (NULL != fgets(s,79,f)) {
194         p=strstr(s, pattern);
195         if (NULL != p) {
196             fclose(f);
197             return(atoi(p+strlen(pattern)));
198         }
199     }
200     return -1;
201 }
202
203 static void
204 set_free_pages()
205 {
206     char s[80];
207     FILE *f;
208
209     f=fopen("/proc/sys/vm/freepages","r");
210     fgets(s,79,f);
211     if (atoi(s) < 32) {
212         fclose(f);
213         f=fopen("/proc/sys/vm/freepages","w");
214         fprintf(f,"30\t40\t50\n");
215         printf("\nIncreased /proc/sys/vm/freepages values to 30/40/50\n");
216     }
217     fclose(f);
218 }
219
220 static void
221 shutdown_system(void)
222 {
223         static const char * const umount_args[] = {"/bin/umount", "-a", "-n", 0};
224         static const char * const swapoff_args[] = {"/bin/swapoff", "-a", 0};
225
226         message(console, "The system is going down NOW !!");
227         sync();
228         /* Allow Ctrl-Alt-Del to reboot system. */
229         reboot(RB_ENABLE_CAD);
230
231         /* Send signals to every process _except_ pid 1 */
232         message(console, "Sending SIGHUP to all processes.\r\n");
233         kill(-1, SIGHUP);
234         sleep(2);
235         sync();
236         message(console, "Sending SIGKILL to all processes.\r\n");
237         kill(-1, SIGKILL);
238         sleep(1);
239         waitfor(run("/bin/swapoff", swapoff_args, console, 0));
240         waitfor(run("/bin/umount", umount_args, console, 0));
241         sync();
242         if (get_kernel_revision() <= 2*65536+2*256+11) {
243             /* Removed  bdflush call, kupdate in kernels >2.2.11 */
244             bdflush(1, 0);
245             sync();
246         }
247 }
248
249 static void
250 halt_signal(int sig)
251 {
252     shutdown_system();
253     message(console, "The system is halted. Press CTRL-ALT-DEL or turn off power\r\n");
254     reboot( RB_HALT_SYSTEM);
255     exit(0);
256 }
257
258 static void
259 reboot_signal(int sig)
260 {
261     shutdown_system();
262     message(console, "Please stand by while rebooting the system.\r\n");
263     reboot( RB_AUTOBOOT);
264     exit(0);
265 }
266
267 static void
268 configure_terminals( int serial_cons, int single_user_mode )
269 {
270         char *tty;
271         struct serial_struct sr;
272         struct vt_stat vt;
273
274
275         switch (serial_cons) {
276         case -1:
277             /* 2.2 kernels:
278             * identify the real console backend and try to make use of it */
279             if (ioctl(0,TIOCGSERIAL,&sr) == 0) {
280                 sprintf( console, "/dev/ttyS%d", sr.line );
281                 serial_cons = sr.line+1;
282             }
283             else if (ioctl(0, VT_GETSTATE, &vt) == 0) {
284                 sprintf( console, "/dev/tty%d", vt.v_active );
285                 serial_cons = 0;
286             }
287             else {
288                 /* unknown backend: fallback to /dev/console */
289                 strcpy( console, "/dev/console" );
290                 serial_cons = 0;
291             }
292             break;
293
294         case 1:
295                 strcpy( console, "/dev/cua0" );
296                 break;
297         case 2:
298                 strcpy( console, "/dev/cua1" );
299                 break;
300         default:
301                 tty = ttyname(0);
302                 if (tty) {
303                         strcpy( console, tty );
304                         if (!strncmp( tty, "/dev/cua", 8 ))
305                                 serial_cons=1;
306                 }
307                 else
308                         /* falls back to /dev/tty1 if an error occurs */
309                         strcpy( console, default_console );
310         }
311         if (!first_terminal)
312                 first_terminal = console;
313 #if defined (__sparc__)
314         if (serial_cons > 0 && !strncmp(term_ptr,"TERM=linux",10))
315             term_ptr = "TERM=vt100";
316 #endif
317         if (serial_cons) {
318             /* disable other but the first terminal:
319             * VT is not initialized anymore on 2.2 kernel when booting from
320             * serial console, therefore modprobe is flooding the display with
321             * "can't locate module char-major-4" messages. */
322             log = 0;
323             second_terminal = 0;
324         }
325 }
326
327 extern int
328 init_main(int argc, char * * argv)
329 {
330         const char *                rc_arguments[100];
331         const char *                arguments[100];
332         int                         run_rc = TRUE;
333         int                         j;
334         int                         pid1 = 0;
335         int                         pid2 = 0;
336         struct stat                 statbuf;
337         const char *                tty_commands[3] = { "etc/init.d/rcS", "bin/sh"};
338         int                         serial_console = 0;
339         int retval;
340
341         /*
342          * If I am started as /linuxrc instead of /sbin/init, I don't have the
343          * environment that init expects. I can't fix the signal behavior. Try
344          * to divorce from the controlling terminal with setsid(). This won't work
345          * if I am the process group leader.
346          */
347         setsid();
348
349         signal(SIGUSR1, halt_signal);
350         signal(SIGUSR2, reboot_signal);
351         signal(SIGINT, reboot_signal);
352         signal(SIGTERM, reboot_signal);
353
354         reboot(RB_DISABLE_CAD);
355
356         message(log, "%s: started. ", argv[0]);
357
358         for ( j = 1; j < argc; j++ ) {
359                 if ( strcmp(argv[j], "single") == 0 ) {
360                         run_rc = FALSE;
361                         tty_commands[0] = "bin/sh";
362                         tty_commands[1] = 0;
363                 }
364         }
365         for ( j = 0; __environ[j] != 0; j++ ) {
366                 if ( strncmp(__environ[j], "tty", 3) == 0
367                  && __environ[j][3] >= '1'
368                  && __environ[j][3] <= '2'
369                  && __environ[j][4] == '=' ) {
370                         const char * s = &__environ[j][5];
371
372                         if ( *s == 0 || strcmp(s, "off") == 0 )
373                                 s = 0;
374
375                         tty_commands[__environ[j][3] - '1'] = s;
376                 }
377                 /* Should catch the syntax of Sparc kernel console setting.   */
378                 /* The kernel does not recognize a serial console when getting*/
379                 /* console=/dev/ttySX !! */
380                 else if ( strcmp(__environ[j], "console=ttya") == 0 ) {
381                         serial_console=1;
382                 }
383                 else if ( strcmp(__environ[j], "console=ttyb") == 0 ) {
384                         serial_console=2;
385                 }
386                 /* standard console settings */
387                 else if ( strncmp(__environ[j], "console=", 8) == 0 ) {
388                         first_terminal=&(__environ[j][8]);
389                 }
390                 else if ( strncmp(__environ[j], "TERM=", 5) == 0) {
391                         term_ptr=__environ[j];
392                 }
393         }
394
395         printf("mounting /proc ...\n");
396         if (mount("/proc","/proc","proc",0,0)) {
397           perror("mounting /proc failed\n");
398         }
399         printf("\tdone.\n");
400
401         if (get_kernel_revision() >= 2*65536+1*256+71) {
402             /* if >= 2.1.71 kernel, /dev/console is not a symlink anymore:
403             * use it as primary console */
404             serial_console=-1;
405         }
406
407         /* Make sure /etc/init.d/rc exists */
408         retval= stat(tty_commands[0],&statbuf);
409         if (retval)
410             run_rc = FALSE;
411
412         configure_terminals( serial_console,  run_rc);
413
414         set_free_pages();
415
416         /* not enough memory to do anything useful*/
417         if (mem_total() < 2000) { 
418             retval= stat("/etc/fstab",&statbuf);
419             if (retval) {
420                 printf("You do not have enough RAM, sorry.\n");
421                 while (1) { sleep(1);}
422             } else { 
423               /* Try to turn on swap */
424                 static const char * const swapon_args[] = {"/bin/swapon", "-a", 0};
425                 waitfor(run("/bin/swapon", swapon_args, console, 0));
426                 if (mem_total() < 2000) { 
427                     printf("You do not have enough RAM, sorry.\n");
428                     while (1) { sleep(1);}
429                 }
430             }
431         }
432
433         /*
434          * Don't modify **argv directly, it would show up in the "ps" display.
435          * I don't want "init" to look like "rc".
436          */
437         rc_arguments[0] = tty_commands[0];
438         for ( j = 1; j < argc; j++ ) {
439                 rc_arguments[j] = argv[j];
440         }
441         rc_arguments[j] = 0;
442
443         arguments[0] = "-sh";
444         arguments[1] = 0;
445         
446         /* Ok, now launch the rc script /etc/init.d/rcS and prepare to start up
447          * some VTs on tty1 and tty2 if somebody hits enter 
448          */
449         for ( ; ; ) {
450                 int     wpid;
451                 int     status;
452
453                 if ( pid1 == 0  && tty_commands[0] ) {
454                    if ( run_rc == TRUE ) {
455                         pid1 = run(tty_commands[0], rc_arguments, first_terminal, 0);
456                    } else {
457                         pid2 = run(tty_commands[1], arguments, first_terminal, 1);
458                    }
459                 }
460                 if ( pid2 == 0 && second_terminal && tty_commands[1] ) {
461                         pid2 = run(tty_commands[1], arguments, second_terminal, 1);
462                 }
463                 wpid = wait(&status);
464                 if ( wpid > 0  && wpid != pid1) {
465                         message(log, "pid %d exited, status=%x.\n", wpid, status);
466                 }
467                 if ( wpid == pid2 ) {
468                         pid2 = 0;
469                 }
470         }
471 }
472