Fix #129: add pre:script and post:script action support

This patch updates the service state machine with two new states: SETUP
and CLEANUP.  If an executable pre:/path/to/script is defined for a
service, it is called every time the task goes to READY state.  If an
executable post:/path/to/script is defined for a service, it is called
when the task goes to HALTED state.

Each of these two scripts default to a three (3) second execution time
before they are SIGKILLed.  This can be adjusted with the `kill:SEC`
option for the service.  There are no execution guarantees, nor are
there any way of detecting if the script was killed before completion or
not -- except for running Finit in debug mode and inspecting the result
printed by system_monitor().

Note: the post:script MUST be idempotent since transitions between READY
      and HALTED can take place any number of times before a task goes
      to its RUNNING state.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2021-04-18 11:33:38 +02:00
parent 9fd860ef3c
commit ba858743e5
3 changed files with 192 additions and 46 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 138 KiB

+180 -46
View File
@@ -376,6 +376,55 @@ static char *group_name(svc_t *svc, char *buf, size_t len)
return buf;
}
static pid_t service_fork(svc_t *svc)
{
pid_t pid;
pid = fork();
if (pid == 0) {
char *home = NULL;
#ifdef ENABLE_STATIC
int uid = 0; /* XXX: Fix better warning that dropprivs is disabled. */
int gid = 0;
#else
int uid = getuser(svc->username, &home);
int gid = getgroup(svc->group);
#endif
sched_yield();
/* Set configured limits */
for (int i = 0; i < RLIMIT_NLIMITS; i++) {
if (setrlimit(i, &svc->rlimit[i]) == -1)
logit(LOG_WARNING,
"%s: rlimit: Failed setting %s",
svc->cmd, rlim2str(i));
}
/* Set desired user+group */
if (gid >= 0)
(void)setgid(gid);
if (uid >= 0) {
(void)setuid(uid);
/* Set default path for regular users */
if (uid > 0)
setenv("PATH", _PATH_DEFPATH, 1);
if (home) {
setenv("HOME", home, 1);
if (chdir(home))
(void)chdir("/");
}
}
/* Source any environment from env:/path/to/file */
source_env(svc);
}
return pid;
}
/**
* service_start - Start service
* @svc: Service to start
@@ -443,48 +492,10 @@ static int service_start(svc_t *svc)
sigaddset(&nmask, SIGCHLD);
sigprocmask(SIG_BLOCK, &nmask, &omask);
pid = fork();
pid = service_fork(svc);
if (pid == 0) {
int status;
char *home = NULL;
#ifdef ENABLE_STATIC
int uid = 0; /* XXX: Fix better warning that dropprivs is disabled. */
int gid = 0;
#else
int uid = getuser(svc->username, &home);
int gid = getgroup(svc->group);
#endif
char *args[MAX_NUM_SVC_ARGS + 1];
sched_yield();
/* Set configured limits */
for (int i = 0; i < RLIMIT_NLIMITS; i++) {
if (setrlimit(i, &svc->rlimit[i]) == -1)
logit(LOG_WARNING,
"%s: rlimit: Failed setting %s",
svc->cmd, rlim2str(i));
}
/* Set desired user+group */
if (gid >= 0)
(void)setgid(gid);
if (uid >= 0) {
(void)setuid(uid);
/* Set default path for regular users */
if (uid > 0)
setenv("PATH", _PATH_DEFPATH, 1);
if (home) {
setenv("HOME", home, 1);
if (chdir(home))
(void)chdir("/");
}
}
/* Source any environment from env:/path/to/file */
source_env(svc);
int status;
if (!svc_is_sysv(svc)) {
wordexp_t we = { 0 };
@@ -932,6 +943,14 @@ 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)
{
if (access(script, X_OK))
logit(LOG_WARNING, "%s: %s:%s is missing or not executable, skipping.", type, script);
else
strlcpy(buf, script, len);
}
/*
* name:<name>
*/
@@ -1087,6 +1106,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
char *username = NULL, *log = NULL, *pid = NULL;
char *name = NULL, *halt = NULL, *delay = NULL;
char *id = NULL, *env = NULL, *cgroup = NULL;
char *pre_script = NULL, *post_script = NULL;
struct tty tty = { 0 };
char *dev = NULL;
int levels = 0;
@@ -1152,6 +1172,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
halt = &cmd[5];
else if (!strncasecmp(cmd, "kill:", 5))
delay = &cmd[5];
else if (!strncasecmp(cmd, "pre:", 4))
pre_script = &cmd[4];
else if (!strncasecmp(cmd, "post:", 5))
post_script = &cmd[5];
else if (!strncasecmp(cmd, "env:", 4))
env = &cmd[4];
else if (!strncasecmp(cmd, "cgroup:", 7))
@@ -1297,6 +1321,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
parse_sighalt(svc, halt);
if (delay)
parse_killdelay(svc, delay);
if (pre_script)
parse_script("pre", pre_script, svc->pre_script, sizeof(svc->pre_script));
if (post_script)
parse_script("post", post_script, svc->post_script, sizeof(svc->post_script));
if (log)
parse_log(svc, log);
if (desc)
@@ -1368,9 +1396,21 @@ void service_monitor(pid_t lost, int status)
return;
}
_d("collected %s(%d), normal exit: %d, signaled: %d, exit code: %d",
svc->cmd, lost, WIFEXITED(status), WIFSIGNALED(status), WEXITSTATUS(status));
svc->status = status;
switch (svc->state) {
case SVC_CLEANUP_STATE:
case SVC_SETUP_STATE:
_d("collected script %s(%d), normal exit: %d, signaled: %d, exit code: %d",
svc->state == SVC_CLEANUP_STATE ? svc->post_script : svc->pre_script,
lost, WIFEXITED(status), WIFSIGNALED(status), WEXITSTATUS(status));
kill(-svc->pid, SIGKILL);
goto done;
default:
_d("collected %s(%d), normal exit: %d, signaled: %d, exit code: %d",
svc->cmd, lost, WIFEXITED(status), WIFSIGNALED(status), WEXITSTATUS(status));
svc->status = status;
break;
}
/* Forking sysv/services declare themselves with pid:!/path/to/pid.file */
if (svc_is_starting(svc) && svc_is_forking(svc))
@@ -1390,6 +1430,7 @@ void service_monitor(pid_t lost, int status)
svc->started = 0;
}
done:
/* No longer running, update books. */
svc->start_time = svc->pid = 0;
@@ -1434,6 +1475,80 @@ void service_update_rdeps(void)
}
}
static void service_kill_script(svc_t *svc)
{
if (svc->pid <= 1)
return;
kill(-svc->pid, SIGKILL);
}
static void service_pre_script(svc_t *svc)
{
svc->pid = service_fork(svc);
if (svc->pid < 0) {
_pe("Failed forking off %s pre-script %s", svc_ident(svc, NULL, 0), svc->pre_script);
return;
}
if (svc->pid == 0) {
char *argv[4] = {
"sh",
"-c",
svc->pre_script,
NULL
};
setenv("SERVICE_IDENT", svc_ident(svc, NULL, 0), 1);
execvp(_PATH_BSHELL, argv);
_exit(EX_OSERR);
}
/* Short hard-coded timeout to prevent locking up Finit */
service_timeout_after(svc, svc->killdelay, service_kill_script);
}
static void service_post_script(svc_t *svc)
{
svc->pid = service_fork(svc);
if (svc->pid < 0) {
_pe("Failed forking off %s post-script %s", svc_ident(svc, NULL, 0), svc->post_script);
return;
}
if (svc->pid == 0) {
char *argv[4] = {
"sh",
"-c",
svc->post_script,
NULL
};
int rc, sig;
rc = WEXITSTATUS(svc->status);
sig = WTERMSIG(svc->status);
setenv("SERVICE_IDENT", svc_ident(svc, NULL, 0), 1);
if (WIFEXITED(svc->status)) {
char val[4];
setenv("EXIT_CODE", "exited", 1);
snprintf(val, sizeof(val), "%d", rc & 0xff);
setenv("EXIT_STATUS", val, 1);
} else if (WIFSIGNALED(svc->status)) {
setenv("EXIT_CODE", "signal", 1);
setenv("EXIT_STATUS", sig2str(sig), 1);
}
execvp(_PATH_BSHELL, argv);
_exit(EX_OSERR);
}
/* Short hard-coded timeout to prevent locking up Finit */
service_timeout_after(svc, svc->killdelay, service_kill_script);
}
static void service_retry(svc_t *svc)
{
int timeout;
@@ -1510,8 +1625,13 @@ restart:
switch (svc->state) {
case SVC_HALTED_STATE:
if (enabled)
svc_set_state(svc, SVC_READY_STATE);
if (enabled) {
if (svc_has_pre(svc)) {
svc_set_state(svc, SVC_SETUP_STATE);
service_pre_script(svc);
} else
svc_set_state(svc, SVC_READY_STATE);
}
break;
case SVC_DONE_STATE:
@@ -1530,7 +1650,11 @@ restart:
switch (svc->type) {
case SVC_TYPE_SERVICE:
case SVC_TYPE_TTY:
svc_set_state(svc, SVC_HALTED_STATE);
if (svc_has_post(svc)) {
svc_set_state(svc, SVC_CLEANUP_STATE);
service_post_script(svc);
} else
svc_set_state(svc, SVC_HALTED_STATE);
break;
case SVC_TYPE_TASK:
@@ -1546,6 +1670,16 @@ restart:
}
break;
case SVC_CLEANUP_STATE:
if (!svc->pid)
svc_set_state(svc, SVC_HALTED_STATE);
break;
case SVC_SETUP_STATE:
if (!svc->pid)
svc_set_state(svc, SVC_READY_STATE);
break;
case SVC_READY_STATE:
if (!enabled) {
svc_set_state(svc, SVC_HALTED_STATE);
+12
View File
@@ -54,6 +54,8 @@ 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_WAITING_STATE, /* Condition is in flux, process SIGSTOPed */
SVC_READY_STATE, /* Enabled but condition not satisfied */
SVC_RUNNING_STATE, /* Process running */
@@ -160,6 +162,8 @@ typedef struct svc {
int args_dirty;
char desc[MAX_STR_LEN];
char env[MAX_ARG_LEN];
char pre_script[MAX_ARG_LEN];
char post_script[MAX_ARG_LEN];
/*
* Used to forcefully kill services that won't shutdown on
@@ -214,6 +218,8 @@ static inline int svc_is_forking (svc_t *svc) { return (svc_is_daemon(svc) ||
static inline int svc_in_runlevel (svc_t *svc, int runlevel) { return svc && ISSET(svc->runlevels, runlevel); }
static inline int svc_nohup (svc_t *svc) { return svc && (0 == svc->sighup || 0 != svc->args_dirty); }
static inline int svc_has_pidfile (svc_t *svc) { return svc_is_daemon(svc) && svc->pidfile[0] != 0 && svc->pidfile[0] != '!'; }
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 void svc_starting (svc_t *svc) { if (svc) svc->starting = 1; }
static inline void svc_started (svc_t *svc) { if (svc) svc->starting = 0; }
@@ -301,6 +307,12 @@ static inline char *svc_status(svc_t *svc)
case SVC_WAITING_STATE:
return "waiting";
case SVC_CLEANUP_STATE:
return "cleanup";
case SVC_SETUP_STATE:
return "setup";
case SVC_READY_STATE:
return "ready";