Update Dasynq to 1.0.2.
[oweals/dinit.git] / src / dinit.cc
index a82f502c69d3f583e4cc75e9cdb91321d31a9384..86e67dbdac2bfe9dac071904ecfc848c3bf7cd36 100644 (file)
 #include <unistd.h>
 #include <fcntl.h>
 #include <pwd.h>
-
-#include "dasync.h"
-#include "service.h"
-#include "control.h"
-#include "dinit-log.h"
-
 #ifdef __linux__
+#include <sys/prctl.h>
 #include <sys/klog.h>
 #include <sys/reboot.h>
 #endif
 
+#include "dasynq.h"
+#include "service.h"
+#include "control.h"
+#include "dinit-log.h"
+#include "dinit-socket.h"
+
 /*
- * "simpleinit" from util-linux-ng package handles signals as follows:
- * SIGTSTP - spawn no more gettys (in preparation for shutdown etc).
- *           In dinit terms this should probably mean "no more auto restarts"
- *           (for any service). (Actually the signal acts as a toggle, if
- *           respawn is disabled it will be re-enabled and init will
- *           act as if SIGHUP had also been sent)
- * SIGTERM - kill spawned gettys (which are still alive)
- *           Interestingly, simpleinit just sends a SIGTERM to the gettys,
- *           which will not normall kill shells (eg bash ignores SIGTERM).
- * "/sbin/initctl -r" - rollback services (ran by "shutdown"/halt etc);
- *           shouldn't return until all services have been stopped.
- *           shutdown calls this after sending SIGTERM to processes running
- *           with uid >= 100 ("mortals").
- * SIGQUIT - init will exec() shutdown. shutdown will detect that it is
- *           running as pid 1 and will just loop and reap child processes.
- *           This is used by shutdown so that init will not hang on to its
- *           inode, allowing the filesystem to be re-mounted readonly
- *           (this is only an issue if the init binary has been unlinked,
- *           since it's then holding an inode which can't be maintained
- *           when the filesystem is unmounted).
+ * When running as the system init process, Dinit processes the following signals:
  *
- * Not sent by shutdown:
- * SIGHUP -  re-read inittab and spawn any new getty entries
- * SIGINT - (ctrl+alt+del handler) - fork & exec "reboot"
- * 
- * On the contrary dinit currently uses:
  * SIGTERM - roll back services and then fork/exec /sbin/halt
  * SIGINT - roll back services and then fork/exec /sbin/reboot
- * SIGQUIT - exec() /sbin/shutdown as per above.
+ * SIGQUIT - exec() /sbin/shutdown without rolling back services
  *
- * It's an open question about whether dinit should roll back services *before*
+ * It's an open question about whether Dinit should roll back services *before*
  * running halt/reboot, since those commands should prompt rollback of services
- * anyway. But it seems safe to do so.
+ * anyway. But it seems safe to do so, and it means the user can at least stop
+ * services even if the halt/reboot commands are unavailable for some reason.
  */
 
+using namespace dasynq;
+using eventloop_t = event_loop<null_mutex>;
 
-using namespace dasync;
-using EventLoop_t = EventLoop<NullMutex>;
+eventloop_t eventLoop;
 
-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 wait_for_user_input() noexcept;
 
-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(EventLoop_t *loop) noexcept;
-static void close_control_socket(EventLoop_t *loop) noexcept;
+static void control_socket_cb(eventloop_t *loop, int fd);
 
-static void control_socket_cb(EventLoop_t *loop, int fd);
-
-class ControlSocketWatcher : public PosixFdWatcher<NullMutex>
-{
-    Rearm gotEvent(EventLoop_t * loop, int fd, int flags)
-    {
-        control_socket_cb(loop, fd);
-        return Rearm::REARM;
-    }
-    
-    public:
-    // TODO the fd is already stored, must we really store it again...
-    int fd;
-    
-    void registerWith(EventLoop_t * loop, int fd, int flags)
-    {
-        this->fd = fd;
-        PosixFdWatcher<NullMutex>::registerWith(loop, fd, flags);
-    }
-};
-
-ControlSocketWatcher control_socket_io;
+void open_control_socket(bool report_ro_failure = true) noexcept;
+void setup_external_log() noexcept;
 
 
 // Variables
 
-static ServiceSet *service_set;
+static dirload_service_set *services;
 
 static bool am_system_init = false; // true if we are the system init process
 
@@ -133,41 +92,44 @@ const char * get_user_home()
 
 
 namespace {
-    class CallbackSignalHandler : public PosixSignalWatcher<NullMutex>
+    class callback_signal_handler : public eventloop_t::signal_watcher_impl<callback_signal_handler>
     {
         public:
-        typedef void (*cb_func_t)(EventLoop_t *);
+        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) { }
+        callback_signal_handler() : cb_func(nullptr) { }
+        callback_signal_handler(cb_func_t pcb_func) :  cb_func(pcb_func) { }
         
         void setCbFunc(cb_func_t cb_func)
         {
             this->cb_func = cb_func;
         }
         
-        Rearm gotSignal(EventLoop_t * eloop, int signo, SigInfo_p siginfo) override
+        rearm received(eventloop_t &eloop, int signo, siginfo_p siginfo)
         {
-            service_set->stop_all_services(ShutdownType::REBOOT);
-            return Rearm::REARM;
+            cb_func(eloop);
+            return rearm::REARM;
         }
     };
 
-    class ControlSocketWatcher : public PosixFdWatcher<NullMutex>
+    class control_socket_watcher : public eventloop_t::fd_watcher_impl<control_socket_watcher>
     {
-        Rearm gotEvent(EventLoop_t * loop, int fd, int flags)
+        public:
+        rearm fd_event(eventloop_t &loop, int fd, int flags) noexcept
         {
-            control_socket_cb(loop, fd);
-            return Rearm::REARM;
+            control_socket_cb(&loop, fd);
+            return rearm::REARM;
         }
     };
+
+    control_socket_watcher control_socket_io;
 }
 
-int main(int argc, char **argv)
+int dinit_main(int argc, char **argv)
 {
     using namespace std;
     
@@ -235,10 +197,12 @@ int main(int argc, char **argv)
             }
         }
         else {
+#ifdef __linux__
             // LILO puts "auto" on the kernel command line for unattended boots; we'll filter it.
             if (! am_system_init || strcmp(argv[i], "auto") != 0) {
                 services_to_start.push_back(argv[i]);
             }
+#endif
         }
       }
     }
@@ -254,7 +218,7 @@ int 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;
@@ -262,8 +226,9 @@ int main(int argc, char **argv)
     sigaddset(&sigwait_set, SIGCHLD);
     sigaddset(&sigwait_set, SIGINT);
     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.
@@ -271,6 +236,8 @@ int main(int argc, char **argv)
     signal(SIGTTIN, SIG_IGN);
     signal(SIGTTOU, SIG_IGN);
     
+    signal(SIGPIPE, SIG_IGN);
+    
     if (! am_system_init && ! control_socket_path_set) {
         const char * userhome = get_user_home();
         if (userhome != nullptr) {
@@ -299,32 +266,29 @@ int main(int argc, char **argv)
     }
 
     // Set up signal handlers
-    CallbackSignalHandler sigint_watcher;
+    callback_signal_handler sigterm_watcher {sigterm_cb};
+    callback_signal_handler sigint_watcher;
+    callback_signal_handler sigquit_watcher;
+
     if (am_system_init) {
-      sigint_watcher.setCbFunc(sigint_reboot_cb);
+        sigint_watcher.setCbFunc(sigint_reboot_cb);
+        sigquit_watcher.setCbFunc(sigquit_cb);
     }
     else {
-      sigint_watcher.setCbFunc(sigterm_cb);
+        sigint_watcher.setCbFunc(sigterm_cb);
     }
+
+    sigint_watcher.add_watch(eventLoop, SIGINT);
+    sigterm_watcher.add_watch(eventLoop, SIGTERM);
     
-    CallbackSignalHandler sigquit_watcher;
     if (am_system_init) {
         // PID 1: SIGQUIT exec's shutdown
-        sigquit_watcher.setCbFunc(sigquit_cb);
+        sigquit_watcher.add_watch(eventLoop, SIGQUIT);
+        // As a user process, we instead just let SIGQUIT perform the default action.
     }
-    else {
-        // Otherwise: SIGQUIT terminates dinit
-        sigquit_watcher.setCbFunc(sigterm_cb);
-    }
-    
-    auto sigterm_watcher = CallbackSignalHandler(sigterm_cb);
-    
-    sigint_watcher.registerWatch(&eventLoop, SIGINT);
-    sigquit_watcher.registerWatch(&eventLoop, SIGQUIT);
-    sigterm_watcher.registerWatch(&eventLoop, SIGTERM);
 
     // Try to open control socket (may fail due to readonly filesystem)
-    open_control_socket(&eventLoop);
+    open_control_socket(false);
     
 #ifdef __linux__
     if (am_system_init) {
@@ -333,53 +297,60 @@ int 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 */
-    service_set = new ServiceSet(service_dir);
+    services = new dirload_service_set(service_dir);
     
-    init_log(service_set);
+    init_log(services);
     
-    for (list<const char *>::iterator i = services_to_start.begin();
-            i != services_to_start.end();
-            ++i) {
+    for (auto svc : services_to_start) {
         try {
-            service_set->startService(*i);
+            services->start_service(svc);
+            // Note in general if we fail to start a service we don't need any special error handling,
+            // since we either leave other services running or, if it was the only service, then no
+            // services will be running and we will process normally (reboot if system process,
+            // exit if user process).
         }
-        catch (ServiceNotFound &snf) {
-            log(LogLevel::ERROR, snf.serviceName, ": Could not find service description.");
+        catch (service_not_found &snf) {
+            log(loglevel_t::ERROR, snf.serviceName, ": Could not find service description.");
         }
-        catch (ServiceLoadExc &sle) {
-            log(LogLevel::ERROR, sle.serviceName, ": ", sle.excDescription);
+        catch (service_load_exc &sle) {
+            log(loglevel_t::ERROR, sle.serviceName, ": ", sle.excDescription);
         }
         catch (std::bad_alloc &badalloce) {
-            log(LogLevel::ERROR, "Out of memory when trying to start service: ", *i, ".");
+            log(loglevel_t::ERROR, "Out of memory when trying to start service: ", svc, ".");
+            break;
         }
     }
     
     event_loop:
     
     // Process events until all services have terminated.
-    while (service_set->count_active_services() != 0) {
+    while (services->count_active_services() != 0) {
         eventLoop.run();
     }
 
-    ShutdownType shutdown_type = service_set->getShutdownType();
+    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 == ShutdownType::REBOOT) {
-            logMsgEnd(" Will reboot.");
+        if (shutdown_type == shutdown_type_t::REBOOT) {
+            log_msg_end(" Will reboot.");
         }
-        else if (shutdown_type == ShutdownType::HALT) {
-            logMsgEnd(" Will halt.");
+        else if (shutdown_type == shutdown_type_t::HALT) {
+            log_msg_end(" Will halt.");
         }
-        else if (shutdown_type == ShutdownType::POWEROFF) {
-            logMsgEnd(" Will power down.");
+        else if (shutdown_type == shutdown_type_t::POWEROFF) {
+            log_msg_end(" Will power down.");
         }
         else {
-            logMsgEnd(" Re-initiating boot sequence.");
+            log_msg_end(" Re-initiating boot sequence.");
         }
     }
     
@@ -387,29 +358,30 @@ int main(int argc, char **argv)
         eventLoop.run();
     }
     
-    close_control_socket(&eventLoop);
+    close_control_socket();
     
     if (am_system_init) {
-        if (shutdown_type == ShutdownType::CONTINUE) {
+        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 {
-                service_set->startService("boot");
+                services->start_service("boot");
                 goto event_loop; // yes, the "evil" goto
             }
             catch (...) {
-                // Now WTF do we do? try to reboot
-                log(LogLevel::ERROR, "Could not start 'boot' service; rebooting.");
-                shutdown_type = ShutdownType::REBOOT;
+                // 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;
             }
         }
         
         const char * cmd_arg;
-        if (shutdown_type == ShutdownType::HALT) {
+        if (shutdown_type == shutdown_type_t::HALT) {
             cmd_arg = "-h";
         }
-        else if (shutdown_type == ShutdownType::REBOOT) {
+        else if (shutdown_type == shutdown_type_t::REBOOT) {
             cmd_arg = "-r";
         }
         else {
@@ -419,47 +391,66 @@ int 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();
         }
     }
+    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);
+        raise(SIGINT);
+        sigprocmask(SIG_UNBLOCK, &sigwait_set, NULL);
+    }
     
     return 0;
 }
 
+// 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
+{
+    std::cout << "Press Enter to continue." << std::endl;
+    char buf[1];
+    read(STDIN_FILENO, buf, 1);
+}
+
 // Callback for control socket
-static void control_socket_cb(EventLoop_t *loop, int sockfd)
+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 = accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
+    int newfd = dinit_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
+            new control_conn_t(loop, services, 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_t::ERROR, "Accepting control connection: ", exc.what());
             close(newfd);
         }
     }
 }
 
-static void open_control_socket(EventLoop_t *loop) noexcept
+void open_control_socket(bool report_ro_failure) noexcept
 {
     if (! control_socket_open) {
         const char * saddrname = control_socket_path;
-        uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(saddrname) + 1;
+        size_t saddrname_len = strlen(saddrname);
+        uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + saddrname_len + 1;
         
         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;
         }
 
@@ -470,19 +461,19 @@ static void open_control_socket(EventLoop_t *loop) noexcept
         }
 
         name->sun_family = AF_UNIX;
-        strcpy(name->sun_path, saddrname);
+        memcpy(name->sun_path, saddrname, saddrname_len + 1);
 
-        // OpenBSD and Linux both allow combining NONBLOCK/CLOEXEC flags with socket type, however
-        // it's not actually POSIX. (TODO).
-        int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+        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) {
-            log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno));
+            if (errno != EROFS || report_ro_failure) {
+                log(loglevel_t::ERROR, "Error binding control socket: ", strerror(errno));
+            }
             close(sockfd);
             free(name);
             return;
@@ -493,27 +484,34 @@ static void open_control_socket(EventLoop_t *loop) 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;
         }
 
-        control_socket_open = true;
-        control_socket_io.registerWith(&eventLoop, sockfd, in_events);
+        try {
+            control_socket_io.add_watch(eventLoop, sockfd, IN_EVENTS);
+            control_socket_open = true;
+        }
+        catch (std::exception &e)
+        {
+            log(loglevel_t::ERROR, "Could not setup I/O on control socket: ", e.what());
+            close(sockfd);
+        }
     }
 }
 
-static void close_control_socket(EventLoop_t *loop) noexcept
+static void close_control_socket() noexcept
 {
     if (control_socket_open) {
-        int fd = control_socket_io.fd;
-        control_socket_io.deregisterWatch(&eventLoop);
+        int fd = control_socket_io.get_watched_fd();
+        control_socket_io.deregister(eventLoop);
         close(fd);
         
         // Unlink the socket:
@@ -521,44 +519,45 @@ static void close_control_socket(EventLoop_t *loop) noexcept
     }
 }
 
-static void setup_external_log() 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;
+        size_t saddrname_len = strlen(saddrname);
+        uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + saddrname_len + 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");
+            log(loglevel_t::ERROR, "Connecting to log socket: out of memory");
             return;
         }
         
         name->sun_family = AF_UNIX;
-        strcpy(name->sun_path, saddrname);
+        memcpy(name->sun_path, saddrname, saddrname_len + 1);
         
-        int sockfd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+        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);
             }
         }
         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.
+            // have started yet.
             close(sockfd);
         }
         
@@ -566,33 +565,24 @@ static void setup_external_log() noexcept
     }
 }
 
-// Called from service when the system is "rw ready" (filesystem mounted r/w etc).
-// May be called more than once.
-void system_rw_ready() noexcept
-{
-    open_control_socket(&eventLoop);
-    setup_external_log();
-}
-
-/* handle SIGINT signal (generated by kernel when ctrl+alt+del pressed) */
-static void sigint_reboot_cb(EventLoop_t *eloop) noexcept
+/* handle SIGINT signal (generated by Linux kernel when ctrl+alt+del pressed) */
+static void sigint_reboot_cb(eventloop_t &eloop) noexcept
 {
-    service_set->stop_all_services(ShutdownType::REBOOT);
+    services->stop_all_services(shutdown_type_t::REBOOT);
 }
 
 /* handle SIGQUIT (if we are system init) */
-static void sigquit_cb(EventLoop_t *eloop) noexcept
+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(eloop);
-    execl("/sbin/shutdown", "/sbin/shutdown", (char *) 0);
-    log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
+    // This performs an immediate shutdown, without service rollback.
+    close_control_socket();
+    execl("/sbin/shutdown", "/sbin/shutdown", "--system", (char *) 0);
+    log(loglevel_t::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
+    sync(); // since a hard poweroff might be required at this point...
 }
 
-/* handle SIGTERM/SIGQUIT - stop all services (not used for system daemon) */
-static void sigterm_cb(EventLoop_t *eloop) noexcept
+/* handle SIGTERM/SIGQUIT(non-system-daemon) - stop all services and shut down */
+static void sigterm_cb(eventloop_t &eloop) noexcept
 {
-    service_set->stop_all_services();
+    services->stop_all_services();
 }