mirror of
https://github.com/troglobit/finit.git
synced 2026-11-02 13:20:32 +00: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:
1 parent
9fd860ef3c
commit
ba858743e5
3 files changed
+192
-46
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 17 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;
|
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
|
* service_start - Start service
|
||||||
* @svc: Service to start
|
* @svc: Service to start
|
||||||
@@ -443,48 +492,10 @@ static int service_start(svc_t *svc)
|
|||||||
sigaddset(&nmask, SIGCHLD);
|
sigaddset(&nmask, SIGCHLD);
|
||||||
sigprocmask(SIG_BLOCK, &nmask, &omask);
|
sigprocmask(SIG_BLOCK, &nmask, &omask);
|
||||||
|
|
||||||
pid = fork();
|
pid = service_fork(svc);
|
||||||
if (pid == 0) {
|
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];
|
char *args[MAX_NUM_SVC_ARGS + 1];
|
||||||
|
int status;
|
||||||
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);
|
|
||||||
|
|
||||||
if (!svc_is_sysv(svc)) {
|
if (!svc_is_sysv(svc)) {
|
||||||
wordexp_t we = { 0 };
|
wordexp_t we = { 0 };
|
||||||
@@ -932,6 +943,14 @@ static void parse_killdelay(svc_t *svc, char *delay)
|
|||||||
svc->killdelay = (int)(sec * 1000);
|
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>
|
* 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 *username = NULL, *log = NULL, *pid = NULL;
|
||||||
char *name = NULL, *halt = NULL, *delay = NULL;
|
char *name = NULL, *halt = NULL, *delay = NULL;
|
||||||
char *id = NULL, *env = NULL, *cgroup = NULL;
|
char *id = NULL, *env = NULL, *cgroup = NULL;
|
||||||
|
char *pre_script = NULL, *post_script = NULL;
|
||||||
struct tty tty = { 0 };
|
struct tty tty = { 0 };
|
||||||
char *dev = NULL;
|
char *dev = NULL;
|
||||||
int levels = 0;
|
int levels = 0;
|
||||||
@@ -1152,6 +1172,10 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
|||||||
halt = &cmd[5];
|
halt = &cmd[5];
|
||||||
else if (!strncasecmp(cmd, "kill:", 5))
|
else if (!strncasecmp(cmd, "kill:", 5))
|
||||||
delay = &cmd[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))
|
else if (!strncasecmp(cmd, "env:", 4))
|
||||||
env = &cmd[4];
|
env = &cmd[4];
|
||||||
else if (!strncasecmp(cmd, "cgroup:", 7))
|
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);
|
parse_sighalt(svc, halt);
|
||||||
if (delay)
|
if (delay)
|
||||||
parse_killdelay(svc, 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)
|
if (log)
|
||||||
parse_log(svc, log);
|
parse_log(svc, log);
|
||||||
if (desc)
|
if (desc)
|
||||||
@@ -1368,9 +1396,21 @@ void service_monitor(pid_t lost, int status)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_d("collected %s(%d), normal exit: %d, signaled: %d, exit code: %d",
|
switch (svc->state) {
|
||||||
svc->cmd, lost, WIFEXITED(status), WIFSIGNALED(status), WEXITSTATUS(status));
|
case SVC_CLEANUP_STATE:
|
||||||
svc->status = status;
|
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 */
|
/* Forking sysv/services declare themselves with pid:!/path/to/pid.file */
|
||||||
if (svc_is_starting(svc) && svc_is_forking(svc))
|
if (svc_is_starting(svc) && svc_is_forking(svc))
|
||||||
@@ -1390,6 +1430,7 @@ void service_monitor(pid_t lost, int status)
|
|||||||
svc->started = 0;
|
svc->started = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
/* No longer running, update books. */
|
/* No longer running, update books. */
|
||||||
svc->start_time = svc->pid = 0;
|
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)
|
static void service_retry(svc_t *svc)
|
||||||
{
|
{
|
||||||
int timeout;
|
int timeout;
|
||||||
@@ -1510,8 +1625,13 @@ restart:
|
|||||||
|
|
||||||
switch (svc->state) {
|
switch (svc->state) {
|
||||||
case SVC_HALTED_STATE:
|
case SVC_HALTED_STATE:
|
||||||
if (enabled)
|
if (enabled) {
|
||||||
svc_set_state(svc, SVC_READY_STATE);
|
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;
|
break;
|
||||||
|
|
||||||
case SVC_DONE_STATE:
|
case SVC_DONE_STATE:
|
||||||
@@ -1530,7 +1650,11 @@ restart:
|
|||||||
switch (svc->type) {
|
switch (svc->type) {
|
||||||
case SVC_TYPE_SERVICE:
|
case SVC_TYPE_SERVICE:
|
||||||
case SVC_TYPE_TTY:
|
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;
|
break;
|
||||||
|
|
||||||
case SVC_TYPE_TASK:
|
case SVC_TYPE_TASK:
|
||||||
@@ -1546,6 +1670,16 @@ restart:
|
|||||||
}
|
}
|
||||||
break;
|
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:
|
case SVC_READY_STATE:
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
svc_set_state(svc, SVC_HALTED_STATE);
|
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_HALTED_STATE = 0, /* Not allowed in runlevel, or not enabled. */
|
||||||
SVC_DONE_STATE, /* Task/Run job has been run */
|
SVC_DONE_STATE, /* Task/Run job has been run */
|
||||||
SVC_STOPPING_STATE, /* Waiting to collect the child process */
|
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_WAITING_STATE, /* Condition is in flux, process SIGSTOPed */
|
||||||
SVC_READY_STATE, /* Enabled but condition not satisfied */
|
SVC_READY_STATE, /* Enabled but condition not satisfied */
|
||||||
SVC_RUNNING_STATE, /* Process running */
|
SVC_RUNNING_STATE, /* Process running */
|
||||||
@@ -160,6 +162,8 @@ typedef struct svc {
|
|||||||
int args_dirty;
|
int args_dirty;
|
||||||
char desc[MAX_STR_LEN];
|
char desc[MAX_STR_LEN];
|
||||||
char env[MAX_ARG_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
|
* 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_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_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_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_starting (svc_t *svc) { if (svc) svc->starting = 1; }
|
||||||
static inline void svc_started (svc_t *svc) { if (svc) svc->starting = 0; }
|
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:
|
case SVC_WAITING_STATE:
|
||||||
return "waiting";
|
return "waiting";
|
||||||
|
|
||||||
|
case SVC_CLEANUP_STATE:
|
||||||
|
return "cleanup";
|
||||||
|
|
||||||
|
case SVC_SETUP_STATE:
|
||||||
|
return "setup";
|
||||||
|
|
||||||
case SVC_READY_STATE:
|
case SVC_READY_STATE:
|
||||||
return "ready";
|
return "ready";
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user