mirror of
https://github.com/troglobit/finit.git
synced 2026-09-30 21:13:01 +07:00
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:
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 138 KiB |
+180
-46
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user