diff options
Diffstat (limited to 'src/supervision/s6-supervise.c')
-rw-r--r-- | src/supervision/s6-supervise.c | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/src/supervision/s6-supervise.c b/src/supervision/s6-supervise.c new file mode 100644 index 0000000..f9a9872 --- /dev/null +++ b/src/supervision/s6-supervise.c @@ -0,0 +1,508 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/bytestr.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <skalibs/sig.h> +#include <skalibs/selfpipe.h> +#include <skalibs/environ.h> +#include <skalibs/skamisc.h> +#include <s6/ftrigw.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-supervise dir" + +typedef enum trans_e trans_t, *trans_t_ref ; +enum trans_e +{ + V_TIMEOUT, V_CHLD, V_TERM, V_HUP, V_QUIT, + V_a, V_b, V_q, V_h, V_k, V_t, V_i, V_1, V_2, V_f, V_F, V_p, V_c, + V_o, V_d, V_u, V_x, V_O +} ; + +typedef enum state_e state_t, *state_t_ref ; +enum state_e +{ + DOWN, + UP, + FINISH, + LASTUP, + LASTFINISH +} ; + +typedef void action_t (void) ; +typedef action_t *action_t_ref ; + +static tain_t deadline ; +static s6_svstatus_t status = { .stamp = TAIN_ZERO, .pid = 0, .flagwant = 1, .flagwantup = 1, .flagpaused = 0, .flagfinishing = 0 } ; +static state_t state = DOWN ; +static int flagsetsid = 1 ; +static int cont = 1 ; + +static inline void settimeout (int secs) +{ + tain_addsec_g(&deadline, secs) ; +} + +static inline void settimeout_infinite (void) +{ + tain_add_g(&deadline, &tain_infinite_relative) ; +} + +static inline void announce (void) +{ + if (!s6_svstatus_write(".", &status)) + strerr_warnwu1sys("write status file") ; +} + + +/* The action array. */ + +static void nop (void) +{ +} + +static void bail (void) +{ + cont = 0 ; +} + +static void killa (void) +{ + kill(status.pid, SIGALRM) ; +} + +static void killb (void) +{ + kill(status.pid, SIGABRT) ; +} + +static void killh (void) +{ + kill(status.pid, SIGHUP) ; +} + +static void killq (void) +{ + kill(status.pid, SIGQUIT) ; +} + +static void killk (void) +{ + kill(status.pid, SIGKILL) ; +} + +static void killt (void) +{ + kill(status.pid, SIGTERM) ; +} + +static void killi (void) +{ + kill(status.pid, SIGINT) ; +} + +static void kill1 (void) +{ + kill(status.pid, SIGUSR1) ; +} + +static void kill2 (void) +{ + kill(status.pid, SIGUSR2) ; +} + +static void killp (void) +{ + kill(status.pid, SIGSTOP) ; + status.flagpaused = 1 ; + announce() ; +} + +static void killc (void) +{ + kill(status.pid, SIGCONT) ; + status.flagpaused = 0 ; + announce() ; +} + +static void trystart (void) +{ + int p[2] ; + pid_t pid ; + if (pipecoe(p) < 0) + { + settimeout(60) ; + strerr_warnwu1sys("pipecoe (waiting 60 seconds)") ; + return ; + } + pid = fork() ; + if (pid < 0) + { + settimeout(60) ; + strerr_warnwu1sys("fork (waiting 60 seconds)") ; + fd_close(p[1]) ; fd_close(p[0]) ; + return ; + } + else if (!pid) + { + char const *cargv[2] = { "run", 0 } ; + PROG = "s6-supervise (child)" ; + selfpipe_finish() ; + fd_close(p[0]) ; + if (flagsetsid) setsid() ; + execve("./run", (char *const *)cargv, (char *const *)environ) ; + fd_write(p[1], "", 1) ; + strerr_dieexec(111, "run") ; + } + fd_close(p[1]) ; + { + char c ; + switch (fd_read(p[0], &c, 1)) + { + case -1 : + fd_close(p[0]) ; + settimeout(60) ; + strerr_warnwu1sys("read pipe (waiting 60 seconds)") ; + kill(pid, SIGKILL) ; + return ; + case 1 : + { + fd_close(p[0]) ; + settimeout(10) ; + strerr_warnwu1x("spawn ./run - waiting 10 seconds") ; + return ; + } + } + } + fd_close(p[0]) ; + settimeout_infinite() ; + state = UP ; + status.pid = pid ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'u') ; +} + +static void downtimeout (void) +{ + if (status.flagwant && status.flagwantup) trystart() ; + else settimeout_infinite() ; +} + +static void down_O (void) +{ + status.flagwant = 0 ; + announce() ; +} + +static void down_o (void) +{ + down_O() ; + trystart() ; +} + +static void down_u (void) +{ + status.flagwant = 1 ; + status.flagwantup = 1 ; + announce() ; + trystart() ; +} + +static void down_d (void) +{ + status.flagwant = 1 ; + status.flagwantup = 0 ; + announce() ; +} + +static void tryfinish (int wstat, int islast) +{ + register pid_t pid = fork() ; + if (pid < 0) + { + strerr_warnwu2sys("fork for ", "./finish") ; + if (islast) bail() ; + state = DOWN ; + status.pid = 0 ; + settimeout(1) ; + return ; + } + else if (!pid) + { + char fmt0[UINT_FMT] ; + char fmt1[UINT_FMT] ; + char *cargv[4] = { "finish", fmt0, fmt1, 0 } ; + selfpipe_finish() ; + fmt0[uint_fmt(fmt0, WIFSIGNALED(wstat) ? 255 : WEXITSTATUS(wstat))] = 0 ; + fmt1[uint_fmt(fmt1, WIFSIGNALED(wstat))] = 0 ; + if (flagsetsid) setsid() ; + execve("./finish", cargv, (char *const *)environ) ; + _exit(111) ; + } + status.pid = pid ; + status.flagfinishing = 1 ; + state = islast ? LASTFINISH : FINISH ; + settimeout(5) ; +} + +static void uptimeout (void) +{ + settimeout_infinite() ; + strerr_warnw1x("can't happen: timeout while the service is up!") ; +} + +static void up_z (void) +{ + int wstat = status.pid ; + status.pid = 0 ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'd') ; + tryfinish(wstat, 0) ; +} + +static void up_o (void) +{ + status.flagwant = 0 ; + announce() ; +} + +static void up_d (void) +{ + status.flagwant = 1 ; + status.flagwantup = 0 ; + killt() ; + killc() ; +} + +static void up_u (void) +{ + status.flagwant = 1 ; + status.flagwantup = 1 ; + announce() ; +} + +static void closethem (void) +{ + fd_close(0) ; + fd_close(1) ; + open_read("/dev/null") ; + open_write("/dev/null") ; +} + +static void up_x (void) +{ + state = LASTUP ; + closethem() ; +} + +static void up_term (void) +{ + up_x() ; + up_d() ; +} + +static void finishtimeout (void) +{ + strerr_warnw1x("finish script takes too long - killing it") ; + killc() ; killk() ; + settimeout(3) ; +} + +static void finish_z (void) +{ + status.pid = 0 ; + status.flagfinishing = 0 ; + state = DOWN ; + announce() ; + settimeout(1) ; +} + +static void finish_u (void) +{ + status.flagwant = 1 ; + status.flagwantup = 1 ; + announce() ; +} + +static void finish_x (void) +{ + state = LASTFINISH ; + closethem() ; +} + +static void lastup_z (void) +{ + int wstat = status.pid ; + status.pid = 0 ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'd') ; + tryfinish(wstat, 1) ; +} + +static action_t_ref const actions[5][23] = +{ + { &downtimeout, &nop, &bail, &bail, &bail, + &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, + &down_o, &down_d, &down_u, &bail, &down_O }, + { &uptimeout, &up_z, &up_term, &up_x, &up_term, + &killa, &killb, &killq, &killh, &killk, &killt, &killi, &kill1, &kill2, &nop, &nop, &killp, &killc, + &up_o, &up_d, &up_u, &up_x, &up_o }, + { &finishtimeout, &finish_z, &finish_x, &finish_x, &finish_x, + &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, + &up_o, &down_d, &finish_u, &finish_x, &up_o }, + { &uptimeout, &lastup_z, &up_d, &nop, &up_d, + &killa, &killb, &killq, &killh, &killk, &killt, &killi, &kill1, &kill2, &nop, &nop, &killp, &killc, + &up_o, &up_d, &nop, &nop, &up_o }, + { &finishtimeout, &bail, &nop, &nop, &nop, + &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, + &nop, &nop, &nop, &nop, &nop } +} ; + + +/* The main loop. + It just loops around the iopause(), calling snippets of code in "actions" when needed. */ + + +static void handle_signals (void) +{ + for (;;) + { + char c = selfpipe_read() ; + switch (c) + { + case -1 : strerr_diefu1sys(111, "selfpipe_read") ; + case 0 : return ; + case SIGCHLD : + if (!status.pid) wait_reap() ; + else + { + int wstat ; + int r = wait_pid_nohang(status.pid, &wstat) ; + if (r < 0) + if (errno != ECHILD) strerr_diefu1sys(111, "wait_pid_nohang") ; + else break ; + else if (!r) break ; + status.pid = wstat ; + (*actions[state][V_CHLD])() ; + } + break ; + case SIGTERM : + (*actions[state][V_TERM])() ; + break ; + case SIGHUP : + (*actions[state][V_HUP])() ; + break ; + case SIGQUIT : + (*actions[state][V_QUIT])() ; + break ; + default : + strerr_dief1x(101, "internal error: inconsistent signal state. Please submit a bug-report.") ; + } + } +} + +static void handle_control (int fd) +{ + for (;;) + { + char c ; + register int r = sanitize_read(fd_read(fd, &c, 1)) ; + if (r < 0) strerr_diefu1sys(111, "read " S6_SUPERVISE_CTLDIR "/control") ; + else if (!r) break ; + else + { + register unsigned int pos = byte_chr("abqhkti12fFpcoduxO", 18, c) ; + if (pos < 18) (*actions[state][V_a + pos])() ; + } + } +} + +int main (int argc, char const *const *argv) +{ + iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ; + PROG = "s6-supervise" ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + if (chdir(argv[1]) < 0) strerr_diefu2sys(111, "chdir to ", argv[1]) ; + { + register unsigned int proglen = str_len(PROG) ; + register unsigned int namelen = str_len(argv[1]) ; + char progname[proglen + namelen + 2] ; + byte_copy(progname, proglen, PROG) ; + progname[proglen] = ' ' ; + byte_copy(progname + proglen + 1, namelen + 1, argv[1]) ; + PROG = progname ; + if (!fd_sanitize()) strerr_diefu1sys(111, "sanitize stdin and stdout") ; + x[1].fd = s6_supervise_lock(S6_SUPERVISE_CTLDIR) ; + if (!ftrigw_fifodir_make(S6_SUPERVISE_EVENTDIR, -1, 0)) + strerr_diefu2sys(111, "mkfifodir ", S6_SUPERVISE_EVENTDIR) ; + x[0].fd = selfpipe_init() ; + if (x[0].fd == -1) strerr_diefu1sys(111, "init selfpipe") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ; + { + sigset_t set ; + sigemptyset(&set) ; + sigaddset(&set, SIGTERM) ; + sigaddset(&set, SIGHUP) ; + sigaddset(&set, SIGQUIT) ; + sigaddset(&set, SIGCHLD) ; + if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ; + } + + if (!ftrigw_clean(S6_SUPERVISE_EVENTDIR)) + strerr_warnwu2sys("ftrigw_clean ", S6_SUPERVISE_EVENTDIR) ; + + { + struct stat st ; + if (stat("down", &st) == -1) + { + if (errno != ENOENT) + strerr_diefu1sys(111, "stat down") ; + } + else status.flagwantup = 0 ; + if (stat("nosetsid", &st) == -1) + { + if (errno != ENOENT) + strerr_diefu1sys(111, "stat nosetsid") ; + } + else flagsetsid = 0 ; + } + + tain_now_g() ; + settimeout(0) ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 's') ; + + while (cont) + { + register int r = iopause_g(x, 2, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + else if (!r) (*actions[state][V_TIMEOUT])() ; + else + { + if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) + strerr_diefu1x(111, "iopause: trouble with pipes") ; + if (x[0].revents & IOPAUSE_READ) handle_signals() ; + else if (x[1].revents & IOPAUSE_READ) handle_control(x[1].fd) ; + } + } + + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'x') ; + } + return 0 ; +} |