command = /bin/sh
restart = false
options = runs-on-console
+chain-to = boot
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
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 {
// 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, ".");
}
}
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));
}
}
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))
{
}
};
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:
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; }
// 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)
{
// 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)
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);
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);
}
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;
}
// 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);
}