Update NEWS and version.
[oweals/dinit.git] / src / dinit.cc
index 1369d3bf4d5def0c6e7bb2d24a279d2e9bf3aaac..1bff5dc6b7c14edf3d8686da0ad4e740cecddefa 100644 (file)
@@ -13,6 +13,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <pwd.h>
+#include <termios.h>
 #ifdef __linux__
 #include <sys/prctl.h>
 #include <sys/klog.h>
@@ -29,6 +30,7 @@
 #include "dinit-log.h"
 #include "dinit-socket.h"
 #include "static-string.h"
+#include "dinit-utmp.h"
 
 #include "mconfig.h"
 
@@ -54,8 +56,9 @@ eventloop_t event_loop;
 static void sigint_reboot_cb(eventloop_t &eloop) noexcept;
 static void sigquit_cb(eventloop_t &eloop) noexcept;
 static void sigterm_cb(eventloop_t &eloop) noexcept;
+static void open_control_socket(bool report_ro_failure = true) noexcept;
 static void close_control_socket() noexcept;
-static void wait_for_user_input() noexcept;
+static void confirm_restart_boot() noexcept;
 static void read_env_file(const char *);
 
 static void control_socket_cb(eventloop_t *loop, int fd);
@@ -68,6 +71,7 @@ static dirload_service_set *services;
 static bool am_pid_one = false;     // true if we are PID 1
 static bool am_system_init = false; // true if we are the system init process
 
+static bool did_log_boot = false;
 static bool control_socket_open = false;
 static bool external_log_open = false;
 int active_control_conns = 0;
@@ -84,10 +88,12 @@ static bool log_is_syslog = true; // if false, log is a file
 
 static const char *user_home_path = nullptr;
 
+// Set to true (when console_input_watcher is active) if console input becomes available
+static bool console_input_ready = false;
 
 // Get user home (and set user_home_path). (The return may become invalid after
 // changing the evironment (HOME variable) or using the getpwuid() function).
-const char * get_user_home()
+static const char * get_user_home()
 {
     if (user_home_path == nullptr) {
         user_home_path = getenv("HOME");
@@ -143,6 +149,18 @@ namespace {
         }
     };
 
+    class console_input_watcher : public eventloop_t::fd_watcher_impl<console_input_watcher>
+    {
+        using rearm = dasynq::rearm;
+
+        public:
+        rearm fd_event(eventloop_t &loop, int fd, int flags) noexcept
+        {
+            console_input_ready = true;
+            return rearm::REARM;
+        }
+    };
+
     // Simple timer used to limit the amount of time waiting for the log flush to complete (at shutdown)
     class log_flush_timer_t : public eventloop_t::timer_impl<log_flush_timer_t>
     {
@@ -164,6 +182,7 @@ namespace {
     };
 
     control_socket_watcher control_socket_io;
+    console_input_watcher console_input_io;
     log_flush_timer_t log_flush_timer;
 }
 
@@ -352,6 +371,7 @@ int dinit_main(int argc, char **argv)
 
     sigint_watcher.add_watch(event_loop, SIGINT);
     sigterm_watcher.add_watch(event_loop, SIGTERM);
+    console_input_io.add_watch(event_loop, STDIN_FILENO, dasynq::IN_EVENTS, false);
     
     if (am_pid_one) {
         // PID 1: SIGQUIT exec's shutdown
@@ -428,7 +448,7 @@ int dinit_main(int argc, char **argv)
         }
     }
     
-    event_loop:
+    run_event_loop:
     
     // Process events until all services have terminated.
     while (services->count_active_services() != 0) {
@@ -436,6 +456,9 @@ int dinit_main(int argc, char **argv)
     }
 
     shutdown_type_t shutdown_type = services->get_shutdown_type();
+    if (shutdown_type == shutdown_type_t::REMAIN) {
+        goto run_event_loop;
+    }
     
     if (am_pid_one) {
         log_msg_begin(loglevel_t::INFO, "No more active services.");
@@ -449,9 +472,6 @@ int dinit_main(int argc, char **argv)
         else if (shutdown_type == shutdown_type_t::POWEROFF) {
             log_msg_end(" Will power down.");
         }
-        else {
-            log_msg_end(" Re-initiating boot sequence.");
-        }
     }
     
     log_flush_timer.arm_timer_rel(event_loop, timespec{5,0}); // 5 seconds
@@ -462,19 +482,23 @@ int dinit_main(int argc, char **argv)
     close_control_socket();
     
     if (am_pid_one) {
-        if (shutdown_type == shutdown_type_t::CONTINUE) {
-            // It could be that we started in single user mode, and the
-            // user has now exited the shell. We'll try and re-start the
-            // boot process...
-            try {
-                services->start_service("boot");
-                goto event_loop; // yes, the "evil" goto
-            }
-            catch (...) {
-                // Now what do we do? try to reboot, but wait for user ack to avoid boot loop.
-                log(loglevel_t::ERROR, "Could not start 'boot' service. Will attempt reboot.");
-                wait_for_user_input();
-                shutdown_type = shutdown_type_t::REBOOT;
+        if (shutdown_type == shutdown_type_t::NONE) {
+            // Services all stopped but there was no shutdown issued. Inform user, wait for ack, and
+            // re-start boot sequence.
+            std::cout << "No shutdown was requested; boot failure? Will re-run boot sequence." << std::endl;
+            sync(); // Sync to minimise data loss if user elects to power off / hard reset
+            confirm_restart_boot();
+            shutdown_type = services->get_shutdown_type();
+            if (shutdown_type == shutdown_type_t::NONE) {
+                try {
+                    services->start_service("boot");
+                    goto run_event_loop; // yes, the "evil" goto
+                }
+                catch (...) {
+                    // Now what do we do? try to reboot, but wait for user ack to avoid boot loop.
+                    log(loglevel_t::ERROR, "Could not start 'boot' service. Will attempt reboot.");
+                    shutdown_type = shutdown_type_t::REBOOT;
+                }
             }
         }
         
@@ -503,11 +527,11 @@ int dinit_main(int argc, char **argv)
     }
     else if (shutdown_type == shutdown_type_t::REBOOT) {
         // Non-system-process. If we got SIGINT, let's die due to it:
-        sigset_t sigwait_set;
-        sigemptyset(&sigwait_set);
-        sigaddset(&sigwait_set, SIGINT);
+        sigset_t sigwait_set_int;
+        sigemptyset(&sigwait_set_int);
+        sigaddset(&sigwait_set_int, SIGINT);
         raise(SIGINT);
-        sigprocmask(SIG_UNBLOCK, &sigwait_set, NULL);
+        sigprocmask(SIG_UNBLOCK, &sigwait_set_int, NULL);
     }
     
     return 0;
@@ -573,13 +597,31 @@ static void read_env_file(const char *env_file_path)
     }
 }
 
-// In exception situations we want user confirmation before proceeding (eg on critical boot failure
-// we wait before rebooting to avoid a reboot loop).
-static void wait_for_user_input() noexcept
+// Get user confirmation before proceeding with restarting boot sequence.
+static void confirm_restart_boot() noexcept
 {
+    // Bypass log; we want to make certain the message is seen:
     std::cout << "Press Enter to continue." << std::endl;
-    char buf[1];
-    read(STDIN_FILENO, buf, 1);
+
+    // Drain input, set non-blocking mode
+    tcflush(STDIN_FILENO, TCIFLUSH);
+    int origFlags = fcntl(STDIN_FILENO, F_GETFL);
+
+    console_input_io.set_enabled(event_loop, true);
+    do {
+        event_loop.run();
+    } while (! console_input_ready && services->get_shutdown_type() == shutdown_type_t::NONE);
+    console_input_io.set_enabled(event_loop, false);
+
+    // We either have input, or shutdown type has been set, or both.
+    if (console_input_ready) {
+        char buf[1];
+        read(STDIN_FILENO, buf, 1);  // read a single character, to make sure we wait for input
+        tcflush(STDIN_FILENO, TCIFLUSH); // discard the rest of input
+        console_input_ready = false;
+    }
+
+    fcntl(STDIN_FILENO, F_SETFL, origFlags);
 }
 
 // Callback for control socket
@@ -604,11 +646,31 @@ static void control_socket_cb(eventloop_t *loop, int sockfd)
     }
 }
 
+// Callback when the root filesystem is read/write:
+void rootfs_is_rw() noexcept
+{
+    open_control_socket(true);
+    if (! did_log_boot) {
+        did_log_boot = log_boot();
+    }
+}
+
 // Open/create the control socket, normally /dev/dinitctl, used to allow client programs to connect
 // and issue service orders and shutdown commands etc. This can safely be called multiple times;
 // once the socket has been successfully opened, further calls have no effect.
-void open_control_socket(bool report_ro_failure) noexcept
+static void open_control_socket(bool report_ro_failure) noexcept
 {
+    if (control_socket_open) {
+        struct stat stat_buf;
+        if (stat(control_socket_path, &stat_buf) != 0 && errno == ENOENT) {
+            // Looks like our control socket has disappeared from the filesystem. Close our control
+            // socket and re-create it:
+            control_socket_io.deregister(event_loop);
+            close(control_socket_io.get_watched_fd());
+            control_socket_open = false; // now re-open below
+        }
+    }
+
     if (! control_socket_open) {
         const char * saddrname = control_socket_path;
         size_t saddrname_len = strlen(saddrname);