top: remove GCCisms
[oweals/busybox.git] / init / init.c
index ed01e71c0fc311a73deed301ccc0a9b7326f381a..6bee8f35ca5bf49c3c3879ddaa2b48a917ef3703 100644 (file)
@@ -14,6 +14,7 @@
 #include <paths.h>
 #include <sys/reboot.h>
 #include <sys/resource.h>
+#include <linux/vt.h>
 
 
 /* Was a CONFIG_xxx option. A lot of people were building
@@ -95,18 +96,15 @@ enum {
 #endif
 };
 
-static void halt_reboot_pwoff(int sig) NORETURN;
-
 /* Print a message to the specified device.
  * "where" may be bitwise-or'd from L_LOG | L_CONSOLE
  * NB: careful, we can be called after vfork!
  */
-#define messageD(...) do { if (DEBUG_INIT) message(__VA_ARGS__); } while (0)
+#define dbg_message(...) do { if (DEBUG_INIT) message(__VA_ARGS__); } while (0)
 static void message(int where, const char *fmt, ...)
        __attribute__ ((format(printf, 2, 3)));
 static void message(int where, const char *fmt, ...)
 {
-       static int log_fd = -1;
        va_list arguments;
        unsigned l;
        char msg[128];
@@ -116,20 +114,23 @@ static void message(int where, const char *fmt, ...)
        l = 1 + vsnprintf(msg + 1, sizeof(msg) - 2, fmt, arguments);
        if (l > sizeof(msg) - 1)
                l = sizeof(msg) - 1;
-       msg[l] = '\0';
        va_end(arguments);
 
-       if (ENABLE_FEATURE_INIT_SYSLOG) {
-               if (where & L_LOG) {
-                       /* Log the message to syslogd */
-                       openlog("init", 0, LOG_DAEMON);
-                       /* don't print "\r" */
-                       syslog(LOG_INFO, "%s", msg + 1);
-                       closelog();
-               }
-               msg[l++] = '\n';
-               msg[l] = '\0';
-       } else {
+#if ENABLE_FEATURE_INIT_SYSLOG
+       msg[l] = '\0';
+       if (where & L_LOG) {
+               /* Log the message to syslogd */
+               openlog("init", 0, LOG_DAEMON);
+               /* don't print "\r" */
+               syslog(LOG_INFO, "%s", msg + 1);
+               closelog();
+       }
+       msg[l++] = '\n';
+       msg[l] = '\0';
+#else
+       {
+               static int log_fd = -1;
+
                msg[l++] = '\n';
                msg[l] = '\0';
                /* Take full control of the log tty, and never close it.
@@ -153,6 +154,7 @@ static void message(int where, const char *fmt, ...)
                                return; /* don't print dup messages */
                }
        }
+#endif
 
        if (where & L_CONSOLE) {
                /* Send console messages to console so people will see them. */
@@ -160,33 +162,9 @@ static void message(int where, const char *fmt, ...)
        }
 }
 
-/* From <linux/serial.h> */
-struct serial_struct {
-       int     type;
-       int     line;
-       unsigned int    port;
-       int     irq;
-       int     flags;
-       int     xmit_fifo_size;
-       int     custom_divisor;
-       int     baud_base;
-       unsigned short  close_delay;
-       char    io_type;
-       char    reserved_char[1];
-       int     hub6;
-       unsigned short  closing_wait; /* time to wait before closing */
-       unsigned short  closing_wait2; /* no longer used... */
-       unsigned char   *iomem_base;
-       unsigned short  iomem_reg_shift;
-       unsigned int    port_high;
-       unsigned long   iomap_base;     /* cookie passed into ioremap */
-       int     reserved[1];
-       /* Paranoia (imagine 64bit kernel overwriting 32bit userspace stack) */
-       uint32_t bbox_reserved[16];
-};
 static void console_init(void)
 {
-       struct serial_struct sr;
+       int vtno;
        char *s;
 
        s = getenv("CONSOLE");
@@ -199,7 +177,7 @@ static void console_init(void)
                        dup2(fd, STDOUT_FILENO);
                        xmove_fd(fd, STDERR_FILENO);
                }
-               messageD(L_LOG, "console='%s'", s);
+               dbg_message(L_LOG, "console='%s'", s);
        } else {
                /* Make sure fd 0,1,2 are not closed
                 * (so that they won't be used by future opens) */
@@ -210,8 +188,9 @@ static void console_init(void)
        }
 
        s = getenv("TERM");
-       if (ioctl(STDIN_FILENO, TIOCGSERIAL, &sr) == 0) {
-               /* Force the TERM setting to vt102 for serial console
+       if (ioctl(STDIN_FILENO, VT_OPENQRY, &vtno) != 0) {
+               /* Not a linux terminal, probably serial console.
+                * Force the TERM setting to vt102
                 * if TERM is set to linux (the default) */
                if (!s || strcmp(s, "linux") == 0)
                        putenv((char*)"TERM=vt102");
@@ -261,7 +240,7 @@ static void set_sane_term(void)
 
 /* Open the new terminal device.
  * NB: careful, we can be called after vfork! */
-static void open_stdio_to_tty(const char* tty_name, int exit_on_failure)
+static int open_stdio_to_tty(const char* tty_name)
 {
        /* empty tty_name means "use init's tty", else... */
        if (tty_name[0]) {
@@ -273,19 +252,13 @@ static void open_stdio_to_tty(const char* tty_name, int exit_on_failure)
                if (fd) {
                        message(L_LOG | L_CONSOLE, "can't open %s: %s",
                                tty_name, strerror(errno));
-                       if (exit_on_failure)
-                               _exit(EXIT_FAILURE);
-                       if (DEBUG_INIT)
-                               _exit(2);
-                       /* NB: we don't reach this if we were called after vfork.
-                        * Thus halt_reboot_pwoff() itself needs not be vfork-safe.
-                        */
-                       halt_reboot_pwoff(SIGUSR1); /* halt the system */
+                       return 0; /* failure */
                }
                dup2(STDIN_FILENO, STDOUT_FILENO);
                dup2(STDIN_FILENO, STDERR_FILENO);
        }
        set_sane_term();
+       return 1; /* success */
 }
 
 /* Wrapper around exec:
@@ -341,20 +314,22 @@ static pid_t run(const struct init_action *a)
 {
        pid_t pid;
 
+       /* Careful: don't be affected by a signal in vforked child */
+       sigprocmask_allsigs(SIG_BLOCK);
        if (BB_MMU && (a->action_type & ASKFIRST))
                pid = fork();
        else
                pid = vfork();
        if (pid < 0)
                message(L_LOG | L_CONSOLE, "can't fork");
-       if (pid)
+       if (pid) {
+               sigprocmask_allsigs(SIG_UNBLOCK);
                return pid; /* Parent or error */
+       }
 
        /* Child */
 
        /* Reset signal handlers that were set by the parent process */
-//TODO: block signals across fork(), prevent them to affect child before
-//signals are reset?
        bb_signals(0
                + (1 << SIGUSR1)
                + (1 << SIGUSR2)
@@ -364,12 +339,14 @@ static pid_t run(const struct init_action *a)
                + (1 << SIGHUP)
                + (1 << SIGTSTP)
                , SIG_DFL);
+       sigprocmask_allsigs(SIG_UNBLOCK);
 
        /* Create a new session and make ourself the process group leader */
        setsid();
 
        /* Open the new terminal device */
-       open_stdio_to_tty(a->terminal, 1 /* - exit if open fails */);
+       if (!open_stdio_to_tty(a->terminal))
+               _exit(EXIT_FAILURE);
 
        /* NB: on NOMMU we can't wait for input in child, so
         * "askfirst" will work the same as "respawn". */
@@ -388,7 +365,7 @@ static pid_t run(const struct init_action *a)
                 * be allowed to start a shell or whatever an init script
                 * specifies.
                 */
-               messageD(L_LOG, "waiting for enter to start '%s'"
+               dbg_message(L_LOG, "waiting for enter to start '%s'"
                                        "(pid %d, tty '%s')\n",
                                a->command, getpid(), a->terminal);
                full_write(STDOUT_FILENO, press_enter, sizeof(press_enter) - 1);
@@ -422,29 +399,16 @@ static pid_t run(const struct init_action *a)
        _exit(-1);
 }
 
-static void delete_init_action(struct init_action *action)
-{
-       struct init_action *a, **nextp;
-
-       nextp = &init_action_list;
-       while ((a = *nextp) != NULL) {
-               if (a == action) {
-                       *nextp = a->next;
-                       free(a);
-                       break;
-               }
-               nextp = &a->next;
-       }
-}
-
-static struct init_action *mark_terminated(int pid)
+static struct init_action *mark_terminated(pid_t pid)
 {
        struct init_action *a;
 
-       for (a = init_action_list; a; a = a->next) {
-               if (a->pid == pid) {
-                       a->pid = 0;
-                       return a;
+       if (pid > 0) {
+               for (a = init_action_list; a; a = a->next) {
+                       if (a->pid == pid) {
+                               a->pid = 0;
+                               return a;
+                       }
                }
        }
        return NULL;
@@ -463,7 +427,7 @@ static void waitfor(pid_t pid)
                mark_terminated(wpid);
                /* Unsafe. SIGTSTP handler might have wait'ed it already */
                /*if (wpid == pid) break;*/
-               /* More reliable */
+               /* More reliable: */
                if (kill(pid, 0))
                        break;
        }
@@ -497,36 +461,44 @@ static void new_init_action(uint8_t action_type, const char *command, const char
 {
        struct init_action *a, **nextp;
 
-//BUG
-//old:
-//::shutdown:umount -a -r
-//::shutdown:swapoff -a
-//new: swapped:
-//::shutdown:swapoff -a
-//::shutdown:umount -a -r
-//on SIGHUP, new one will be loaded, but order will be wrong.
+       /* Scenario:
+        * old inittab:
+        * ::shutdown:umount -a -r
+        * ::shutdown:swapoff -a
+        * new inittab:
+        * ::shutdown:swapoff -a
+        * ::shutdown:umount -a -r
+        * On reload, we must ensure entries end up in correct order.
+        * To achieve that, if we find a matching entry, we move it
+        * to the end.
+        */
        nextp = &init_action_list;
        while ((a = *nextp) != NULL) {
                /* Don't enter action if it's already in the list,
-                * just overwrite existing one's type.
                 * This prevents losing running RESPAWNs.
                 */
                if ((strcmp(a->command, command) == 0)
                 && (strcmp(a->terminal, cons) == 0)
                ) {
-                       a->action_type = action_type;
-                       return;
+                       /* Remove from list */
+                       *nextp = a->next;
+                       /* Find the end of the list */
+                       while (*nextp != NULL)
+                               nextp = &(*nextp)->next;
+                       a->next = NULL;
+                       break;
                }
                nextp = &a->next;
        }
 
+       if (!a)
+               a = xzalloc(sizeof(*a));
        /* Append to the end of the list */
-       a = xzalloc(sizeof(*a));
        *nextp = a;
        a->action_type = action_type;
        safe_strncpy(a->command, command, sizeof(a->command));
        safe_strncpy(a->terminal, cons, sizeof(a->terminal));
-       messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
+       dbg_message(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
                a->command, a->action_type, a->terminal);
 }
 
@@ -603,8 +575,8 @@ static void parse_inittab(void)
 #endif
 }
 
-static void low_level_reboot(unsigned magic) NORETURN;
-static void low_level_reboot(unsigned magic)
+static void pause_and_low_level_reboot(unsigned magic) NORETURN;
+static void pause_and_low_level_reboot(unsigned magic)
 {
        pid_t pid;
 
@@ -619,12 +591,11 @@ static void low_level_reboot(unsigned magic)
                reboot(magic);
                _exit(EXIT_SUCCESS);
        }
-       waitfor(pid);
        while (1)
                sleep(1);
 }
 
-static void kill_all_processes(void)
+static void run_shutdown_and_kill_processes(void)
 {
        /* Run everything to be run at "shutdown".  This is done _prior_
         * to killing everything, in case people wish to use scripts to
@@ -633,19 +604,16 @@ static void kill_all_processes(void)
 
        message(L_CONSOLE | L_LOG, "The system is going down NOW!");
 
-       /* Allow Ctrl-Alt-Del to reboot system. */
-       reboot(RB_ENABLE_CAD); /* misnomer */
-
        /* Send signals to every process _except_ pid 1 */
-       message(L_CONSOLE | L_LOG, "Sending SIG%s to all processes", "TERM");
        kill(-1, SIGTERM);
+       message(L_CONSOLE | L_LOG, "Sent SIG%s to all processes", "TERM");
        sync();
        sleep(1);
 
-       message(L_CONSOLE, "Sending SIG%s to all processes", "KILL");
        kill(-1, SIGKILL);
+       message(L_CONSOLE, "Sent SIG%s to all processes", "KILL");
        sync();
-       sleep(1);
+       /*sleep(1); - callers take care about making a pause */
 }
 
 /* Signal handling by init:
@@ -681,15 +649,16 @@ static void kill_all_processes(void)
  * it to happen even somewhere inside "sysinit" would be a bit awkward.
  *
  * There is a tiny probability that SIGHUP and Ctrl-Alt-Del will collide
- * and only one will be remebered and acted upon.
+ * and only one will be remembered and acted upon.
  */
 
+static void halt_reboot_pwoff(int sig) NORETURN;
 static void halt_reboot_pwoff(int sig)
 {
        const char *m;
        unsigned rb;
 
-       kill_all_processes();
+       run_shutdown_and_kill_processes();
 
        m = "halt";
        rb = RB_HALT_SYSTEM;
@@ -701,7 +670,7 @@ static void halt_reboot_pwoff(int sig)
                rb = RB_POWER_OFF;
        }
        message(L_CONSOLE, "Requesting system %s", m);
-       low_level_reboot(rb);
+       pause_and_low_level_reboot(rb);
        /* not reached */
 }
 
@@ -711,11 +680,11 @@ static void halt_reboot_pwoff(int sig)
  */
 static void stop_handler(int sig UNUSED_PARAM)
 {
-       int saved_errno;
        smallint saved_bb_got_signal;
+       int saved_errno;
 
-       saved_errno = errno;
        saved_bb_got_signal = bb_got_signal;
+       saved_errno = errno;
        signal(SIGCONT, record_signo);
 
        while (1) {
@@ -728,14 +697,13 @@ static void stop_handler(int sig UNUSED_PARAM)
                 * code which is resilient against this.
                 */
                wpid = wait_any_nohang(NULL);
-               if (wpid > 0)
-                       mark_terminated(wpid);
+               mark_terminated(wpid);
                sleep(1);
        }
 
        signal(SIGCONT, SIG_DFL);
-       bb_got_signal = saved_bb_got_signal;
        errno = saved_errno;
+       bb_got_signal = saved_bb_got_signal;
 }
 
 /* Handler for QUIT - exec "restart" action,
@@ -752,11 +720,27 @@ static void restart_handler(int sig UNUSED_PARAM)
                 * Thus don't need to worry about preserving errno
                 * and such.
                 */
-               kill_all_processes();
-               open_stdio_to_tty(a->terminal, 0 /* - halt if open fails */);
-               messageD(L_CONSOLE, "Trying to re-exec %s", a->command);
-               init_exec(a->command);
-               low_level_reboot(RB_HALT_SYSTEM);
+               run_shutdown_and_kill_processes();
+
+               /* Allow Ctrl-Alt-Del to reboot the system.
+                * This is how kernel sets it up for init, we follow suit.
+                */
+               reboot(RB_ENABLE_CAD); /* misnomer */
+
+               if (open_stdio_to_tty(a->terminal)) {
+                       dbg_message(L_CONSOLE, "Trying to re-exec %s", a->command);
+                       /* Theoretically should be safe.
+                        * But in practice, kernel bugs may leave
+                        * unkillable processes, and wait() may block forever.
+                        * Oh well. Hoping "new" init won't be too surprised
+                        * by having children it didn't create.
+                        */
+                       //while (wait(NULL) > 0)
+                       //      continue;
+                       init_exec(a->command);
+               }
+               /* Open or exec failed */
+               pause_and_low_level_reboot(RB_HALT_SYSTEM);
                /* not reached */
        }
 }
@@ -764,40 +748,50 @@ static void restart_handler(int sig UNUSED_PARAM)
 #if ENABLE_FEATURE_USE_INITTAB
 static void reload_inittab(void)
 {
-       struct init_action *a, *tmp;
+       struct init_action *a, **nextp;
 
        message(L_LOG, "reloading /etc/inittab");
 
        /* Disable old entries */
-       for (a = init_action_list; a; a = a->next) {
+       for (a = init_action_list; a; a = a->next)
                a->action_type = ONCE;
-       }
 
+       /* Append new entries, or modify existing entries
+        * (set a->action_type) if cmd and device name
+        * match new ones. End result: only entries with
+        * a->action_type == ONCE are stale.
+        */
        parse_inittab();
 
-       if (ENABLE_FEATURE_KILL_REMOVED) {
-               /* Be nice and send SIGTERM first */
-               for (a = init_action_list; a; a = a->next)
-                       if (a->pid != 0)
-                               kill(a->pid, SIGTERM);
-               if (CONFIG_FEATURE_KILL_DELAY) {
-                       /* NB: parent will wait in NOMMU case */
-                       if ((BB_MMU ? fork() : vfork()) == 0) { /* child */
-                               sleep(CONFIG_FEATURE_KILL_DELAY);
-                               for (a = init_action_list; a; a = a->next)
-                                       if (a->pid != 0)
-                                               kill(a->pid, SIGKILL);
-                               _exit(EXIT_SUCCESS);
-                       }
+#if ENABLE_FEATURE_KILL_REMOVED
+       /* Kill stale entries */
+       /* Be nice and send SIGTERM first */
+       for (a = init_action_list; a; a = a->next)
+               if (a->action_type == ONCE && a->pid != 0)
+                       kill(a->pid, SIGTERM);
+       if (CONFIG_FEATURE_KILL_DELAY) {
+               /* NB: parent will wait in NOMMU case */
+               if ((BB_MMU ? fork() : vfork()) == 0) { /* child */
+                       sleep(CONFIG_FEATURE_KILL_DELAY);
+                       for (a = init_action_list; a; a = a->next)
+                               if (a->action_type == ONCE && a->pid != 0)
+                                       kill(a->pid, SIGKILL);
+                       _exit(EXIT_SUCCESS);
                }
        }
+#endif
 
-       /* Remove old and unused entries */
-       for (a = init_action_list; a; a = tmp) {
-               tmp = a->next;
-               if (a->action_type & (ONCE | SYSINIT | WAIT))
-                       delete_init_action(a);
+       /* Remove stale (ONCE) and not useful (SYSINIT,WAIT) entries */
+       nextp = &init_action_list;
+       while ((a = *nextp) != NULL) {
+               if (a->action_type & (ONCE | SYSINIT | WAIT)) {
+                       *nextp = a->next;
+                       free(a);
+               } else {
+                       nextp = &a->next;
+               }
        }
+
        /* Not needed: */
        /* run_actions(RESPAWN | ASKFIRST); */
        /* - we return to main loop, which does this automagically */
@@ -868,7 +862,7 @@ int init_main(int argc UNUSED_PARAM, char **argv)
                struct sysinfo info;
 
                if (sysinfo(&info) == 0
-                && (info.mem_unit ? : 1) * (long long)info.totalram < 1024*1024
+                && (info.mem_unit ? info.mem_unit : 1) * (long long)info.totalram < 1024*1024
                ) {
                        message(L_CONSOLE, "Low memory, forcing swapon");
                        /* swapon -a requires /proc typically */
@@ -966,43 +960,48 @@ int init_main(int argc UNUSED_PARAM, char **argv)
                bb_signals_recursive_norestart((1 << SIGHUP), record_signo);
 
        /* Now run the looping stuff for the rest of forever.
-        * NB: if delayed signal happened, avoid blocking in wait().
         */
        while (1) {
-               pid_t wpid;
-               int got_sigs;
+               int maybe_WNOHANG;
 
-               got_sigs = check_delayed_sigs();
+               maybe_WNOHANG = check_delayed_sigs();
 
                /* (Re)run the respawn/askfirst stuff */
                run_actions(RESPAWN | ASKFIRST);
-
-               got_sigs |= check_delayed_sigs();
+               maybe_WNOHANG |= check_delayed_sigs();
 
                /* Don't consume all CPU time - sleep a bit */
                sleep(1);
-
-               got_sigs |= check_delayed_sigs();
-
-               /* Wait for any child process to exit.
-                * NB: "delayed" signals will also interrupt this wait(),
-                * bb_signals_recursive_norestart() set them up for that.
-                * This guarantees we won't be stuck here
-                * till next orphan dies.
+               maybe_WNOHANG |= check_delayed_sigs();
+
+               /* Wait for any child process(es) to exit.
+                *
+                * If check_delayed_sigs above reported that a signal
+                * was caught, wait will be nonblocking. This ensures
+                * that if SIGHUP has reloaded inittab, respawn and askfirst
+                * actions will not be delayed until next child death.
                 */
-               if (got_sigs)
-                       goto dont_block;
-               wpid = wait(NULL);
-               while (wpid > 0) {
-                       struct init_action *a = mark_terminated(wpid);
+               if (maybe_WNOHANG)
+                       maybe_WNOHANG = WNOHANG;
+               while (1) {
+                       pid_t wpid;
+                       struct init_action *a;
+
+                       /* If signals happen _in_ the wait, they interrupt it,
+                        * bb_signals_recursive_norestart set them up that way
+                        */
+                       wpid = waitpid(-1, NULL, maybe_WNOHANG);
+                       if (wpid <= 0)
+                               break;
+
+                       a = mark_terminated(wpid);
                        if (a) {
                                message(L_LOG, "process '%s' (pid %d) exited. "
                                                "Scheduling for restart.",
                                                a->command, wpid);
                        }
                        /* See if anyone else is waiting to be reaped */
- dont_block:
-                       wpid = wait_any_nohang(NULL);
+                       maybe_WNOHANG = WNOHANG;
                }
        } /* while (1) */
 }