Clean up some TODOs
[oweals/dinit.git] / src / dinit.cc
index 6a2e78cef6f2674144f1dce63f3a91e47d2084e7..d7153a3e79a5e64b10c8006470421ca0402c21bb 100644 (file)
@@ -13,8 +13,8 @@
 #include <fcntl.h>
 #include <pwd.h>
 
+#include "dasynq.h"
 #include "service.h"
-#include "ev++.h"
 #include "control.h"
 #include "dinit-log.h"
 
  */
 
 
-static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents);
-static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents);
-static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents);
-void open_control_socket(struct ev_loop *loop) noexcept;
-void close_control_socket(struct ev_loop *loop) noexcept;
+using namespace dasynq;
+using EventLoop_t = EventLoop<NullMutex>;
 
-struct ev_io control_socket_io;
+EventLoop_t eventLoop = EventLoop_t();
+
+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 close_control_socket() 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;
+
+
+class ControlSocketWatcher : public EventLoop_t::FdWatcher
+{
+    Rearm fdEvent(EventLoop_t &loop, int fd, int flags) override
+    {
+        control_socket_cb(&loop, fd);
+        return Rearm::REARM;
+    }
+    
+    public:
+    // TODO the fd is already stored, must we really store it again...
+    int fd;
+    
+    void addWatch(EventLoop_t &loop, int fd, int flags)
+    {
+        this->fd = fd;
+        EventLoop_t::FdWatcher::addWatch(loop, fd, flags);
+    }
+};
+
+ControlSocketWatcher control_socket_io;
 
 
 // Variables
@@ -76,6 +105,7 @@ static ServiceSet *service_set;
 static bool am_system_init = false; // true if we are the system init process
 
 static bool control_socket_open = false;
+static bool external_log_open = false;
 int active_control_conns = 0;
 
 // Control socket path. We maintain a string (control_socket_str) in case we need
@@ -83,6 +113,8 @@ int active_control_conns = 0;
 static const char *control_socket_path = "/dev/dinitctl";
 static std::string control_socket_str;
 
+static const char *log_socket_path = "/dev/log";
+
 static const char *user_home_path = nullptr;
 
 
@@ -103,6 +135,41 @@ const char * get_user_home()
 }
 
 
+namespace {
+    class CallbackSignalHandler : public EventLoop_t::SignalWatcher
+    {
+        public:
+        typedef void (*cb_func_t)(EventLoop_t *);
+        
+        private:
+        cb_func_t cb_func;
+        
+        public:
+        CallbackSignalHandler() : cb_func(nullptr) { }
+        CallbackSignalHandler(cb_func_t pcb_func) :  cb_func(pcb_func) { }
+        
+        void setCbFunc(cb_func_t cb_func)
+        {
+            this->cb_func = cb_func;
+        }
+        
+        Rearm received(EventLoop_t &eloop, int signo, SigInfo_p siginfo) override
+        {
+            service_set->stop_all_services(ShutdownType::REBOOT);
+            return Rearm::REARM;
+        }
+    };
+
+    class ControlSocketWatcher : public EventLoop_t::FdWatcher
+    {
+        Rearm fdEvent(EventLoop_t &loop, int fd, int flags)
+        {
+            control_socket_cb(&loop, fd);
+            return Rearm::REARM;
+        }
+    };
+}
+
 int main(int argc, char **argv)
 {
     using namespace std;
@@ -193,11 +260,12 @@ int main(int argc, char **argv)
     
     /* Set up signal handlers etc */
     /* SIG_CHILD is ignored by default: good */
-    /* sigemptyset(&sigwait_set); */
-    /* sigaddset(&sigwait_set, SIGCHLD); */
-    /* sigaddset(&sigwait_set, SIGINT); */
-    /* sigaddset(&sigwait_set, SIGTERM); */
-    /* sigprocmask(SIG_BLOCK, &sigwait_set, NULL); */
+    sigset_t sigwait_set;
+    sigemptyset(&sigwait_set);
+    sigaddset(&sigwait_set, SIGCHLD);
+    sigaddset(&sigwait_set, SIGINT);
+    sigaddset(&sigwait_set, SIGTERM);
+    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
@@ -234,35 +302,32 @@ int main(int argc, char **argv)
     }
 
     // Set up signal handlers
-    ev_signal sigint_ev_signal;
+    CallbackSignalHandler sigint_watcher;
     if (am_system_init) {
-      ev_signal_init(&sigint_ev_signal, sigint_reboot_cb, SIGINT);
+      sigint_watcher.setCbFunc(sigint_reboot_cb);
     }
     else {
-      ev_signal_init(&sigint_ev_signal, sigterm_cb, SIGINT);
+      sigint_watcher.setCbFunc(sigterm_cb);
     }
     
-    ev_signal sigquit_ev_signal;
+    CallbackSignalHandler sigquit_watcher;
     if (am_system_init) {
         // PID 1: SIGQUIT exec's shutdown
-        ev_signal_init(&sigquit_ev_signal, sigquit_cb, SIGQUIT);
+        sigquit_watcher.setCbFunc(sigquit_cb);
     }
     else {
         // Otherwise: SIGQUIT terminates dinit
-        ev_signal_init(&sigquit_ev_signal, sigterm_cb, SIGQUIT);
+        sigquit_watcher.setCbFunc(sigterm_cb);
     }
     
-    ev_signal sigterm_ev_signal;
-    ev_signal_init(&sigterm_ev_signal, sigterm_cb, SIGTERM);
+    auto sigterm_watcher = CallbackSignalHandler(sigterm_cb);
     
-    /* Set up libev */
-    struct ev_loop *loop = ev_default_loop(EVFLAG_AUTO /* | EVFLAG_SIGNALFD */);
-    ev_signal_start(loop, &sigint_ev_signal);
-    ev_signal_start(loop, &sigquit_ev_signal);
-    ev_signal_start(loop, &sigterm_ev_signal);
+    sigint_watcher.addWatch(eventLoop, SIGINT);
+    sigquit_watcher.addWatch(eventLoop, SIGQUIT);
+    sigterm_watcher.addWatch(eventLoop, SIGTERM);
 
     // Try to open control socket (may fail due to readonly filesystem)
-    open_control_socket(loop);
+    open_control_socket(false);
     
 #ifdef __linux__
     if (am_system_init) {
@@ -275,6 +340,9 @@ int main(int argc, char **argv)
     
     /* start requested services */
     service_set = new ServiceSet(service_dir);
+    
+    init_log(service_set);
+    
     for (list<const char *>::iterator i = services_to_start.begin();
             i != services_to_start.end();
             ++i) {
@@ -296,7 +364,7 @@ int main(int argc, char **argv)
     
     // Process events until all services have terminated.
     while (service_set->count_active_services() != 0) {
-        ev_loop(loop, EVLOOP_ONESHOT);
+        eventLoop.run();
     }
 
     ShutdownType shutdown_type = service_set->getShutdownType();
@@ -318,7 +386,11 @@ int main(int argc, char **argv)
         }
     }
     
-    close_control_socket(ev_default_loop(EVFLAG_AUTO));
+    while (! is_log_flushed()) {
+        eventLoop.run();
+    }
+    
+    close_control_socket();
     
     if (am_system_init) {
         if (shutdown_type == ShutdownType::CONTINUE) {
@@ -354,7 +426,7 @@ int main(int argc, char **argv)
         
         // PID 1 must not actually exit, although we should never reach this point:
         while (true) {
-            ev_loop(loop, EVLOOP_ONESHOT);
+            eventLoop.run();
         }
     }
     
@@ -362,29 +434,27 @@ int main(int argc, char **argv)
 }
 
 // Callback for control socket
-static void control_socket_cb(struct ev_loop *loop, ev_io *w, int revents)
+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.
 
     // Accept a connection
-    int sockfd = w->fd;
-
     int newfd = accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
 
     if (newfd != -1) {
         try {
             new ControlConn(loop, service_set, newfd);  // will delete itself when it's finished
         }
-        catch (std::bad_alloc &bad_alloc_exc) {
-            log(LogLevel::ERROR, "Accepting control connection: Out of memory");
+        catch (std::exception &exc) {
+            log(LogLevel::ERROR, "Accepting control connection: ", exc.what());
             close(newfd);
         }
     }
 }
 
-void open_control_socket(struct ev_loop *loop) noexcept
+void open_control_socket(bool report_ro_failure) noexcept
 {
     if (! control_socket_open) {
         const char * saddrname = control_socket_path;
@@ -415,7 +485,9 @@ void open_control_socket(struct ev_loop *loop) noexcept
         }
 
         if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
-            log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno));
+            if (errno != EROFS || report_ro_failure) {
+                log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno));
+            }
             close(sockfd);
             free(name);
             return;
@@ -437,17 +509,23 @@ void open_control_socket(struct ev_loop *loop) noexcept
             return;
         }
 
-        control_socket_open = true;
-        ev_io_init(&control_socket_io, control_socket_cb, sockfd, EV_READ);
-        ev_io_start(loop, &control_socket_io);
+        try {
+            control_socket_io.addWatch(eventLoop, sockfd, IN_EVENTS);
+            control_socket_open = true;
+        }
+        catch (std::exception &e)
+        {
+            log(LogLevel::ERROR, "Could not setup I/O on control socket: ", e.what());
+            close(sockfd);
+        }
     }
 }
 
-void close_control_socket(struct ev_loop *loop) noexcept
+static void close_control_socket() noexcept
 {
     if (control_socket_open) {
         int fd = control_socket_io.fd;
-        ev_io_stop(loop, &control_socket_io);
+        control_socket_io.deregister(eventLoop);
         close(fd);
         
         // Unlink the socket:
@@ -455,27 +533,70 @@ void close_control_socket(struct ev_loop *loop) noexcept
     }
 }
 
+void setup_external_log() noexcept
+{
+    if (! external_log_open) {
+    
+        const char * saddrname = log_socket_path;
+        uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(saddrname) + 1;
+        
+        struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
+        if (name == nullptr) {
+            log(LogLevel::ERROR, "Connecting to log socket: out of memory");
+            return;
+        }
+        
+        name->sun_family = AF_UNIX;
+        strcpy(name->sun_path, saddrname);
+        
+        int sockfd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+        if (sockfd == -1) {
+            log(LogLevel::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.
+            try {
+                setup_main_log(sockfd);
+            }
+            catch (std::exception &e) {
+                log(LogLevel::ERROR, "Setting up log failed: ", e.what());
+                close(sockfd);
+            }
+        }
+        else {
+            // Note if connect fails, we haven't warned at all, because the syslog server might not
+            // have started yet. TODO, have a special startup flag to indicate when syslog should
+            // be available.
+            close(sockfd);
+        }
+        
+        free(name);
+    }
+}
+
 /* handle SIGINT signal (generated by kernel when ctrl+alt+del pressed) */
-static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents)
+static void sigint_reboot_cb(EventLoop_t *eloop) noexcept
 {
-    log_to_console = true;
     service_set->stop_all_services(ShutdownType::REBOOT);
 }
 
 /* handle SIGQUIT (if we are system init) */
-static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents)
+static void sigquit_cb(EventLoop_t *eloop) noexcept
 {
     // This allows remounting the filesystem read-only if the dinit binary has been
     // unlinked. In that case the kernel holds the binary open, so that it can't be
     // properly removed.
-    close_control_socket(ev_default_loop(EVFLAG_AUTO));
+    close_control_socket();
     execl("/sbin/shutdown", "/sbin/shutdown", (char *) 0);
     log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
 }
 
 /* handle SIGTERM/SIGQUIT - stop all services (not used for system daemon) */
-static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents)
+static void sigterm_cb(EventLoop_t *eloop) noexcept
 {
-    log_to_console = true;
     service_set->stop_all_services();
 }