Add support for "bgprocess" services - daemons which fork and put
authorDavin McCall <davmac@davmac.org>
Sun, 3 Jan 2016 01:37:46 +0000 (01:37 +0000)
committerDavin McCall <davmac@davmac.org>
Sun, 3 Jan 2016 01:42:06 +0000 (01:42 +0000)
themselves in the background (but which ideally write their PID
into a file that can be read by Dinit).

README
load_service.cc
service-constants.h
service.cc
service.h

diff --git a/README b/README
index 883a2dcf3eb65c5c03011f6323c4971ad4d50472..4a50c4f44a6f521970a4733014d574b8cb70d71c 100644 (file)
--- a/README
+++ b/README
@@ -35,7 +35,8 @@ Introduction to services
 A "service" is nominally a persistent process or system state. The two main
 types of service are a _process_ service (represented by a an actual process)
 and a _scripted_ service (which is started and stopped by running a process -
-often a shell script - to completion).
+often a shell script - to completion). There are also _bgprocess_ services
+and _internal_services.
 
 Many programs that you might want to run under dinit's supervision can run
 either "in the foreground" or as a daemon ("in the background"), and the
@@ -54,6 +55,21 @@ one from a service listed as dependent) which tries to contact it will not
 be able to do so. In practice, this is not usually a problem (and external
 solutions, like D-Bus, do exist).
 
+A _scripted_ service has separate commands for startup and (optional)
+shutdown. Scripted services can be used for tasks such as mounting file
+systems that don't need a persisten process, and in some cases can be used
+for daemon processes (although Dinit will not be able to supervise a
+process that is registered as a scripted service).
+
+A _bgprocess_ service is a mix between a process service and a scripted
+service. A command is used to start the service, and once started, the
+process ID is expected to be available in a file which Dinit can then
+read. Many existing daemons can operate in this way. Dinit can only
+supervise the process if it runs as the system "init" (PID 1),
+
+An _internal_ service is just a placeholder service that can be used to
+describe a set of dependencies. An internal service has no corresponding
+process.
 
 
 Service Description files
@@ -80,7 +96,7 @@ Parameter values are interpreted literally, except that:
 
 Parameters are:
 
-type = process | scripted | internal
+type = process | bgprocess | scripted | internal
 command = ...
 logfile = ...
 onstart = ...
index 845f5c243d4ec62631a89f5d7060d99027e87e2e..deac5027fa7ccab27cb3dd6a12a612dcd32c847c 100644 (file)
@@ -210,6 +210,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
     list<pair<unsigned,unsigned>> command_offsets;
     string stop_command;
     list<pair<unsigned,unsigned>> stop_command_offsets;
+    string pid_file;
 
     ServiceType service_type = ServiceType::PROCESS;
     std::list<ServiceRecord *> depends_on;
@@ -262,6 +263,9 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
                 else if (setting == "stop-command") {
                     stop_command = read_setting_value(i, end, &stop_command_offsets);
                 }
+                else if (setting == "pid-file") {
+                    pid_file = read_setting_value(i, end);
+                }
                 else if (setting == "depends-on") {
                     string dependency_name = read_setting_value(i, end);
                     depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
@@ -289,12 +293,15 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
                     else if (type_str == "process") {
                         service_type = ServiceType::PROCESS;
                     }
+                    else if (type_str == "bgprocess") {
+                        service_type = ServiceType::BGPROCESS;
+                    }
                     else if (type_str == "internal") {
                         service_type = ServiceType::INTERNAL;
                     }
                     else {
-                        throw ServiceDescriptionExc(name, "Service type must be \"scripted\""
-                            " or \"process\" or \"internal\"");
+                        throw ServiceDescriptionExc(name, "Service type must be one of: \"scripted\","
+                            " \"process\", \"bgprocess\" or \"internal\"");
                     }
                 }
                 else if (setting == "onstart") {
@@ -350,6 +357,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
                 rval->setSmoothRecovery(smooth_recovery);
                 rval->setOnstartFlags(onstart_flags);
                 rval->setExtraTerminationSignal(term_signal);
+                rval->set_pid_file(std::move(pid_file));
                 *iter = rval;
                 break;
             }
index f54720ba4a88214fa70df2dad2bf005995b76b71..808406603aa05625df99c8cc91523f52585928ae 100644 (file)
@@ -11,12 +11,14 @@ enum class ServiceState {
 
 /* Service types */
 enum class ServiceType {
-    DUMMY,      // dummy service, used to detect cyclice dependencies
-    PROCESS,    // service runs as a process, and can be stopped by
-                // sending the process a signal (SIGTERM)
-    SCRIPTED,   // service requires an external command to start,
+    DUMMY,      // Dummy service, used to detect cyclice dependencies
+    PROCESS,    // Service runs as a process, and can be stopped by
+                // sending the process a signal (usually SIGTERM)
+    BGPROCESS,  // Service runs as a process which "daemonizes" to run in the
+                // "background".
+    SCRIPTED,   // Service requires an external command to start,
                 // and a second command to stop
-    INTERNAL    // internal service, runs no external process
+    INTERNAL    // Internal service, runs no external process
 };
 
 /* Service events */
index c60c22862e5b6f7efbb27f8f828627f1c13c643e..bce32faf08736b9ca6309bb80aea6a0917f4e0a3 100644 (file)
@@ -56,7 +56,7 @@ void ServiceSet::stopService(const std::string & name) noexcept
 // Called when a service has actually stopped.
 void ServiceRecord::stopped() noexcept
 {
-    if (service_type != ServiceType::SCRIPTED && onstart_flags.runs_on_console) {
+    if (service_type != ServiceType::SCRIPTED && service_type != ServiceType::BGPROCESS && onstart_flags.runs_on_console) {
         tcsetpgrp(0, getpgrp());
         releaseConsole();
     }
@@ -103,12 +103,22 @@ void ServiceRecord::process_child_callback(struct ev_loop *loop, ev_child *w, in
 
 void ServiceRecord::handle_exit_status() noexcept
 {
-    if (service_type == ServiceType::PROCESS) {
-        // TODO log non-zero rstatus?
-        if (service_state == ServiceState::STOPPING) {
+    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS) {
+        if (service_state == ServiceState::STARTING) {
+            // (only applies to BGPROCESS)
+            if (exit_status == 0) {
+                started();
+            }
+            else {
+                log(LogLevel::ERROR, "service ", service_name, " failed to start with exit code", exit_status);
+                failed_to_start();
+            }
+        }
+        else if (service_state == ServiceState::STOPPING) {
+            // TODO log non-zero rstatus?
             stopped();
         }
-        else if (smooth_recovery) {
+        else if (smooth_recovery && service_state == ServiceState::STARTED) {
             // TODO ensure a minimum time between restarts
             // TODO if we are pinned-started then we should probably check
             //      that dependencies have started before trying to re-start the
@@ -293,7 +303,8 @@ void ServiceRecord::allDepsStarted(bool has_console) noexcept
 
     if (has_console) log_to_console = false;
 
-    if (service_type == ServiceType::PROCESS || service_type == ServiceType::SCRIPTED) {
+    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS
+            || service_type == ServiceType::SCRIPTED) {
         bool start_success = start_ps_process();
         if (! start_success) {
             failed_to_start();
@@ -320,12 +331,37 @@ void ServiceRecord::acquiredConsole() noexcept
     }
 }
 
-void ServiceRecord::started()
+void ServiceRecord::started() noexcept
 {
-    if (onstart_flags.runs_on_console && service_type == ServiceType::SCRIPTED) {
+    if (onstart_flags.runs_on_console && (service_type == ServiceType::SCRIPTED || service_type == ServiceType::BGPROCESS)) {
         tcsetpgrp(0, getpgrp());
         releaseConsole();
     }
+    
+    if (service_type == ServiceType::BGPROCESS && pid_file.length() != 0) {
+        const char *pid_file_c = pid_file.c_str();
+        int fd = open(pid_file_c, O_CLOEXEC);
+        if (fd != -1) {
+            char pidbuf[10];
+            int r = read(fd, pidbuf, 9);
+            if (r > 0) {
+                pidbuf[r] = 0; // store nul terminator
+                pid = std::atoi(pidbuf);
+                if (kill(pid, 0) == 0) {
+                    ev_child_init(&child_listener, process_child_callback, pid, 0);
+                    child_listener.data = this;
+                    ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
+                }
+                else {
+                    pid = -1;
+                }
+            }
+            close(fd);
+        }
+        else {
+            log(LogLevel::ERROR, service_name, ": read pid file: ", strerror(errno));
+        }
+    }
 
     logServiceStarted(service_name);
     service_state = ServiceState::STARTED;
@@ -603,7 +639,7 @@ bool ServiceRecord::stopDependents() noexcept
 void ServiceRecord::allDepsStopped()
 {
     waiting_for_deps = false;
-    if (service_type == ServiceType::PROCESS) {
+    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS) {
         if (pid != -1) {
             // The process is still kicking on - must actually kill it.
             if (! onstart_flags.no_sigterm) {
index dca323a8165a20dc57081f78046afa2c69799f63..b41d524493cc812c6fc8465287332479f4933c07 100644 (file)
--- a/service.h
+++ b/service.h
@@ -161,6 +161,8 @@ class ServiceRecord
     string stop_command;          /* storage for stop program/script and arguments */
     std::vector<const char *> stop_arg_parts; /* pointer to each argument/part of the stop_command */
     
+    string pid_file;
+    
     OnstartFlags onstart_flags;
 
     string logfile;           // log file name, empty string specifies /dev/null
@@ -221,7 +223,7 @@ class ServiceRecord
     void stopped() noexcept;
     
     // Service has successfully started
-    void started();
+    void started() noexcept;
     
     // Service failed to start
     void failed_to_start();
@@ -375,6 +377,11 @@ class ServiceRecord
     {
         this->term_signal = signo;
     }
+    
+    void set_pid_file(string &&pid_file) noexcept
+    {
+        this->pid_file = pid_file;
+    }
 
     const char *getServiceName() const noexcept { return service_name.c_str(); }
     ServiceState getState() const noexcept { return service_state; }