Implement mock functionality for some system calls in bp_sys.
[oweals/dinit.git] / src / dinit.cc
index 7d3c2fd0a38fb19c00d0df481d3153fb7101a95e..105403279ff821a7bfc3e78ad4ccf7367bfcb283 100644 (file)
 #include <unistd.h>
 #include <fcntl.h>
 #include <pwd.h>
+#ifdef __linux__
+#include <sys/prctl.h>
+#include <sys/klog.h>
+#include <sys/reboot.h>
+#endif
 
+#include "dinit.h"
 #include "dasynq.h"
 #include "service.h"
 #include "control.h"
 #include "dinit-log.h"
 #include "dinit-socket.h"
 
-#ifdef __linux__
-#include <sys/klog.h>
-#include <sys/reboot.h>
-#endif
-
 /*
  * When running as the system init process, Dinit processes the following signals:
  *
  * services even if the halt/reboot commands are unavailable for some reason.
  */
 
+using eventloop_t = dasynq::event_loop<dasynq::null_mutex>;
 
-using namespace dasynq;
-using eventloop_t = event_loop<null_mutex>;
-
-eventloop_t eventLoop = eventloop_t();
+eventloop_t event_loop;
 
 static void sigint_reboot_cb(eventloop_t &eloop) noexcept;
 static void sigquit_cb(eventloop_t &eloop) noexcept;
@@ -51,9 +50,6 @@ static void wait_for_user_input() noexcept;
 
 static void control_socket_cb(eventloop_t *loop, int fd);
 
-void open_control_socket(bool report_ro_failure = true) noexcept;
-void setup_external_log() noexcept;
-
 
 // Variables
 
@@ -95,6 +91,8 @@ const char * get_user_home()
 namespace {
     class callback_signal_handler : public eventloop_t::signal_watcher_impl<callback_signal_handler>
     {
+        using rearm = dasynq::rearm;
+
         public:
         typedef void (*cb_func_t)(eventloop_t &);
         
@@ -119,6 +117,8 @@ namespace {
 
     class control_socket_watcher : public eventloop_t::fd_watcher_impl<control_socket_watcher>
     {
+        using rearm = dasynq::rearm;
+
         public:
         rearm fd_event(eventloop_t &loop, int fd, int flags) noexcept
         {
@@ -219,7 +219,7 @@ int dinit_main(int argc, char **argv)
         if (onefd > 2) close(onefd);
         if (twofd > 2) close(twofd);
     }
-    
+
     /* Set up signal handlers etc */
     /* SIG_CHILD is ignored by default: good */
     sigset_t sigwait_set;
@@ -229,7 +229,7 @@ int dinit_main(int argc, char **argv)
     sigaddset(&sigwait_set, SIGTERM);
     if (am_system_init) sigaddset(&sigwait_set, SIGQUIT);
     sigprocmask(SIG_BLOCK, &sigwait_set, NULL);
-    
+
     // Terminal access control signals - we block these so that dinit can't be
     // suspended if it writes to the terminal after some other process has claimed
     // ownership of it.
@@ -279,12 +279,12 @@ int dinit_main(int argc, char **argv)
         sigint_watcher.setCbFunc(sigterm_cb);
     }
 
-    sigint_watcher.add_watch(eventLoop, SIGINT);
-    sigterm_watcher.add_watch(eventLoop, SIGTERM);
+    sigint_watcher.add_watch(event_loop, SIGINT);
+    sigterm_watcher.add_watch(event_loop, SIGTERM);
     
     if (am_system_init) {
         // PID 1: SIGQUIT exec's shutdown
-        sigquit_watcher.add_watch(eventLoop, SIGQUIT);
+        sigquit_watcher.add_watch(event_loop, SIGQUIT);
         // As a user process, we instead just let SIGQUIT perform the default action.
     }
 
@@ -298,6 +298,10 @@ int dinit_main(int argc, char **argv)
         // Make ctrl+alt+del combination send SIGINT to PID 1 (this process)
         reboot(RB_DISABLE_CAD);
     }
+
+    // Mark ourselves as a subreaper. This means that if a process we start double-forks, the
+    // orphaned child will re-parent to us rather than to PID 1 (although that could be us too).
+    prctl(PR_SET_CHILD_SUBREAPER, 1);
 #endif
     
     /* start requested services */
@@ -314,13 +318,13 @@ int dinit_main(int argc, char **argv)
             // exit if user process).
         }
         catch (service_not_found &snf) {
-            log(LogLevel::ERROR, snf.serviceName, ": Could not find service description.");
+            log(loglevel_t::ERROR, snf.serviceName, ": Could not find service description.");
         }
         catch (service_load_exc &sle) {
-            log(LogLevel::ERROR, sle.serviceName, ": ", sle.excDescription);
+            log(loglevel_t::ERROR, sle.serviceName, ": ", sle.excDescription);
         }
         catch (std::bad_alloc &badalloce) {
-            log(LogLevel::ERROR, "Out of memory when trying to start service: ", svc, ".");
+            log(loglevel_t::ERROR, "Out of memory when trying to start service: ", svc, ".");
             break;
         }
     }
@@ -329,30 +333,30 @@ int dinit_main(int argc, char **argv)
     
     // Process events until all services have terminated.
     while (services->count_active_services() != 0) {
-        eventLoop.run();
+        event_loop.run();
     }
 
     shutdown_type_t shutdown_type = services->getShutdownType();
     
     if (am_system_init) {
-        logMsgBegin(LogLevel::INFO, "No more active services.");
+        log_msg_begin(loglevel_t::INFO, "No more active services.");
         
         if (shutdown_type == shutdown_type_t::REBOOT) {
-            logMsgEnd(" Will reboot.");
+            log_msg_end(" Will reboot.");
         }
         else if (shutdown_type == shutdown_type_t::HALT) {
-            logMsgEnd(" Will halt.");
+            log_msg_end(" Will halt.");
         }
         else if (shutdown_type == shutdown_type_t::POWEROFF) {
-            logMsgEnd(" Will power down.");
+            log_msg_end(" Will power down.");
         }
         else {
-            logMsgEnd(" Re-initiating boot sequence.");
+            log_msg_end(" Re-initiating boot sequence.");
         }
     }
     
     while (! is_log_flushed()) {
-        eventLoop.run();
+        event_loop.run();
     }
     
     close_control_socket();
@@ -368,7 +372,7 @@ int dinit_main(int argc, char **argv)
             }
             catch (...) {
                 // Now what do we do? try to reboot, but wait for user ack to avoid boot loop.
-                log(LogLevel::ERROR, "Could not start 'boot' service. Will attempt reboot.");
+                log(loglevel_t::ERROR, "Could not start 'boot' service. Will attempt reboot.");
                 wait_for_user_input();
                 shutdown_type = shutdown_type_t::REBOOT;
             }
@@ -388,11 +392,11 @@ int dinit_main(int argc, char **argv)
         
         // Fork and execute dinit-reboot.
         execl("/sbin/shutdown", "/sbin/shutdown", "--system", cmd_arg, nullptr);
-        log(LogLevel::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno));
+        log(loglevel_t::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno));
         
         // PID 1 must not actually exit, although we should never reach this point:
         while (true) {
-            eventLoop.run();
+            event_loop.run();
         }
     }
     else if (shutdown_type == shutdown_type_t::REBOOT) {
@@ -419,19 +423,20 @@ static void wait_for_user_input() noexcept
 // Callback for control socket
 static void control_socket_cb(eventloop_t *loop, int sockfd)
 {
-    // TODO limit the number of active connections. Keep a tally, and disable the
-    // control connection listening socket watcher if it gets high, and re-enable
-    // it once it falls below the maximum.
+    // Considered keeping a limit the number of active connections, however, there doesn't
+    // seem much to be gained from that. Only root can create connections and not being
+    // able to establish a control connection is as much a denial-of-service as is not being
+    // able to start a service due to lack of fd's.
 
     // Accept a connection
     int newfd = dinit_accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
 
     if (newfd != -1) {
         try {
-            new control_conn_t(loop, services, newfd);  // will delete itself when it's finished
+            new control_conn_t(*loop, services, newfd);  // will delete itself when it's finished
         }
         catch (std::exception &exc) {
-            log(LogLevel::ERROR, "Accepting control connection: ", exc.what());
+            log(loglevel_t::ERROR, "Accepting control connection: ", exc.what());
             close(newfd);
         }
     }
@@ -446,7 +451,7 @@ void open_control_socket(bool report_ro_failure) noexcept
         
         struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
         if (name == nullptr) {
-            log(LogLevel::ERROR, "Opening control socket: out of memory");
+            log(loglevel_t::ERROR, "Opening control socket: out of memory");
             return;
         }
 
@@ -461,14 +466,14 @@ void open_control_socket(bool report_ro_failure) noexcept
 
         int sockfd = dinit_socket(AF_UNIX, SOCK_STREAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
         if (sockfd == -1) {
-            log(LogLevel::ERROR, "Error creating control socket: ", strerror(errno));
+            log(loglevel_t::ERROR, "Error creating control socket: ", strerror(errno));
             free(name);
             return;
         }
 
         if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
             if (errno != EROFS || report_ro_failure) {
-                log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno));
+                log(loglevel_t::ERROR, "Error binding control socket: ", strerror(errno));
             }
             close(sockfd);
             free(name);
@@ -480,24 +485,24 @@ void open_control_socket(bool report_ro_failure) noexcept
         // No connections can be made until we listen, so it is fine to change the permissions now
         // (and anyway there is no way to atomically create the socket and set permissions):
         if (chmod(saddrname, S_IRUSR | S_IWUSR) == -1) {
-            log(LogLevel::ERROR, "Error setting control socket permissions: ", strerror(errno));
+            log(loglevel_t::ERROR, "Error setting control socket permissions: ", strerror(errno));
             close(sockfd);
             return;
         }
 
         if (listen(sockfd, 10) == -1) {
-            log(LogLevel::ERROR, "Error listening on control socket: ", strerror(errno));
+            log(loglevel_t::ERROR, "Error listening on control socket: ", strerror(errno));
             close(sockfd);
             return;
         }
 
         try {
-            control_socket_io.add_watch(eventLoop, sockfd, IN_EVENTS);
+            control_socket_io.add_watch(event_loop, sockfd, dasynq::IN_EVENTS);
             control_socket_open = true;
         }
         catch (std::exception &e)
         {
-            log(LogLevel::ERROR, "Could not setup I/O on control socket: ", e.what());
+            log(loglevel_t::ERROR, "Could not setup I/O on control socket: ", e.what());
             close(sockfd);
         }
     }
@@ -507,7 +512,7 @@ static void close_control_socket() noexcept
 {
     if (control_socket_open) {
         int fd = control_socket_io.get_watched_fd();
-        control_socket_io.deregister(eventLoop);
+        control_socket_io.deregister(event_loop);
         close(fd);
         
         // Unlink the socket:
@@ -525,7 +530,7 @@ void setup_external_log() noexcept
         
         struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
         if (name == nullptr) {
-            log(LogLevel::ERROR, "Connecting to log socket: out of memory");
+            log(loglevel_t::ERROR, "Connecting to log socket: out of memory");
             return;
         }
         
@@ -534,19 +539,20 @@ void setup_external_log() noexcept
         
         int sockfd = dinit_socket(AF_UNIX, SOCK_DGRAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
         if (sockfd == -1) {
-            log(LogLevel::ERROR, "Error creating log socket: ", strerror(errno));
+            log(loglevel_t::ERROR, "Error creating log socket: ", strerror(errno));
             free(name);
             return;
         }
         
         if (connect(sockfd, (struct sockaddr *) name, sockaddr_size) == 0 || errno == EINPROGRESS) {
-            // TODO for EINPROGRESS, set up a watcher so we can properly wait until
-            // connection is established (or fails) before we pass it to the logging subsystem.
+            // For EINPROGRESS, connection is still being established; however, we can select on
+            // the file descriptor so we will be notified when it's ready. In other words we can
+            // basically use it anyway.
             try {
                 setup_main_log(sockfd);
             }
             catch (std::exception &e) {
-                log(LogLevel::ERROR, "Setting up log failed: ", e.what());
+                log(loglevel_t::ERROR, "Setting up log failed: ", e.what());
                 close(sockfd);
             }
         }
@@ -572,7 +578,7 @@ static void sigquit_cb(eventloop_t &eloop) noexcept
     // This performs an immediate shutdown, without service rollback.
     close_control_socket();
     execl("/sbin/shutdown", "/sbin/shutdown", "--system", (char *) 0);
-    log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
+    log(loglevel_t::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
     sync(); // since a hard poweroff might be required at this point...
 }