Add cleanup:script support, runs at service removal

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-02-17 05:16:28 +01:00
parent 9245869e18
commit cf201a43df
3 changed files with 93 additions and 19 deletions
+14 -8
View File
@@ -899,19 +899,20 @@ e.g., `halt:SIGPWR`. To change the delay between the stop signal and
KILL, use the option `kill:<1-60>`, e.g., `kill:10` to wait 10 seconds
before sending `SIGKILL`.
Services, including the `sysv` variant, support pre/post/ready scripts:
Services, including the `sysv` variant, support pre/post/ready and
cleanup scripts:
* `pre:[0-3600,]script`
* `post:[0-3600,]script`
* `ready:[0-3600,]script`
* `pre:[0-3600,]script` -- called before the sysv/service is stated
* `post:[0-3600,]script` -- called after the sysv/service has stopped
* `ready:[0-3600,]script` -- called when the sysv/service is ready
* `cleanup:[0-3600,]script` -- called when run/task/sysv/service is removed
The optional number (0-3600) is the timeout before Finit kills the
script, it defaults to the kill delay value and can be disabled by
setting it to zero. These scripts run as the same `@USER:GROUP` as the
service itself, with any `env:file` sourced. The scripts must use an
absolute path, but are executed from the `$HOME` of the given user. The
scripts are not called with any argument, but get a set of environment
variables:
service itself, with any `env:file` sourced. The scripts are executed
from the `$HOME` of the given user. The scripts are not called with any
argument, but get a set of environment variables:
* `SERVICE_IDENT=foo:1`
* `SERVICE_NAME=foo`
@@ -925,6 +926,11 @@ variables:
the program, if it exited normally, or the signal name (`HUP`,
`TERM`, etc.) if it exited due to signal
When a run/task/sys/service is removed (disable + reload) it is first
stopped and then removed from the runlevel. The `post:script` always
runs when the process has stopped, and the `cleanup:script` runs when
the the stanza has been removed from the runlevel.
> [!IMPORTANT]
> These script actions are intended for setup, cleanup, and readiness
> notification. It is up to the user to ensure the scripts terminate.
+65 -8
View File
@@ -1524,6 +1524,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
char *id = NULL, *env = NULL, *cgroup = NULL;
char *pre_script = NULL, *post_script = NULL;
char *ready_script = NULL, *conflict = NULL;
char *cleanup_script = NULL;
char ident[MAX_IDENT_LEN];
char *ifstmt = NULL;
char *notify = NULL;
@@ -1630,6 +1631,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
post_script = arg;
else if (MATCH_CMD(cmd, "ready:", arg))
ready_script = arg;
else if (MATCH_CMD(cmd, "cleanup:", arg))
cleanup_script = arg;
else if (MATCH_CMD(cmd, "env:", arg))
env = arg;
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
@@ -1817,6 +1820,11 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
parse_script(svc, "ready", ready_script, &svc->ready_tmo, svc->ready_script, sizeof(svc->ready_script));
else
memset(svc->ready_script, 0, sizeof(svc->ready_script));
if (cleanup_script)
parse_script(svc, "cleanup", cleanup_script, &svc->cleanup_tmo, svc->cleanup_script, sizeof(svc->cleanup_script));
else
memset(svc->cleanup_script, 0, sizeof(svc->cleanup_script));
if (!svc_is_tty(svc)) {
if (log)
parse_log(svc, log);
@@ -1916,8 +1924,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
}
/*
* This function is called when cleaning up lingering (stopped) services
* after a .conf reload.
* This function is called at the end of a runlevel change or reload
* command to delete all remnants of removed services.
*
* We need to ensure we properly stop the service before removing it,
* including stopping any pending restart or SIGKILL timers before we
@@ -1962,10 +1970,14 @@ void service_monitor(pid_t lost, int status)
/* If the setup phase fails, drive svc to crashed. */
svc->status = status;
/* fallthrough */
case SVC_TEARDOWN_STATE:
case SVC_CLEANUP_STATE:
dbg("collected script %s(%d), normal exit: %d, signaled: %d, exit code: %d",
svc->state == SVC_CLEANUP_STATE ? svc->post_script : svc->pre_script,
lost, ok, sig, rc);
(svc->state == SVC_TEARDOWN_STATE
? svc->post_script
: (svc->state == SVC_CLEANUP_STATE
? svc->cleanup_script
: svc->pre_script)), lost, ok, sig, rc);
/* Kill all children in the same proess group, e.g. logit */
dbg("Killing lingering children in same process group ...");
kill(-svc->pid, SIGKILL);
@@ -2245,6 +2257,41 @@ void service_ready_script(svc_t *svc)
service_script_add(svc, pid, svc->ready_tmo);
}
static void service_cleanup_script(svc_t *svc)
{
svc->pid = service_fork(svc);
if (svc->pid < 0) {
err(1, "Failed forking off %s cleanup:script %s", svc_ident(svc, NULL, 0), svc->cleanup_script);
return;
}
if (svc->pid == 0) {
char buf[CMD_SIZE];
char *argv[4] = {
"sh",
"-ac",
buf,
NULL
};
char *env_file;
redirect(svc);
env_file = svc_getenv(svc);
if (env_file)
snprintf(buf, sizeof(buf), ". %s; exec %s", env_file, svc->cleanup_script);
else
strlcpy(buf, svc->cleanup_script, sizeof(buf));
set_pre_post_envs(svc, "cleanup");
execvp(_PATH_BSHELL, argv);
_exit(EX_OSERR);
}
dbg("%s: cleanup:script %s started as PID %d", svc_ident(svc, NULL, 0), svc->cleanup_script, svc->pid);
service_timeout_after(svc, svc->cleanup_tmo, service_kill_script);
}
static void service_retry(svc_t *svc)
{
char *restart_cnt = (char *)&svc->restart_cnt;
@@ -2431,6 +2478,11 @@ restart:
condstr(cond_get_agg(svc->cond)));
switch (svc->state) {
case SVC_CLEANUP_STATE:
if (!svc->pid)
svc_set_state(svc, SVC_HALTED_STATE);
break;
case SVC_HALTED_STATE:
if (enabled)
svc_set_state(svc, SVC_WAITING_STATE);
@@ -2469,7 +2521,7 @@ restart:
case SVC_TYPE_SYSV:
case SVC_TYPE_TTY:
if (svc_has_post(svc)) {
svc_set_state(svc, SVC_CLEANUP_STATE);
svc_set_state(svc, SVC_TEARDOWN_STATE);
service_post_script(svc);
} else
svc_set_state(svc, SVC_HALTED_STATE);
@@ -2489,9 +2541,14 @@ restart:
}
break;
case SVC_CLEANUP_STATE:
if (!svc->pid)
svc_set_state(svc, SVC_HALTED_STATE);
case SVC_TEARDOWN_STATE:
if (!svc->pid) {
if (svc_is_removed(svc) && svc_has_cleanup(svc)) {
svc_set_state(svc, SVC_CLEANUP_STATE);
service_cleanup_script(svc);
} else
svc_set_state(svc, SVC_HALTED_STATE);
}
break;
case SVC_SETUP_STATE:
+14 -3
View File
@@ -59,8 +59,9 @@ typedef enum {
SVC_HALTED_STATE = 0, /* Not allowed in runlevel, or not enabled. */
SVC_DONE_STATE, /* Task/Run job has been run */
SVC_STOPPING_STATE, /* Waiting to collect the child process */
SVC_CLEANUP_STATE, /* Running post: script */
SVC_SETUP_STATE, /* Running pre: script */
SVC_TEARDOWN_STATE, /* Running post: script */
SVC_CLEANUP_STATE, /* Running cleanup: script */
SVC_PAUSED_STATE, /* Condition is in flux, process SIGSTOPed */
SVC_WAITING_STATE, /* Enabled but condition not satisfied */
SVC_STARTING_STATE, /* Conditions OK and pre: script done, start */
@@ -196,13 +197,19 @@ typedef struct svc {
char conflict[MAX_ARG_LEN];
char desc[MAX_STR_LEN];
char env[MAX_CMD_LEN];
char pre_script[MAX_CMD_LEN];
int pre_tmo;
char post_script[MAX_CMD_LEN];
int post_tmo;
char ready_script[MAX_CMD_LEN];
int ready_tmo;
char cleanup_script[MAX_CMD_LEN];
int cleanup_tmo;
/*
* Used to forcefully kill services that won't shutdown on
* termination and to delay restarts of crashing services.
@@ -270,6 +277,7 @@ static inline int svc_has_pidfile (svc_t *svc) { return svc_is_daemon(svc) && s
static inline int svc_has_pre (svc_t *svc) { return svc->pre_script[0]; }
static inline int svc_has_post (svc_t *svc) { return svc->post_script[0]; }
static inline int svc_has_ready (svc_t *svc) { return svc->ready_script[0];}
static inline int svc_has_cleanup (svc_t *svc) { return svc->cleanup_script[0]; }
static inline void svc_starting (svc_t *svc) { if (svc) svc->starting = 1; }
static inline void svc_started (svc_t *svc) { if (svc) svc->starting = 0; }
@@ -390,12 +398,15 @@ static inline char *svc_status(svc_t *svc)
return "stopping";
}
case SVC_CLEANUP_STATE:
return "cleanup";
case SVC_TEARDOWN_STATE:
return "teardown";
case SVC_SETUP_STATE:
return "setup";
case SVC_CLEANUP_STATE:
return "setup";
case SVC_PAUSED_STATE:
return "paused";