mirror of
https://github.com/troglobit/finit.git
synced 2026-10-24 11:54:36 +00:00
Dynamically manage service states based on user defined conditions
Old event system has been replaced with a more generic condition concept. The idea is that finit plugins may provide arbitrary conditions that services may specify as dependencies that they require to run. In order to accomodate this, the service management has been redesigned to use a state machine.
This commit is contained in:
1 parent
7a3408c173
commit
2232aea0f7
17 files changed
+879
-736
No files matched your search
@@ -31,7 +31,7 @@
|
||||
|
||||
#include "finit.h"
|
||||
#include "conf.h"
|
||||
#include "event.h"
|
||||
#include "cond.h"
|
||||
#include "helpers.h"
|
||||
#include "private.h"
|
||||
#include "sig.h"
|
||||
@@ -41,15 +41,12 @@
|
||||
|
||||
#define RESPAWN_MAX 10 /* Prevent endless respawn of faulty services. */
|
||||
|
||||
static int dyn_stop_cnt = 0;
|
||||
static int in_teardown = 0, in_dyn_teardown = 0;
|
||||
|
||||
static int is_norespawn (void);
|
||||
static void restart_lost_procs (void);
|
||||
static void svc_dance (svc_t *svc);
|
||||
#ifndef INETD_DISABLED
|
||||
static svc_t *find_inetd_svc (char *path, char *service, char *proto);
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* service_bootstrap - Start bootstrap services and tasks
|
||||
*
|
||||
@@ -58,114 +55,69 @@ static svc_t *find_inetd_svc (char *path, char *service, char *proto);
|
||||
*/
|
||||
void service_bootstrap(void)
|
||||
{
|
||||
svc_t *svc;
|
||||
|
||||
_d("Bootstrapping all services in runlevel S from %s", FINIT_CONF);
|
||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
||||
svc_cmd_t cmd;
|
||||
|
||||
/* Inetd services cannot be part of bootstrap currently. */
|
||||
if (svc_is_inetd(svc))
|
||||
continue;
|
||||
|
||||
cmd = service_enabled(svc, 0, NULL);
|
||||
if (SVC_START == cmd || (SVC_RELOAD == cmd))
|
||||
service_start(svc);
|
||||
}
|
||||
service_step_all(SVC_TYPE_RUN | SVC_TYPE_TASK | SVC_TYPE_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* service_enabled - Should the service run?
|
||||
* @svc: Pointer to &svc_t object
|
||||
* @event: Dynamic event, opaque flag passed to callback
|
||||
* @arg: Event argument, used only by external service plugins.
|
||||
*
|
||||
* This method calls an associated service callback, if registered by a
|
||||
* plugin, and returns the &svc_cmd_t status. If no plugin is registered
|
||||
* the service is statically enabled in /etc/finit.conf and the result
|
||||
* will always be %SVC_START.
|
||||
* @svc: Pointer to &svc_t object
|
||||
*
|
||||
* Returns:
|
||||
* Either one of %SVC_START, %SVC_STOP, %SVC_RELOAD.
|
||||
* 1, if the service is allowed to run in the current runlevel and the
|
||||
* user has not manually requested that this service should not run. 0
|
||||
* otherwise.
|
||||
*/
|
||||
svc_cmd_t service_enabled(svc_t *svc, int event, void *arg)
|
||||
int service_enabled(svc_t *svc)
|
||||
{
|
||||
svc_cmd_t cmd = SVC_START; /* Default to start, since listed in finit.conf */
|
||||
if (!svc ||
|
||||
!svc_in_runlevel(svc, runlevel) ||
|
||||
svc_is_removed(svc) ||
|
||||
svc->block != SVC_BLOCK_NONE)
|
||||
return 0;
|
||||
|
||||
if (!svc) {
|
||||
errno = EINVAL;
|
||||
return SVC_STOP;
|
||||
}
|
||||
|
||||
if (!svc_in_runlevel(svc, runlevel))
|
||||
return SVC_STOP;
|
||||
|
||||
/*
|
||||
* Event conditions for services are ignored during bootstrap.
|
||||
*/
|
||||
_d("Checking %s runlevel %d and events %s", svc->cmd, runlevel, svc->events);
|
||||
if (runlevel && !event_service_cond(svc->events))
|
||||
return SVC_STOP;
|
||||
|
||||
if (svc->state == SVC_RELOAD_STATE)
|
||||
cmd = SVC_RELOAD;
|
||||
if (svc->state == SVC_PAUSED_STATE)
|
||||
cmd = SVC_STOP;
|
||||
|
||||
/* Is there a service plugin registered? */
|
||||
if (svc->cb) {
|
||||
int status;
|
||||
pid_t pid;
|
||||
|
||||
/* Let callback run in separate process so it doesn't crash PID 1 */
|
||||
pid = fork();
|
||||
if (-1 == pid) {
|
||||
_pe("Failed in %s callback", svc->cmd);
|
||||
return SVC_STOP;
|
||||
}
|
||||
|
||||
if (!pid)
|
||||
_exit(svc->cb(svc, event, arg));
|
||||
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
_pe("Failed reading status from %s callback", svc->cmd);
|
||||
return SVC_STOP;
|
||||
}
|
||||
|
||||
/* Callback normally exits here. */
|
||||
if (WIFEXITED(status)) {
|
||||
svc_cmd_t tmp = WEXITSTATUS(status);
|
||||
return tmp == SVC_START ? cmd : tmp;
|
||||
}
|
||||
|
||||
/* Check for SEGFAULT or other error ... */
|
||||
if (WIFSIGNALED(status) && WCOREDUMP(status))
|
||||
_e("Callback to %s crashed!\n", svc->cmd);
|
||||
else
|
||||
_e("Callback to %s did not exit normally!\n", svc->cmd);
|
||||
|
||||
return SVC_STOP;
|
||||
}
|
||||
|
||||
_d("%s => %s", svc->cmd, (cmd == SVC_START
|
||||
? "SVC_START"
|
||||
: (cmd == SVC_RELOAD
|
||||
? "SVC_RELOAD"
|
||||
: "SVC_STOP")));
|
||||
|
||||
return cmd;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Remember: service_enabled() must be called before calling service_start() */
|
||||
int service_start(svc_t *svc)
|
||||
/**
|
||||
* service_stop_is_done - Have all stopped services been collected?
|
||||
*
|
||||
* Returns:
|
||||
* 1, if all stopped services have been collected. 0 otherwise.
|
||||
*/
|
||||
static int service_stop_is_done(void)
|
||||
{
|
||||
int respawn, sd = 0;
|
||||
svc_t *svc;
|
||||
|
||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0))
|
||||
if (svc->state == SVC_STOPPING_STATE)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_norespawn(void)
|
||||
{
|
||||
return sig_stopped() ||
|
||||
fexist("/mnt/norespawn") ||
|
||||
fexist("/tmp/norespawn");
|
||||
}
|
||||
|
||||
/**
|
||||
* service_start - Start service
|
||||
* @svc: Service to start
|
||||
*
|
||||
* Returns:
|
||||
* 0 if the service was successfully started. Non-zero otherwise.
|
||||
*/
|
||||
static int service_start(svc_t *svc)
|
||||
{
|
||||
int sd = 0;
|
||||
pid_t pid;
|
||||
sigset_t nmask, omask;
|
||||
|
||||
if (!svc)
|
||||
return 1;
|
||||
respawn = svc->pid != 0;
|
||||
|
||||
/* Don't try and start service if it doesn't exist. */
|
||||
if (!fexist(svc->cmd) && !svc->inetd.cmd) {
|
||||
@@ -177,12 +129,13 @@ int service_start(svc_t *svc)
|
||||
print_result(1);
|
||||
}
|
||||
|
||||
svc->block = SVC_BLOCK_MISSING;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Ignore if finit is SIGSTOP'ed */
|
||||
if (is_norespawn())
|
||||
return 0;
|
||||
return 1;
|
||||
|
||||
#ifndef INETD_DISABLED
|
||||
if (svc_is_inetd(svc)) {
|
||||
@@ -221,9 +174,9 @@ int service_start(svc_t *svc)
|
||||
#endif
|
||||
if (verbose) {
|
||||
if (svc_is_daemon(svc))
|
||||
print_desc("", svc->desc);
|
||||
else if (!respawn)
|
||||
print_desc("Starting ", svc->desc);
|
||||
else
|
||||
print_desc("", svc->desc);
|
||||
}
|
||||
|
||||
/* Block sigchild while forking. */
|
||||
@@ -291,7 +244,7 @@ int service_start(svc_t *svc)
|
||||
if (strlen(arg) < (sizeof(buf) - strlen(buf)))
|
||||
strcat(buf, arg);
|
||||
}
|
||||
_e("%starting %s: %s", respawn ? "Res" : "S", svc->cmd, buf);
|
||||
_e("Starting %s: %s", svc->cmd, buf);
|
||||
}
|
||||
|
||||
sig_unblock();
|
||||
@@ -312,20 +265,15 @@ int service_start(svc_t *svc)
|
||||
exit(status);
|
||||
}
|
||||
svc->pid = pid;
|
||||
svc->state = SVC_RUNNING_STATE;
|
||||
|
||||
if (svc_is_inetd(svc)) {
|
||||
if (svc->inetd.type == SOCK_STREAM)
|
||||
close(sd);
|
||||
} else {
|
||||
int result;
|
||||
int result = 0;
|
||||
|
||||
if (SVC_TYPE_RUN == svc->type)
|
||||
result = WEXITSTATUS(complete(svc->cmd, pid));
|
||||
else if (!respawn)
|
||||
result = svc->pid > 1 ? 0 : 1;
|
||||
else
|
||||
result = 0;
|
||||
|
||||
if (verbose)
|
||||
print_result(result);
|
||||
@@ -334,7 +282,14 @@ int service_start(svc_t *svc)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int service_stop(svc_t *svc, int state)
|
||||
/**
|
||||
* service_stop - Stop service
|
||||
* @svc: Service to stop
|
||||
*
|
||||
* Returns:
|
||||
* 0 if the service was successfully stopped. Non-zero otherwise.
|
||||
*/
|
||||
static int service_stop(svc_t *svc)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
@@ -343,17 +298,12 @@ int service_stop(svc_t *svc, int state)
|
||||
|
||||
if (svc->pid <= 1) {
|
||||
_d("Bad PID %d for %s, SIGTERM", svc->pid, svc->desc);
|
||||
res = 1;
|
||||
goto exit;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (SVC_TYPE_SERVICE != svc->type)
|
||||
return 0;
|
||||
|
||||
_d("Service %s state %d, new state %d, sighup %d", svc->cmd, svc->state, state, svc->sighup);
|
||||
if (state == SVC_RELOAD_STATE && svc->sighup)
|
||||
goto exit;
|
||||
|
||||
if (runlevel != 1 && verbose)
|
||||
print_desc("Stopping ", svc->desc);
|
||||
|
||||
@@ -362,114 +312,12 @@ int service_stop(svc_t *svc, int state)
|
||||
|
||||
if (runlevel != 1 && verbose)
|
||||
print_result(res);
|
||||
exit:
|
||||
if (!res)
|
||||
svc->state = state;
|
||||
svc->restart_counter = 0;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_start_dynamic - Start new or reload modified dynamic services
|
||||
*/
|
||||
void service_start_dynamic(void)
|
||||
{
|
||||
svc_t *svc;
|
||||
|
||||
_d("Starting enabled/added services ...");
|
||||
for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0)) {
|
||||
if (svc_is_updated(svc))
|
||||
svc_dance(svc);
|
||||
}
|
||||
|
||||
/* Cleanup stale services */
|
||||
svc_clean_dynamic(service_unregister);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unless there are services we must collect first (wait for
|
||||
* them to stop), we can call HOOK_SVC_RECONF here.
|
||||
*/
|
||||
static int service_stop_done(svc_t *svc)
|
||||
{
|
||||
if (svc && !svc_is_changed(svc))
|
||||
return 0;
|
||||
|
||||
if (svc && dyn_stop_cnt)
|
||||
dyn_stop_cnt--;
|
||||
|
||||
_d("dyn_stop_cnt %d", dyn_stop_cnt);
|
||||
if (!dyn_stop_cnt) {
|
||||
_d("All disabled/removed services have been stoppped, calling reconf hooks ...");
|
||||
plugin_run_hooks(HOOK_SVC_RECONF); /* Reconfigure HW/VLANs/etc here */
|
||||
|
||||
/* Finish off by starting/reload modified/new services */
|
||||
service_start_dynamic();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_stop_dynamic - Stop disabled/removed dynamic services
|
||||
*
|
||||
* We call it "stop", but in reality it could be "skip" as well, if the
|
||||
* service supports SIGHUP. This function is just one step on the road
|
||||
* to reload all modified services.
|
||||
*/
|
||||
void service_stop_dynamic(void)
|
||||
{
|
||||
svc_t *svc;
|
||||
|
||||
_d("Stopping disabled/removed services ...");
|
||||
for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0)) {
|
||||
if (svc_is_changed(svc) && svc->pid) {
|
||||
svc_state_t new_state = SVC_RELOAD_STATE;
|
||||
|
||||
if (svc_is_removed(svc))
|
||||
new_state = SVC_HALTED_STATE;
|
||||
|
||||
if (!svc_has_sighup(svc))
|
||||
dyn_stop_cnt++;
|
||||
|
||||
_d("Marking service %s as state %d", svc->cmd, new_state);
|
||||
service_stop(svc, new_state);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we need to collect any services before calling user HOOK */
|
||||
service_stop_done(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* service_restart - Restart or SIGHUP a process
|
||||
*
|
||||
* Unfortunately we cannot call service_start() here because we must
|
||||
* wait for the svc to actually be collected by the service_monitor()
|
||||
* first. Currently this means that an initctl user will not get
|
||||
* correct feedback when restarting a service. (Prompt may return
|
||||
* before we've actually started upp the service again.)
|
||||
*
|
||||
* XXX: This should be refactored to let initctl check the sighup
|
||||
* flag and then to stop+start or reload.
|
||||
*/
|
||||
int service_restart(svc_t *svc)
|
||||
{
|
||||
if (!svc)
|
||||
return 1;
|
||||
|
||||
/* Ignore if finit is SIGSTOP'ed */
|
||||
if (is_norespawn())
|
||||
return 0;
|
||||
|
||||
svc->restart_counter = 0;
|
||||
|
||||
return service_stop(svc, SVC_RESTART_STATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* service_reload - Send SIGHUP to a service
|
||||
* service_restart - Restart a service by sending %SIGHUP
|
||||
* @svc: Service to reload
|
||||
*
|
||||
* This function does some basic checks of the runtime state of Finit
|
||||
@@ -478,13 +326,15 @@ int service_restart(svc_t *svc)
|
||||
* Returns:
|
||||
* POSIX OK(0) or non-zero on error.
|
||||
*/
|
||||
int service_reload(svc_t *svc)
|
||||
static int service_restart(svc_t *svc)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* Ignore if finit is SIGSTOP'ed */
|
||||
if (is_norespawn())
|
||||
return 0;
|
||||
return 1;
|
||||
|
||||
if (!svc)
|
||||
if (!svc || !svc->sighup)
|
||||
return 1;
|
||||
|
||||
if (svc->pid <= 1) {
|
||||
@@ -492,10 +342,40 @@ int service_reload(svc_t *svc)
|
||||
svc->pid = 0;
|
||||
return 1;
|
||||
}
|
||||
svc->state = SVC_RUNNING_STATE;
|
||||
|
||||
if (verbose)
|
||||
print_desc("Restarting ", svc->desc);
|
||||
|
||||
_d("Sending SIGHUP to PID %d", svc->pid);
|
||||
return kill(svc->pid, SIGHUP);
|
||||
err = kill(svc->pid, SIGHUP);
|
||||
|
||||
if (verbose)
|
||||
print_result(err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* service_reload_dynamic_finish - Finish dynamic service reload
|
||||
*
|
||||
* Second stage of dynamic reload. Called either directly from first
|
||||
* stage if no services had to be stopped, or later from
|
||||
* service_monitor once all stopped services have been collected.
|
||||
*/
|
||||
static void service_reload_dynamic_finish(void)
|
||||
{
|
||||
in_dyn_teardown = 0;
|
||||
|
||||
/* Cleanup stale services */
|
||||
svc_clean_dynamic(service_unregister);
|
||||
|
||||
_d("Starting services after reconf ...");
|
||||
service_step_all(SVC_TYPE_SERVICE);
|
||||
|
||||
_d("Calling reconf hooks ...");
|
||||
plugin_run_hooks(HOOK_SVC_RECONF);
|
||||
|
||||
service_step_all(SVC_TYPE_SERVICE);
|
||||
_d("Reconfiguration done");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -510,78 +390,38 @@ void service_reload_dynamic(void)
|
||||
/* First reload all *.conf in /etc/finit.d/ */
|
||||
conf_reload_dynamic();
|
||||
|
||||
/* Then stop any disabled/removed services and non-reloadable */
|
||||
service_stop_dynamic();
|
||||
/* Then, mark all affected service conditions as in-flux and
|
||||
* let all affected services move to WAITING/HALTED */
|
||||
_d("Stopping services services not allowed after reconf ...");
|
||||
in_dyn_teardown = 1;
|
||||
cond_reload();
|
||||
service_step_all(SVC_TYPE_SERVICE);
|
||||
|
||||
/*
|
||||
* Finish off by starting/reloading modified/new services.
|
||||
* Postponed to service_stop_done() to make sure all services
|
||||
* are guaranteed to have been stopped before being started
|
||||
* again.
|
||||
*/
|
||||
/* Need to wait for any services to stop? If so, exit early
|
||||
* and perform second stage from service_monitor later. */
|
||||
if (!service_stop_is_done())
|
||||
return;
|
||||
|
||||
/* Otherwise, kick all svcs again right away */
|
||||
service_reload_dynamic_finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* service_runlevel - Change to a new runlevel
|
||||
* @newlevel: New runlevel to activate
|
||||
* service_runlevel_finish - Finish runlevel change
|
||||
*
|
||||
* Stops all services not in @newlevel and starts, or lets continue to run,
|
||||
* those in @newlevel. Also updates @prevlevel and active @runlevel.
|
||||
* Second stage of runlevel change. Called either directly from first
|
||||
* stage if no services had to be stopped, or later from
|
||||
* service_monitor once all stopped services have been collected.
|
||||
*/
|
||||
void service_runlevel(int newlevel)
|
||||
static void service_runlevel_finish(void)
|
||||
{
|
||||
svc_t *svc;
|
||||
|
||||
if (runlevel == newlevel)
|
||||
return;
|
||||
|
||||
if (newlevel < 0 || newlevel > 9)
|
||||
return;
|
||||
|
||||
prevlevel = runlevel;
|
||||
runlevel = newlevel;
|
||||
|
||||
_d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel);
|
||||
runlevel_set(prevlevel, newlevel);
|
||||
|
||||
/* Make sure to (re)load all *.conf in /etc/finit.d/ */
|
||||
conf_reload_dynamic();
|
||||
|
||||
_d("Stopping services services not allowed in new runlevel ...");
|
||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
||||
if (!svc_in_runlevel(svc, runlevel)) {
|
||||
#ifndef INETD_DISABLED
|
||||
if (svc_is_inetd(svc))
|
||||
inetd_stop(&svc->inetd);
|
||||
else
|
||||
#endif
|
||||
service_stop(svc, SVC_HALTED_STATE);
|
||||
}
|
||||
|
||||
/* ... or disabled/removed services from /etc/finit.d/ */
|
||||
if (svc_is_dynamic(svc) && svc_is_changed(svc))
|
||||
service_stop(svc, SVC_HALTED_STATE);
|
||||
}
|
||||
|
||||
/* Prev runlevel services stopped, call hooks before starting new runlevel ... */
|
||||
_d("All services have been stoppped, calling runlevel change hooks ...");
|
||||
plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */
|
||||
|
||||
_d("Starting services services new to this runlevel ...");
|
||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
||||
#ifndef INETD_DISABLED
|
||||
/* Inetd services have slightly different semantics */
|
||||
if (svc_is_inetd(svc)) {
|
||||
if (svc_in_runlevel(svc, runlevel))
|
||||
inetd_start(&svc->inetd);
|
||||
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* All other services consult their callback here */
|
||||
svc_dance(svc);
|
||||
}
|
||||
in_teardown = 0;
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
/* Cleanup stale services */
|
||||
svc_clean_dynamic(service_unregister);
|
||||
@@ -605,6 +445,42 @@ void service_runlevel(int newlevel)
|
||||
tty_runlevel(runlevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* service_runlevel - Change to a new runlevel
|
||||
* @newlevel: New runlevel to activate
|
||||
*
|
||||
* Stops all services not in @newlevel and starts, or lets continue to run,
|
||||
* those in @newlevel. Also updates @prevlevel and active @runlevel.
|
||||
*/
|
||||
void service_runlevel(int newlevel)
|
||||
{
|
||||
if (runlevel == newlevel)
|
||||
return;
|
||||
|
||||
if (newlevel < 0 || newlevel > 9)
|
||||
return;
|
||||
|
||||
prevlevel = runlevel;
|
||||
runlevel = newlevel;
|
||||
|
||||
_d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel);
|
||||
runlevel_set(prevlevel, newlevel);
|
||||
|
||||
/* Make sure to (re)load all *.conf in /etc/finit.d/ */
|
||||
conf_reload_dynamic();
|
||||
|
||||
_d("Stopping services services not allowed in new runlevel ...");
|
||||
in_teardown = 1;
|
||||
service_step_all(SVC_TYPE_ANY);
|
||||
|
||||
/* Need to wait for any services to stop? If so, exit early
|
||||
* and perform second stage from service_monitor later. */
|
||||
if (!service_stop_is_done())
|
||||
return;
|
||||
|
||||
service_runlevel_finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* service_register - Register service, task or run commands
|
||||
* @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2)
|
||||
@@ -666,7 +542,7 @@ int service_register(int type, char *line, time_t mtime, char *username)
|
||||
int forking = 0;
|
||||
#endif
|
||||
char *service = NULL, *proto = NULL, *ifaces = NULL;
|
||||
char *cmd, *desc, *runlevels = NULL, *events = NULL;
|
||||
char *cmd, *desc, *runlevels = NULL, *cond = NULL;
|
||||
svc_t *svc;
|
||||
plugin_t *plugin = NULL;
|
||||
|
||||
@@ -691,8 +567,8 @@ int service_register(int type, char *line, time_t mtime, char *username)
|
||||
username = &cmd[1];
|
||||
else if (cmd[0] == '[') /* [runlevels] */
|
||||
runlevels = &cmd[0];
|
||||
else if (cmd[0] == '<') /* [!ev] */
|
||||
events = &cmd[1];
|
||||
else if (cmd[0] == '<') /* <[!][ev][,ev..]> */
|
||||
cond = &cmd[1];
|
||||
else if (cmd[0] == ':') /* :ID */
|
||||
id = atoi(&cmd[1]);
|
||||
#ifndef INETD_DISABLED
|
||||
@@ -800,7 +676,7 @@ int service_register(int type, char *line, time_t mtime, char *username)
|
||||
_d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels);
|
||||
|
||||
if (type == SVC_TYPE_SERVICE)
|
||||
conf_parse_events(svc, events);
|
||||
conf_parse_cond(svc, cond);
|
||||
|
||||
#ifndef INETD_DISABLED
|
||||
if (svc_is_inetd(svc)) {
|
||||
@@ -841,26 +717,36 @@ void service_unregister(svc_t *svc)
|
||||
svc_del(svc);
|
||||
}
|
||||
|
||||
/**
|
||||
* service_teardown_finish - Complete runlevel change or dynamic reload
|
||||
*
|
||||
* If any runlevel change or dynamic service reload is in progress and
|
||||
* all services that had to be stopped have been collected, run the
|
||||
* corresponding second stage.
|
||||
*/
|
||||
static void service_teardown_finish(void)
|
||||
{
|
||||
if (!(in_teardown || in_dyn_teardown))
|
||||
return;
|
||||
|
||||
if (!service_stop_is_done())
|
||||
return;
|
||||
|
||||
if (in_teardown)
|
||||
service_runlevel_finish();
|
||||
|
||||
if (in_dyn_teardown)
|
||||
service_reload_dynamic_finish();
|
||||
}
|
||||
|
||||
|
||||
void service_monitor(pid_t lost)
|
||||
{
|
||||
svc_t *svc;
|
||||
static int was_stopped = 0;
|
||||
|
||||
if (was_stopped && !is_norespawn()) {
|
||||
was_stopped = 0;
|
||||
restart_lost_procs();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fexist(SYNC_SHUTDOWN) || lost <= 1)
|
||||
return;
|
||||
|
||||
/* Power user at the console, don't respawn tasks. */
|
||||
if (is_norespawn()) {
|
||||
was_stopped = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (tty_respawn(lost))
|
||||
return;
|
||||
|
||||
@@ -869,86 +755,192 @@ void service_monitor(pid_t lost)
|
||||
return;
|
||||
#endif
|
||||
|
||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
||||
if (lost != svc->pid)
|
||||
continue;
|
||||
svc = svc_find_by_pid(lost);
|
||||
if (!svc) {
|
||||
_d("collected unknown PID %d", lost);
|
||||
FLOG_WARN("collected unknown PID %d", lost);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prevlevel && svc_clean_bootstrap(svc))
|
||||
continue;
|
||||
if (!prevlevel && svc_clean_bootstrap(svc))
|
||||
return;
|
||||
|
||||
if (SVC_TYPE_SERVICE != svc->type) {
|
||||
svc->pid = 0;
|
||||
continue;
|
||||
}
|
||||
_d("collected %s(%d)", svc->cmd, lost);
|
||||
|
||||
_d("Ouch, lost pid %d - %s(%d)", lost, basename(svc->cmd), svc->pid);
|
||||
/* No longer running, update books. */
|
||||
svc->pid = 0;
|
||||
service_step(svc);
|
||||
|
||||
/* No longer running, update books. */
|
||||
svc->pid = 0;
|
||||
/* Check if we're still collecting stopped dynamic services */
|
||||
service_teardown_finish();
|
||||
}
|
||||
|
||||
/* Check if we're still collecting stopped dynamic services */
|
||||
if (service_stop_done(svc))
|
||||
break;
|
||||
void service_step(svc_t *svc)
|
||||
{
|
||||
/* These fields are marked as const in svc_t, only this
|
||||
* function is allowed to modify them */
|
||||
svc_state_t *state = (svc_state_t *)&svc->state;
|
||||
int *restart_counter = (int *)&svc->restart_counter;
|
||||
|
||||
if (sig_stopped()) {
|
||||
_e("Stopped, not respawning killed processes.");
|
||||
break;
|
||||
}
|
||||
svc_cmd_t enabled;
|
||||
svc_state_t old_state;
|
||||
cond_state_t cond;
|
||||
char *old_status = NULL;
|
||||
int err;
|
||||
|
||||
/* Restarting lost service. */
|
||||
if (service_enabled(svc, 0, NULL)) {
|
||||
if (svc->restart_counter > RESPAWN_MAX) {
|
||||
_e("Not restarting %s id %d, respawn MAX (%d) reached!",
|
||||
svc->cmd, svc->id, RESPAWN_MAX);
|
||||
restart:
|
||||
old_state = *state;
|
||||
enabled = service_enabled(svc);
|
||||
|
||||
if (debug)
|
||||
old_status = strdup(svc_status(svc));
|
||||
|
||||
switch(*state) {
|
||||
case SVC_HALTED_STATE:
|
||||
*restart_counter = 0;
|
||||
if (enabled)
|
||||
*state = SVC_READY_STATE;
|
||||
break;
|
||||
|
||||
case SVC_DONE_STATE:
|
||||
if (svc_is_changed(svc))
|
||||
*state = SVC_HALTED_STATE;
|
||||
break;
|
||||
|
||||
case SVC_STOPPING_STATE:
|
||||
if (!svc->pid)
|
||||
*state = SVC_HALTED_STATE;
|
||||
break;
|
||||
|
||||
case SVC_READY_STATE:
|
||||
if (!enabled) {
|
||||
*state = SVC_HALTED_STATE;
|
||||
} else if (cond_get_agg(svc->cond) == COND_ON) {
|
||||
if (*restart_counter >= RESPAWN_MAX) {
|
||||
_e("%s keeps crashing, not restarting",
|
||||
svc->desc ? : svc->cmd);
|
||||
svc->block = SVC_BLOCK_CRASHING;
|
||||
*state = SVC_HALTED_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
svc->restart_counter++;
|
||||
service_start(svc);
|
||||
err = service_start(svc);
|
||||
if (err || !svc->pid) {
|
||||
(*restart_counter)++;
|
||||
break;
|
||||
}
|
||||
|
||||
svc->dirty = 0;
|
||||
|
||||
switch (svc->type) {
|
||||
case SVC_TYPE_SERVICE:
|
||||
*state = SVC_RUNNING_STATE;
|
||||
break;
|
||||
case SVC_TYPE_INETD:
|
||||
case SVC_TYPE_TASK:
|
||||
*state = SVC_STOPPING_STATE;
|
||||
break;
|
||||
case SVC_TYPE_RUN:
|
||||
*state = SVC_DONE_STATE;
|
||||
break;
|
||||
default:
|
||||
_e("unknown service type %d", svc->type);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SVC_RUNNING_STATE:
|
||||
if (!enabled) {
|
||||
service_stop(svc);
|
||||
*state = SVC_STOPPING_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!svc->pid) {
|
||||
(*restart_counter)++;
|
||||
*state = SVC_READY_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
cond = cond_get_agg(svc->cond);
|
||||
|
||||
if (cond == COND_OFF ||
|
||||
(!svc->sighup && (cond < COND_ON || svc_is_changed(svc)))) {
|
||||
service_stop(svc);
|
||||
*state = SVC_READY_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cond == COND_FLUX) {
|
||||
kill(svc->pid, SIGSTOP);
|
||||
*state = SVC_WAITING_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (svc_is_changed(svc)) {
|
||||
if (svc->sighup) {
|
||||
service_restart(svc);
|
||||
} else {
|
||||
service_stop(svc);
|
||||
*state = SVC_READY_STATE;
|
||||
}
|
||||
svc->dirty = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SVC_WAITING_STATE:
|
||||
if (!enabled) {
|
||||
kill(svc->pid, SIGCONT);
|
||||
service_stop(svc);
|
||||
*state = SVC_HALTED_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!svc->pid) {
|
||||
(*restart_counter)++;
|
||||
*state = SVC_READY_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
cond = cond_get_agg(svc->cond);
|
||||
switch (cond) {
|
||||
case COND_ON:
|
||||
kill(svc->pid, SIGCONT);
|
||||
*state = SVC_RUNNING_STATE;
|
||||
break;
|
||||
|
||||
case COND_OFF:
|
||||
kill(svc->pid, SIGCONT);
|
||||
service_stop(svc);
|
||||
*state = SVC_READY_STATE;
|
||||
break;
|
||||
|
||||
case COND_FLUX:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (*state != old_state) {
|
||||
if (debug) {
|
||||
_d("%-20.20s %s -> %s", svc->cmd,
|
||||
old_status, svc_status(svc));
|
||||
free(old_status);
|
||||
}
|
||||
goto restart;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_norespawn(void)
|
||||
{
|
||||
return sig_stopped() ||
|
||||
fexist("/mnt/norespawn") ||
|
||||
fexist("/tmp/norespawn");
|
||||
}
|
||||
|
||||
static void restart_lost_procs(void)
|
||||
void service_step_all(int types)
|
||||
{
|
||||
svc_t *svc;
|
||||
|
||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
||||
if (svc->pid > 0 && pid_alive(svc->pid))
|
||||
if (!(svc->type & types))
|
||||
continue;
|
||||
|
||||
/* Only restart lost daemons, not task/run/inetd services */
|
||||
if (SVC_TYPE_SERVICE != svc->type) {
|
||||
svc->pid = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
service_start(svc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Singing and dancing ... */
|
||||
static void svc_dance(svc_t *svc)
|
||||
{
|
||||
svc_cmd_t cmd = service_enabled(svc, 0, NULL);
|
||||
|
||||
if (svc->pid) {
|
||||
if (SVC_STOP == cmd)
|
||||
service_stop(svc, SVC_HALTED_STATE);
|
||||
else if (SVC_RELOAD == cmd)
|
||||
service_reload(svc);
|
||||
} else {
|
||||
if (SVC_START == cmd || SVC_RELOAD == cmd)
|
||||
service_start(svc);
|
||||
service_step(svc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user