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
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
Parameters are:
-type = process | scripted | internal
+type = process | bgprocess | scripted | internal
command = ...
logfile = ...
onstart = ...
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;
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()));
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") {
rval->setSmoothRecovery(smooth_recovery);
rval->setOnstartFlags(onstart_flags);
rval->setExtraTerminationSignal(term_signal);
+ rval->set_pid_file(std::move(pid_file));
*iter = rval;
break;
}
/* 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 */
// 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();
}
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
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();
}
}
-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;
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) {
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
void stopped() noexcept;
// Service has successfully started
- void started();
+ void started() noexcept;
// Service failed to start
void failed_to_start();
{
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; }