From: Davin McCall Date: Mon, 29 Oct 2018 19:02:59 +0000 (+0000) Subject: Implement a "chain-to" service property. X-Git-Tag: v0.5.0~39 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=f1c1672893755c13640eced55d28f07901e3396d;p=oweals%2Fdinit.git Implement a "chain-to" service property. 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. --- diff --git a/doc/linux/services/single b/doc/linux/services/single index 1805ffc..ccb7af5 100644 --- a/doc/linux/services/single +++ b/doc/linux/services/single @@ -2,3 +2,4 @@ type = process command = /bin/sh restart = false options = runs-on-console +chain-to = boot diff --git a/doc/manpages/dinit-service.5 b/doc/manpages/dinit-service.5 index df7f1b4..d50cda9 100644 --- a/doc/manpages/dinit-service.5 +++ b/doc/manpages/dinit-service.5 @@ -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 diff --git a/src/control.cc b/src/control.cc index 4a15f2c..2d9ba39 100644 --- a/src/control.cc +++ b/src/control.cc @@ -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 { diff --git a/src/dinit.cc b/src/dinit.cc index 35740c4..1369d3b 100644 --- a/src/dinit.cc +++ b/src/dinit.cc @@ -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)); } } diff --git a/src/includes/load-service.h b/src/includes/load-service.h index b27f9cd..21cb463 100644 --- a/src/includes/load-service.h +++ b/src/includes/load-service.h @@ -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)) { } }; diff --git a/src/includes/service.h b/src/includes/service.h index e808677..91bbcde 100644 --- a/src/includes/service.h +++ b/src/includes/service.h @@ -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) diff --git a/src/load-service.cc b/src/load-service.cc index 1252c75..675966e 100644 --- a/src/load-service.cc +++ b/src/load-service.cc @@ -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; } diff --git a/src/service.cc b/src/service.cc index 32ada6e..2d94e57 100644 --- a/src/service.cc +++ b/src/service.cc @@ -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); }