From cf201a43df05f9007096f67c804c1ab3456a7873 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 17 Feb 2025 05:16:28 +0100 Subject: [PATCH] Add cleanup:script support, runs at service removal Signed-off-by: Joachim Wiberg --- doc/config.md | 22 ++++++++++------ src/service.c | 73 +++++++++++++++++++++++++++++++++++++++++++++------ src/svc.h | 17 +++++++++--- 3 files changed, 93 insertions(+), 19 deletions(-) diff --git a/doc/config.md b/doc/config.md index 8bd18252..6f9787e7 100644 --- a/doc/config.md +++ b/doc/config.md @@ -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. diff --git a/src/service.c b/src/service.c index 9f6f08d3..73f1f6e3 100644 --- a/src/service.c +++ b/src/service.c @@ -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: diff --git a/src/svc.h b/src/svc.h index 275b92f4..99d3d4bf 100644 --- a/src/svc.h +++ b/src/svc.h @@ -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";