From: Davin McCall Date: Sun, 3 Jan 2016 01:37:46 +0000 (+0000) Subject: Add support for "bgprocess" services - daemons which fork and put X-Git-Tag: v0.01~63 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=84b9d1f29213583f0935c310a012aa242927a939;p=oweals%2Fdinit.git Add support for "bgprocess" services - daemons which fork and put themselves in the background (but which ideally write their PID into a file that can be read by Dinit). --- diff --git a/README b/README index 883a2dc..4a50c4f 100644 --- 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 = ... diff --git a/load_service.cc b/load_service.cc index 845f5c2..deac502 100644 --- a/load_service.cc +++ b/load_service.cc @@ -210,6 +210,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name) list> command_offsets; string stop_command; list> stop_command_offsets; + string pid_file; ServiceType service_type = ServiceType::PROCESS; std::list 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; } diff --git a/service-constants.h b/service-constants.h index f54720b..8084066 100644 --- a/service-constants.h +++ b/service-constants.h @@ -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 */ diff --git a/service.cc b/service.cc index c60c228..bce32fa 100644 --- a/service.cc +++ b/service.cc @@ -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) { diff --git a/service.h b/service.h index dca323a..b41d524 100644 --- a/service.h +++ b/service.h @@ -161,6 +161,8 @@ class ServiceRecord string stop_command; /* storage for stop program/script and arguments */ std::vector 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; }