Implement a "chain-to" service property.
authorDavin McCall <davmac@davmac.org>
Mon, 29 Oct 2018 19:02:59 +0000 (19:02 +0000)
committerDavin McCall <davmac@davmac.org>
Tue, 30 Oct 2018 10:03:05 +0000 (10:03 +0000)
This specifies that if a service completes, another service should be
started (specified by name and resolved at the time it is needed, not
beforehand).

This has applications for recovery ("single-user mode") services, and
for multi-stage booting where service descriptions are on a separate
filesystem which is mounted by a service in an initial stage.

doc/linux/services/single
doc/manpages/dinit-service.5
src/control.cc
src/dinit.cc
src/includes/load-service.h
src/includes/service.h
src/load-service.cc
src/service.cc

index 1805ffca35ffca90181e37a872e65cf96e3e37e0..ccb7af5b192cc9d3b5732a676a8b0bc733944332 100644 (file)
@@ -2,3 +2,4 @@ type = process
 command = /bin/sh
 restart = false
 options = runs-on-console
+chain-to = boot
index df7f1b404affa9a1493dbae96c7bc1630eaa3d1f..d50cda99d6b05897e5bd01a5648230fb2de57d02 100644 (file)
@@ -177,6 +177,20 @@ any of the services named within, is not considered fatal.
 The directory path, if not absolute, is relative to the directory containing
 the service description file.
 .TP
+\fBchain-to\fR = \fIservice-name\fR
+When this service completes successfully (i.e. starts and then stops), the
+named service should be started. Note that the named service is not loaded
+until that time; naming an invalid service will not cause this service to
+fail to load.
+
+This can be used for a service that supplies an interactive "recovery mode"
+for another service; once the user exits the recovery shell, the primary
+service (as named via this setting) will then start. It also supports
+multi-stage system startup where later service description files reside on
+a separate filesystem that is mounted during the first stage; such service
+descriptions will not be found at initial start, and so cannot be started
+directly, but can be chained via this directive.
+.TP
 \fBsocket\-listen\fR = \fIsocket-path\fR
 Pre-open a socket for the service and pass it to the service using the
 \fBsystemd\fR activation protocol. This by itself does not give so called
index 4a15f2cb0b6f457e905868396857e36061c3477e..2d9ba398a79f3be9aa7b92fec5f38c761acd1d8d 100644 (file)
@@ -142,7 +142,8 @@ bool control_conn_t::process_find_load(int pktType)
             record = services->load_service(serviceName.c_str());
         }
         catch (service_load_exc &slexc) {
-            log(loglevel_t::ERROR, "Could not load service ", slexc.serviceName, ": ", slexc.excDescription);
+            log(loglevel_t::ERROR, "Could not load service ", slexc.service_name, ": ",
+                    slexc.exc_description);
         }
     }
     else {
index 35740c40c0e654d89413cb814760aef05a774cad..1369d3bf4d5def0c6e7bb2d24a279d2e9bf3aaac 100644 (file)
@@ -417,10 +417,10 @@ int dinit_main(int argc, char **argv)
             // exit if user process).
         }
         catch (service_not_found &snf) {
-            log(loglevel_t::ERROR, snf.serviceName, ": Could not find service description.");
+            log(loglevel_t::ERROR, snf.service_name, ": Could not find service description.");
         }
         catch (service_load_exc &sle) {
-            log(loglevel_t::ERROR, sle.serviceName, ": ", sle.excDescription);
+            log(loglevel_t::ERROR, sle.service_name, ": ", sle.exc_description);
         }
         catch (std::bad_alloc &badalloce) {
             log(loglevel_t::ERROR, "Out of memory when trying to start service: ", svc, ".");
@@ -746,7 +746,8 @@ void setup_external_log() noexcept
                 }
             }
             else {
-                // log failure to log? It makes more sense than first appears, because we also log to console:
+                // 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));
             }
         }
index b27f9cd9ead41ef1fbed9b5f06d9467efea0f09d..21cb4632ec5f84e5a8f8bd3e55a4c01ce618dac2 100644 (file)
@@ -5,12 +5,12 @@
 class service_load_exc
 {
     public:
-    std::string serviceName;
-    std::string excDescription;
+    std::string service_name;
+    std::string exc_description;
 
     protected:
     service_load_exc(const std::string &serviceName, std::string &&desc) noexcept
-        : serviceName(serviceName), excDescription(std::move(desc))
+        : service_name(serviceName), exc_description(std::move(desc))
     {
     }
 };
index e808677d6993416ef76062a7f33074a28d7e30fd..91bbcde2d71079f12b5dfd80f0f929c93898a688 100644 (file)
@@ -286,6 +286,8 @@ class service_record
 
     stopped_reason_t stop_reason = stopped_reason_t::NORMAL;  // reason why stopped
 
+    string start_on_completion;  // service to start when this one completes
+
     // Data for use by service_set
     public:
     
@@ -530,6 +532,12 @@ class service_record
         this->socket_gid = socket_gid;
     }
 
+    // Set the service that this one "chains" to. When this service completes, the named service is started.
+    void set_chain_to(string &&chain_to)
+    {
+        start_on_completion = std::move(chain_to);
+    }
+
     const std::string &get_name() const noexcept { return service_name; }
     service_state_t get_state() const noexcept { return service_state; }
     
@@ -775,7 +783,7 @@ class service_set
     // Load a service description, and dependencies, if there is no existing
     // record for the given name.
     // Throws:
-    //   ServiceLoadException (or subclass) on problem with service description
+    //   service_load_exc (or subclass) on problem with service description
     //   std::bad_alloc on out-of-memory condition
     virtual service_record *load_service(const char *name)
     {
@@ -789,7 +797,7 @@ class service_set
     // Start the service with the given name. The named service will begin
     // transition to the 'started' state.
     //
-    // Throws a ServiceLoadException (or subclass) if the service description
+    // Throws a service_load_exc (or subclass) if the service description
     // cannot be loaded or is invalid;
     // Throws std::bad_alloc if out of memory.
     void start_service(const char *name)
index 1252c750a0a6355b3cae6ef46546b5667a8ae92e..675966ebbee2bbfad3698c7613de166cad081eb7 100644 (file)
@@ -362,6 +362,8 @@ service_record * dirload_service_set::load_service(const char * name)
     uid_t run_as_uid = -1;
     gid_t run_as_gid = -1;
 
+    string chain_to_name;
+
     string line;
     service_file.exceptions(ios::badbit | ios::failbit);
     
@@ -553,6 +555,9 @@ service_record * dirload_service_set::load_service(const char * name)
                 string run_as_str = read_setting_value(i, end, nullptr);
                 run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
             }
+            else if (setting == "chain-to") {
+                chain_to_name = read_setting_value(i, end, nullptr);
+            }
             else {
                 throw service_description_exc(name, "Unknown setting: " + setting);
             }
@@ -623,6 +628,7 @@ service_record * dirload_service_set::load_service(const char * name)
                 rval->set_smooth_recovery(smooth_recovery);
                 rval->set_flags(onstart_flags);
                 rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
+                rval->set_chain_to(std::move(chain_to_name));
                 *iter = rval;
                 break;
             }
index 32ada6e7a466ca55495c641241426638b076acf8..2d94e57389a0c4d62f9d8ba3cdb19739ae4cfaaf 100644 (file)
@@ -108,6 +108,22 @@ void service_record::stopped() noexcept
     // Start failure will have been logged already, only log if we are stopped for other reasons:
     if (! start_failed) {
         log_service_stopped(service_name);
+
+        // If this service chains to another, start the other service now:
+        if (! will_restart && ! start_on_completion.empty()) {
+            try {
+                auto chain_to = services->load_service(start_on_completion.c_str());
+                chain_to->start();
+            }
+            catch (service_load_exc &sle) {
+                log(loglevel_t::ERROR, "Couldn't chain to service ", start_on_completion, ": ",
+                        "couldn't load ", sle.service_name, ": ", sle.exc_description);
+            }
+            catch (std::bad_alloc &bae) {
+                log(loglevel_t::ERROR, "Couldn't chain to service ", start_on_completion,
+                        ": Out of memory");
+            }
+        }
     }
     notify_listeners(service_event_t::STOPPED);
 }