Skip to content

Commit cf201a4

Browse files
committed
Add cleanup:script support, runs at service removal
Signed-off-by: Joachim Wiberg <[email protected]>
1 parent 9245869 commit cf201a4

File tree

3 files changed

+93
-19
lines changed

3 files changed

+93
-19
lines changed

doc/config.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -899,19 +899,20 @@ e.g., `halt:SIGPWR`. To change the delay between the stop signal and
899899
KILL, use the option `kill:<1-60>`, e.g., `kill:10` to wait 10 seconds
900900
before sending `SIGKILL`.
901901

902-
Services, including the `sysv` variant, support pre/post/ready scripts:
902+
Services, including the `sysv` variant, support pre/post/ready and
903+
cleanup scripts:
903904

904-
* `pre:[0-3600,]script`
905-
* `post:[0-3600,]script`
906-
* `ready:[0-3600,]script`
905+
* `pre:[0-3600,]script` -- called before the sysv/service is stated
906+
* `post:[0-3600,]script` -- called after the sysv/service has stopped
907+
* `ready:[0-3600,]script` -- called when the sysv/service is ready
908+
* `cleanup:[0-3600,]script` -- called when run/task/sysv/service is removed
907909

908910
The optional number (0-3600) is the timeout before Finit kills the
909911
script, it defaults to the kill delay value and can be disabled by
910912
setting it to zero. These scripts run as the same `@USER:GROUP` as the
911-
service itself, with any `env:file` sourced. The scripts must use an
912-
absolute path, but are executed from the `$HOME` of the given user. The
913-
scripts are not called with any argument, but get a set of environment
914-
variables:
913+
service itself, with any `env:file` sourced. The scripts are executed
914+
from the `$HOME` of the given user. The scripts are not called with any
915+
argument, but get a set of environment variables:
915916

916917
* `SERVICE_IDENT=foo:1`
917918
* `SERVICE_NAME=foo`
@@ -925,6 +926,11 @@ variables:
925926
the program, if it exited normally, or the signal name (`HUP`,
926927
`TERM`, etc.) if it exited due to signal
927928

929+
When a run/task/sys/service is removed (disable + reload) it is first
930+
stopped and then removed from the runlevel. The `post:script` always
931+
runs when the process has stopped, and the `cleanup:script` runs when
932+
the the stanza has been removed from the runlevel.
933+
928934
> [!IMPORTANT]
929935
> These script actions are intended for setup, cleanup, and readiness
930936
> notification. It is up to the user to ensure the scripts terminate.

src/service.c

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
15241524
char *id = NULL, *env = NULL, *cgroup = NULL;
15251525
char *pre_script = NULL, *post_script = NULL;
15261526
char *ready_script = NULL, *conflict = NULL;
1527+
char *cleanup_script = NULL;
15271528
char ident[MAX_IDENT_LEN];
15281529
char *ifstmt = NULL;
15291530
char *notify = NULL;
@@ -1630,6 +1631,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
16301631
post_script = arg;
16311632
else if (MATCH_CMD(cmd, "ready:", arg))
16321633
ready_script = arg;
1634+
else if (MATCH_CMD(cmd, "cleanup:", arg))
1635+
cleanup_script = arg;
16331636
else if (MATCH_CMD(cmd, "env:", arg))
16341637
env = arg;
16351638
/* 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)
18171820
parse_script(svc, "ready", ready_script, &svc->ready_tmo, svc->ready_script, sizeof(svc->ready_script));
18181821
else
18191822
memset(svc->ready_script, 0, sizeof(svc->ready_script));
1823+
if (cleanup_script)
1824+
parse_script(svc, "cleanup", cleanup_script, &svc->cleanup_tmo, svc->cleanup_script, sizeof(svc->cleanup_script));
1825+
else
1826+
memset(svc->cleanup_script, 0, sizeof(svc->cleanup_script));
1827+
18201828
if (!svc_is_tty(svc)) {
18211829
if (log)
18221830
parse_log(svc, log);
@@ -1916,8 +1924,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
19161924
}
19171925

19181926
/*
1919-
* This function is called when cleaning up lingering (stopped) services
1920-
* after a .conf reload.
1927+
* This function is called at the end of a runlevel change or reload
1928+
* command to delete all remnants of removed services.
19211929
*
19221930
* We need to ensure we properly stop the service before removing it,
19231931
* including stopping any pending restart or SIGKILL timers before we
@@ -1962,10 +1970,14 @@ void service_monitor(pid_t lost, int status)
19621970
/* If the setup phase fails, drive svc to crashed. */
19631971
svc->status = status;
19641972
/* fallthrough */
1973+
case SVC_TEARDOWN_STATE:
19651974
case SVC_CLEANUP_STATE:
19661975
dbg("collected script %s(%d), normal exit: %d, signaled: %d, exit code: %d",
1967-
svc->state == SVC_CLEANUP_STATE ? svc->post_script : svc->pre_script,
1968-
lost, ok, sig, rc);
1976+
(svc->state == SVC_TEARDOWN_STATE
1977+
? svc->post_script
1978+
: (svc->state == SVC_CLEANUP_STATE
1979+
? svc->cleanup_script
1980+
: svc->pre_script)), lost, ok, sig, rc);
19691981
/* Kill all children in the same proess group, e.g. logit */
19701982
dbg("Killing lingering children in same process group ...");
19711983
kill(-svc->pid, SIGKILL);
@@ -2245,6 +2257,41 @@ void service_ready_script(svc_t *svc)
22452257
service_script_add(svc, pid, svc->ready_tmo);
22462258
}
22472259

2260+
static void service_cleanup_script(svc_t *svc)
2261+
{
2262+
svc->pid = service_fork(svc);
2263+
if (svc->pid < 0) {
2264+
err(1, "Failed forking off %s cleanup:script %s", svc_ident(svc, NULL, 0), svc->cleanup_script);
2265+
return;
2266+
}
2267+
2268+
if (svc->pid == 0) {
2269+
char buf[CMD_SIZE];
2270+
char *argv[4] = {
2271+
"sh",
2272+
"-ac",
2273+
buf,
2274+
NULL
2275+
};
2276+
char *env_file;
2277+
2278+
redirect(svc);
2279+
2280+
env_file = svc_getenv(svc);
2281+
if (env_file)
2282+
snprintf(buf, sizeof(buf), ". %s; exec %s", env_file, svc->cleanup_script);
2283+
else
2284+
strlcpy(buf, svc->cleanup_script, sizeof(buf));
2285+
2286+
set_pre_post_envs(svc, "cleanup");
2287+
execvp(_PATH_BSHELL, argv);
2288+
_exit(EX_OSERR);
2289+
}
2290+
2291+
dbg("%s: cleanup:script %s started as PID %d", svc_ident(svc, NULL, 0), svc->cleanup_script, svc->pid);
2292+
service_timeout_after(svc, svc->cleanup_tmo, service_kill_script);
2293+
}
2294+
22482295
static void service_retry(svc_t *svc)
22492296
{
22502297
char *restart_cnt = (char *)&svc->restart_cnt;
@@ -2431,6 +2478,11 @@ int service_step(svc_t *svc)
24312478
condstr(cond_get_agg(svc->cond)));
24322479

24332480
switch (svc->state) {
2481+
case SVC_CLEANUP_STATE:
2482+
if (!svc->pid)
2483+
svc_set_state(svc, SVC_HALTED_STATE);
2484+
break;
2485+
24342486
case SVC_HALTED_STATE:
24352487
if (enabled)
24362488
svc_set_state(svc, SVC_WAITING_STATE);
@@ -2469,7 +2521,7 @@ int service_step(svc_t *svc)
24692521
case SVC_TYPE_SYSV:
24702522
case SVC_TYPE_TTY:
24712523
if (svc_has_post(svc)) {
2472-
svc_set_state(svc, SVC_CLEANUP_STATE);
2524+
svc_set_state(svc, SVC_TEARDOWN_STATE);
24732525
service_post_script(svc);
24742526
} else
24752527
svc_set_state(svc, SVC_HALTED_STATE);
@@ -2489,9 +2541,14 @@ int service_step(svc_t *svc)
24892541
}
24902542
break;
24912543

2492-
case SVC_CLEANUP_STATE:
2493-
if (!svc->pid)
2494-
svc_set_state(svc, SVC_HALTED_STATE);
2544+
case SVC_TEARDOWN_STATE:
2545+
if (!svc->pid) {
2546+
if (svc_is_removed(svc) && svc_has_cleanup(svc)) {
2547+
svc_set_state(svc, SVC_CLEANUP_STATE);
2548+
service_cleanup_script(svc);
2549+
} else
2550+
svc_set_state(svc, SVC_HALTED_STATE);
2551+
}
24952552
break;
24962553

24972554
case SVC_SETUP_STATE:

src/svc.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ typedef enum {
5959
SVC_HALTED_STATE = 0, /* Not allowed in runlevel, or not enabled. */
6060
SVC_DONE_STATE, /* Task/Run job has been run */
6161
SVC_STOPPING_STATE, /* Waiting to collect the child process */
62-
SVC_CLEANUP_STATE, /* Running post: script */
6362
SVC_SETUP_STATE, /* Running pre: script */
63+
SVC_TEARDOWN_STATE, /* Running post: script */
64+
SVC_CLEANUP_STATE, /* Running cleanup: script */
6465
SVC_PAUSED_STATE, /* Condition is in flux, process SIGSTOPed */
6566
SVC_WAITING_STATE, /* Enabled but condition not satisfied */
6667
SVC_STARTING_STATE, /* Conditions OK and pre: script done, start */
@@ -196,13 +197,19 @@ typedef struct svc {
196197
char conflict[MAX_ARG_LEN];
197198
char desc[MAX_STR_LEN];
198199
char env[MAX_CMD_LEN];
200+
199201
char pre_script[MAX_CMD_LEN];
200202
int pre_tmo;
203+
201204
char post_script[MAX_CMD_LEN];
202205
int post_tmo;
206+
203207
char ready_script[MAX_CMD_LEN];
204208
int ready_tmo;
205209

210+
char cleanup_script[MAX_CMD_LEN];
211+
int cleanup_tmo;
212+
206213
/*
207214
* Used to forcefully kill services that won't shutdown on
208215
* 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
270277
static inline int svc_has_pre (svc_t *svc) { return svc->pre_script[0]; }
271278
static inline int svc_has_post (svc_t *svc) { return svc->post_script[0]; }
272279
static inline int svc_has_ready (svc_t *svc) { return svc->ready_script[0];}
280+
static inline int svc_has_cleanup (svc_t *svc) { return svc->cleanup_script[0]; }
273281

274282
static inline void svc_starting (svc_t *svc) { if (svc) svc->starting = 1; }
275283
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)
390398
return "stopping";
391399
}
392400

393-
case SVC_CLEANUP_STATE:
394-
return "cleanup";
401+
case SVC_TEARDOWN_STATE:
402+
return "teardown";
395403

396404
case SVC_SETUP_STATE:
397405
return "setup";
398406

407+
case SVC_CLEANUP_STATE:
408+
return "setup";
409+
399410
case SVC_PAUSED_STATE:
400411
return "paused";
401412

0 commit comments

Comments
 (0)