Adjust dependency auto-break logic.
[oweals/dinit.git] / src / dinit.cc
index 9ae6f8e1cad96c376b86dfd33324a7b23440501b..b7fe727a007ecb7f2bae19fe303d160d9fddc2c9 100644 (file)
@@ -28,6 +28,9 @@
 #include "control.h"
 #include "dinit-log.h"
 #include "dinit-socket.h"
+#include "static-string.h"
+
+#include "mconfig.h"
 
 /*
  * When running as the system init process, Dinit processes the following signals:
@@ -42,6 +45,8 @@
  * services even if the halt/reboot commands are unavailable for some reason.
  */
 
+using namespace cts;
+
 using eventloop_t = dasynq::event_loop<dasynq::null_mutex>;
 
 eventloop_t event_loop;
@@ -68,12 +73,13 @@ int active_control_conns = 0;
 
 // Control socket path. We maintain a string (control_socket_str) in case we need
 // to allocate storage, but control_socket_path is the authoritative value.
-static const char *control_socket_path = "/dev/dinitctl";
+static const char *control_socket_path = SYSCONTROLSOCKET;
 static std::string control_socket_str;
 
 static const char *env_file_path = "/etc/dinit/environment";
 
-static const char *log_socket_path = "/dev/log";
+static const char *log_path = "/dev/log";
+static bool log_is_syslog = true; // if false, log is a file
 
 static const char *user_home_path = nullptr;
 
@@ -164,6 +170,7 @@ int dinit_main(int argc, char **argv)
     
     am_system_init = (getpid() == 1);
     const char * service_dir = nullptr;
+    bool service_dir_dynamic = false; // service_dir dynamically allocated?
     const char * env_file = nullptr;
     string service_dir_str; // to hold storage for above if necessary
     bool control_socket_path_set = false;
@@ -212,6 +219,16 @@ int dinit_main(int argc, char **argv)
                     return 1;
                 }
             }
+            else if (strcmp(argv[i], "--log-file") == 0 || strcmp(argv[i], "-l") == 0) {
+                if (++i < argc) {
+                    log_path = argv[i];
+                    log_is_syslog = false;
+                }
+                else {
+                    cerr << "dinit: '--log-file' (-l) requires an argument" << endl;
+                    return 1;
+                }
+            }
             else if (strcmp(argv[i], "--help") == 0) {
                 cout << "dinit, an init with dependency management" << endl;
                 cout << " --help                       display help" << endl;
@@ -295,16 +312,20 @@ int dinit_main(int argc, char **argv)
     if (service_dir == nullptr && ! am_system_init) {
         const char * userhome = get_user_home();
         if (userhome != nullptr) {
-            service_dir_str = get_user_home();
-            service_dir_str += "/dinit.d";
-            service_dir = service_dir_str.c_str();
+            const char * user_home = get_user_home();
+            size_t user_home_len = strlen(user_home);
+            size_t dinit_d_len = strlen("/dinit.d");
+            size_t full_len = user_home_len + dinit_d_len + 1;
+            char *service_dir_w = new char[full_len];
+            std::memcpy(service_dir_w, user_home, user_home_len);
+            std::memcpy(service_dir_w + user_home_len, "/dinit.d", dinit_d_len);
+            service_dir_w[full_len - 1] = 0;
+
+            service_dir = service_dir_w;
+            service_dir_dynamic = true;
         }
     }
     
-    if (service_dir == nullptr) {
-        service_dir = "/etc/dinit.d";
-    }
-    
     if (services_to_start.empty()) {
         services_to_start.push_back("boot");
     }
@@ -354,14 +375,28 @@ int dinit_main(int argc, char **argv)
     
     log_flush_timer.add_timer(event_loop, dasynq::clock_type::MONOTONIC);
 
+    bool add_all_service_dirs = false;
+    if (service_dir == nullptr) {
+        service_dir = "/etc/dinit.d";
+        add_all_service_dirs = true;
+    }
+
     /* start requested services */
-    services = new dirload_service_set(service_dir);
+    services = new dirload_service_set(service_dir, service_dir_dynamic);
+    if (add_all_service_dirs) {
+        services->add_service_dir("/usr/local/lib/dinit.d", false);
+        services->add_service_dir("/lib/dinit.d", false);
+    }
     
-    init_log(services);
+    init_log(services, log_is_syslog);
     if (am_system_init) {
         log(loglevel_t::INFO, false, "starting system");
     }
     
+    // Only try to set up the external log now if we aren't the system init. (If we are the
+    // system init, wait until the log service starts).
+    if (! am_system_init) setup_external_log();
+
     if (env_file != nullptr) {
         read_env_file(env_file);
     }
@@ -449,8 +484,10 @@ int dinit_main(int argc, char **argv)
         }
         
         // Fork and execute dinit-reboot.
-        execl("/sbin/shutdown", "/sbin/shutdown", "--system", cmd_arg, nullptr);
-        log(loglevel_t::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno));
+        constexpr auto shutdown_exec = literal(SBINDIR) + "/shutdown";
+        execl(shutdown_exec.c_str(), shutdown_exec.c_str(), "--system", cmd_arg, nullptr);
+        log(loglevel_t::ERROR, (literal("Could not execute ") + SBINDIR + "/shutdown: ").c_str(),
+                strerror(errno));
         
         // PID 1 must not actually exit, although we should never reach this point:
         while (true) {
@@ -640,46 +677,66 @@ static void close_control_socket() noexcept
 void setup_external_log() noexcept
 {
     if (! external_log_open) {
-    
-        const char * saddrname = log_socket_path;
-        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_t::ERROR, "Connecting to log socket: out of memory");
-            return;
-        }
-        
-        name->sun_family = AF_UNIX;
-        memcpy(name->sun_path, saddrname, saddrname_len + 1);
-        
-        int sockfd = dinit_socket(AF_UNIX, SOCK_DGRAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
-        if (sockfd == -1) {
-            log(loglevel_t::ERROR, "Error creating log socket: ", strerror(errno));
-            free(name);
-            return;
-        }
-        
-        if (connect(sockfd, (struct sockaddr *) name, sockaddr_size) == 0 || errno == EINPROGRESS) {
-            // 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);
+        if (log_is_syslog) {
+            const char * saddrname = log_path;
+            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_t::ERROR, "Connecting to log socket: out of memory");
+                return;
             }
-            catch (std::exception &e) {
-                log(loglevel_t::ERROR, "Setting up log failed: ", e.what());
+
+            name->sun_family = AF_UNIX;
+            memcpy(name->sun_path, saddrname, saddrname_len + 1);
+
+            int sockfd = dinit_socket(AF_UNIX, SOCK_DGRAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
+            if (sockfd == -1) {
+                log(loglevel_t::ERROR, "Error creating log socket: ", strerror(errno));
+                free(name);
+                return;
+            }
+
+            if (connect(sockfd, (struct sockaddr *) name, sockaddr_size) == 0 || errno == EINPROGRESS) {
+                // 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);
+                    external_log_open = true;
+                }
+                catch (std::exception &e) {
+                    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.
                 close(sockfd);
             }
+
+            free(name);
         }
         else {
-            // Note if connect fails, we haven't warned at all, because the syslog server might not
-            // have started yet.
-            close(sockfd);
+            // log to file:
+            int log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_NONBLOCK | O_CLOEXEC, 0644);
+            if (log_fd >= 0) {
+                try {
+                    setup_main_log(log_fd);
+                    external_log_open = true;
+                }
+                catch (std::exception &e) {
+                    log(loglevel_t::ERROR, "Setting up log failed: ", e.what());
+                    close(log_fd);
+                }
+            }
+            else {
+                // log failure to log? It makes more sense than first appears, because we also log to console:
+                log(loglevel_t::ERROR, "Setting up log failed: ", strerror(errno));
+            }
         }
-        
-        free(name);
     }
 }
 
@@ -694,8 +751,9 @@ 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_t::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
+    constexpr auto shutdown_exec = literal(SBINDIR) + "/shutdown";
+    execl(shutdown_exec.c_str(), shutdown_exec.c_str(), "--system", (char *) 0);
+    log(loglevel_t::ERROR, literal("Error executing ") + SBINDIR + "/sbin/shutdown: ", strerror(errno));
     sync(); // since a hard poweroff might be required at this point...
 }