diff --git a/doc/config.md b/doc/config.md index f68c9aee..8bd18252 100644 --- a/doc/config.md +++ b/doc/config.md @@ -890,33 +890,45 @@ configurable with the following options: * `oncrash:script` -- similarly, but instead of rebooting, call the `post:script` action if set, see below. -When stopping a service (run/task/sysv/service), either manually or -when moving to another runlevel, Finit starts by sending `SIGTERM`, to -allow the process to shut down gracefully. If the process has not -been collected within 3 seconds, Finit sends `SIGKILL`. To halt the -process using a different signal, use the option `halt:SIGNAL`, e.g., -`halt:SIGPWR`. To change the delay between your halt signal and KILL, -use the option `kill:SEC`, e.g., `kill:10` to wait 10 seconds before -sending `SIGKILL`. +When stopping a service (run/task/sysv/service), either manually or when +moving to another runlevel, Finit starts by sending `SIGTERM`, to allow +the process to shut down gracefully. However, if the process has not +been collected within 3 seconds, Finit will send `SIGKILL`. To stop the +process using a different signal than `SIGTERM`, use `halt:SIGNAL`, +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 support `pre:script` and `post:script` actions as well. These -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 (currently), but both get the `SERVICE_IDENT=foo` environment -variable set. Here `foo` denotes the identity of the service, which if -there are multiple services named `foo`, may be `foo:1`, or any unique -identifier specified in the .conf file. The `post:script` is called -with an additional set of environment variables: +Services, including the `sysv` variant, support pre/post/ready scripts: + + * `pre:[0-3600,]script` + * `post:[0-3600,]script` + * `ready:[0-3600,]script` + +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_IDENT=foo:1` + * `SERVICE_NAME=foo` + * `SERVICE_ID=1` + +The `post:script` is called with an additional set of environment +variables: - `EXIT_CODE=[exited,signal]`: set to one of `exited` or `signal` - `EXIT_STATUS=[num,SIGNAME]`: set to one of exit status code from the program, if it exited normally, or the signal name (`HUP`, `TERM`, etc.) if it exited due to signal -These script actions *must terminate*, so they have a default execution -time of 3 seconds before they are SIGKILLed, this can be adjusted using -the above `kill:SEC` syntax. +> [!IMPORTANT] +> These script actions are intended for setup, cleanup, and readiness +> notification. It is up to the user to ensure the scripts terminate. + #### Conditional Loading diff --git a/src/service.c b/src/service.c index b995ff42..e7524d97 100644 --- a/src/service.c +++ b/src/service.c @@ -100,6 +100,9 @@ static void service_timeout_cb(uev_t *w, void *arg, int events) */ int service_timeout_after(svc_t *svc, int timeout, void (*cb)(svc_t *svc)) { + if (timeout == 0) + return 0; /* OK, not starting timer. */ + if (svc->timer_cb) return -EBUSY; @@ -153,7 +156,7 @@ static void service_script_kill(svc_t *svc) } } -static int service_script_add(svc_t *svc, pid_t pid) +static int service_script_add(svc_t *svc, pid_t pid, int tmo) { struct assoc *ptr = malloc(sizeof(*ptr)); @@ -166,7 +169,7 @@ static int service_script_add(svc_t *svc, pid_t pid) ptr->pid = pid; TAILQ_INSERT_TAIL(&svc_assoc_list, ptr, link); - service_timeout_after(svc, svc->killdelay, service_script_kill); + service_timeout_after(svc, tmo, service_script_kill); return 0; } @@ -1297,12 +1300,35 @@ static void parse_killdelay(svc_t *svc, char *delay) svc->killdelay = (int)(sec * 1000); } -static void parse_script(char *type, char *script, char *buf, size_t len) +/* + * pre:[0-3600,]/path/to/script + */ +static void parse_script(svc_t *svc, char *type, char *script, int *tmo, char *buf, size_t len) { - if (access(script, X_OK)) - logit(LOG_WARNING, "%s:%s is missing or not executable, skipping.", type, script); + char *path; + + path = strchr(script, ','); + if (path) { + const char *errstr; + long long sec; + + *path++ = 0; + + sec = strtonum(script, 0, 3600, &errstr); + if (errstr) { + errx(1, "%s: tmo %s is %s (0-3600)", svc_ident(svc, NULL, 0), script, errstr); + return; + } + *tmo = (int)(sec * 1000); + } else { + path = script; + *tmo = svc->killdelay; + } + + if (access(path, X_OK)) + logit(LOG_WARNING, "%s:%s is missing or not executable, skipping.", type, path); else - strlcpy(buf, script, len); + strlcpy(buf, path, len); } /* @@ -1770,15 +1796,15 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) else svc->killdelay = SVC_TERM_TIMEOUT; if (pre_script) - parse_script("pre", pre_script, svc->pre_script, sizeof(svc->pre_script)); + parse_script(svc, "pre", pre_script, &svc->pre_tmo, svc->pre_script, sizeof(svc->pre_script)); else memset(svc->pre_script, 0, sizeof(svc->pre_script)); if (post_script) - parse_script("post", post_script, svc->post_script, sizeof(svc->post_script)); + parse_script(svc, "post", post_script, &svc->post_tmo, svc->post_script, sizeof(svc->post_script)); else memset(svc->post_script, 0, sizeof(svc->post_script)); if (ready_script) - parse_script("ready", ready_script, svc->ready_script, sizeof(svc->ready_script)); + 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 (!svc_is_tty(svc)) { @@ -2059,6 +2085,9 @@ static void set_pre_post_envs(svc_t *svc, const char *type) char buf[25 + 256] = "/etc/default:/etc/conf.d"; setenv("SERVICE_TYPE", svc_typestr(svc), 1); + setenv("SERVICE_NAME", svc->name, 1); + if (svc->id[0]) + setenv("SERVICE_ID", svc->id, 1); setenv("SERVICE_IDENT", svc_ident(svc, NULL, 0), 1); setenv("SERVICE_SCRIPT_TYPE", type, 1); @@ -2087,6 +2116,8 @@ static void service_pre_script(svc_t *svc) }; char *env_file; + redirect(svc); + env_file = svc_getenv(svc); if (env_file) snprintf(buf, sizeof(buf), ". %s; exec %s", env_file, svc->pre_script); @@ -2101,7 +2132,7 @@ static void service_pre_script(svc_t *svc) dbg("%s: pre:script %s started as PID %d", svc_ident(svc, NULL, 0), svc->pre_script, svc->pid); /* Short hard-coded timeout to prevent locking up Finit */ - service_timeout_after(svc, svc->killdelay, service_kill_script); + service_timeout_after(svc, svc->pre_tmo, service_kill_script); } static void service_post_script(svc_t *svc) @@ -2154,7 +2185,7 @@ static void service_post_script(svc_t *svc) dbg("%s: post:script %s started as PID %d", svc_ident(svc, NULL, 0), svc->post_script, svc->pid); /* Short hard-coded timeout to prevent locking up Finit */ - service_timeout_after(svc, svc->killdelay, service_kill_script); + service_timeout_after(svc, svc->post_tmo, service_kill_script); } void service_ready_script(svc_t *svc) @@ -2194,7 +2225,7 @@ void service_ready_script(svc_t *svc) dbg("%s: ready:script %s started as PID %d", svc_ident(svc, NULL, 0), svc->ready_script, pid); /* Short hard-coded timeout to prevent locking up Finit */ - service_script_add(svc, pid); + service_script_add(svc, pid, svc->ready_tmo); } static void service_retry(svc_t *svc) diff --git a/src/svc.h b/src/svc.h index e2f6bd8d..275b92f4 100644 --- a/src/svc.h +++ b/src/svc.h @@ -197,8 +197,11 @@ typedef struct svc { 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; /* * Used to forcefully kill services that won't shutdown on