s6

Mirror/fork of https://skarnet.org/software/s6/
git clone https://ccx.te2000.cz/git/s6
Log | Files | Refs | README | LICENSE

commit 9a669c4b3973118dfcce2a2b1fb4babd0aaa6af7
parent 2654165a84b90af2cbfef967e6e230b2975b644c
Author: Laurent Bercot <ska-skaware@skarnet.org>
Date:   Sat, 25 Mar 2017 11:25:15 +0000

 Stop using flagwant; new s6-svstat API; s6-svwait's timeout exitcode is now 99

Diffstat:
Mdoc/s6-svlisten.html | 2+-
Mdoc/s6-svlisten1.html | 2+-
Mdoc/s6-svstat.html | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mdoc/s6-svwait.html | 2+-
Msrc/include/s6/s6-supervise.h | 2+-
Msrc/supervision/s6-supervise.c | 13++++---------
Msrc/supervision/s6-svstat.c | 261+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/supervision/s6_svlisten_loop.c | 2+-
8 files changed, 319 insertions(+), 58 deletions(-)

diff --git a/doc/s6-svlisten.html b/doc/s6-svlisten.html @@ -85,7 +85,7 @@ given services comes up or down. </li> given services come up or down. This is the default. </li> <li> <tt>-t&nbsp;<em>timeout</em></tt>&nbsp;: if the requested events have not happened after <em>timeout</em> milliseconds, s6-svlisten will print a message -to stderr and exit 1. By default, <em>timeout</em> is 0, which means no time +to stderr and exit 99. By default, <em>timeout</em> is 0, which means no time limit. </li> </ul> diff --git a/doc/s6-svlisten1.html b/doc/s6-svlisten1.html @@ -68,7 +68,7 @@ the service has been started or restarted and has notified readiness. </li> <li> <tt>-t&nbsp;<em>timeout</em></tt>&nbsp;: if the requested event has not happened after <em>timeout</em> milliseconds, s6-svlisten1 will print a message -to stderr and exit 1. By default, <em>timeout</em> is 0, which means no time +to stderr and exit 99. By default, <em>timeout</em> is 0, which means no time limit. </li> </ul> diff --git a/doc/s6-svstat.html b/doc/s6-svstat.html @@ -26,13 +26,22 @@ monitored by <a href="s6-supervise.html">s6-supervise</a>. <h2> Interface </h2> <pre> - s6-svstat [ -n ] <em>servicedir</em> + s6-svstat [ -uwNrpest | -o up,wantedup,normallyup,ready,paused,pid,exitcode,signal,signum,updownsince,readysince,updownfor,readyfor ] [ -n ] <em>servicedir</em> </pre> <p> s6-svstat gives information about the process being monitored at the <em>servicedir</em> <a href="servicedir.html">service directory</a>, then -exits 0. The information includes the following: +exits 0. +</p> + +<p> + When s6-svstat is invoked without options, or with only the <tt>-n</tt> option, +it prints a human-readable summary of all the +available information on the service. In this case, the <tt>-n</tt> option +instructs it to print a signal number (instead of a signal name) if the +supervised process was killed by a signal. The summary includes the +following data: </p> <ul> @@ -42,8 +51,6 @@ seconds that it has been up. </li> signal, if it is down </li> <li> what its default state is, if it is different from its current state </li> <li> the number of seconds since it last changed states </li> - <li> if the current state is transient and will change as soon as the -kernel's scheduler picks up s6-supervise </li> <li> whether the service is <a href="notifywhenup.html">ready</a>, as notified by the daemon itself, and if it is, the number of seconds that it has been. @@ -53,12 +60,66 @@ cleanup phase, i.e. the <tt>./finish</tt> script is still being executed. </li> </li> </ul> +<p> + When s6-svstat is invoked with one or several options other than <tt>-n</tt>, +it outputs programmatically parsable information instead. The output is a series of +space-separated values, one value per requested field. The valid options +are as follows. +</p> + <h2> Options </h2> <ul> - <li> <tt>-n</tt>&nbsp;: if the monitored process has been killed by a signal, -print the signal number. By default, a symbolic name for the signal will be -printed instead. </li> + <li> <tt>-o&nbsp;</tt><em>fields</em>&nbsp;: list fields to print. +<em>fields</em> is a list of comma-separated field names. The valid field +names are the following: + <ul> + <li> <tt>up</tt>: print <tt>true</tt> if the service is up and <tt>false</tt> if it is down. </li> + <li> <tt>wantedup</tt>: print <tt>true</tt> if <a href="s6-supervise.html">s6-supervise</a> +is currently instructed to (re)start the service when it is down, and <tt>false</tt> if +<a href="s6-supervise.html">s6-supervise</a> is currently instructed to leave the service alone. </li> + <li> <tt>normallyup</tt>: print <tt>true</tt> if the service is supposed to start when +<a href="s6-supervise.html">s6-supervise</a> starts (i.e. no <tt>./down</tt> file), and <tt>false</tt> +if it is not (i.e. there is a <tt>./down</tt> file). </li> + <li> <tt>ready</tt>: print <tt>true</tt> if the service is ready, and <tt>false</tt> if +it is not. Note that a service can be both <em>down</em> and <em>ready</em>: it simply means that +it is ready to be started (i.e. no <tt>./finish</tt> script is currently running). To check for +service readiness, you should give this option along with <tt>up</tt>: the service is ready iff +<tt>s6-svstat -o up,ready</tt> prints <tt>true true</tt>. </li> + <li> <tt>paused</tt>: print <tt>true</tt> if the service is paused (i.e. the process is +currently stopped) and <tt>false</tt> if it is not. It is a rare flag, you shouldn't normally +need to use this option. </li> + <li> <tt>pid</tt>: print the pid of the supervised process. If the service is currently down, +<tt>-1</tt> is printed instead. </li> + <li> <tt>pid</tt>: print the exit code of the last execution of <tt>./run</tt>. If the +service is currently up, or the last <tt>./run</tt> process was killed by a signal, +<tt>-1</tt> is printed instead. </li> + <li> <tt>signal</tt>: print the name of the signal the last <tt>./run</tt> process was +killed with. If the service is currently up, or the last <tt>./run</tt> process was not +killed by a signal, <tt>NA</tt> is printed instead. </li> + <li> <tt>signum</tt>: print the number of the signal the last <tt>./run</tt> process was +killed with. If the service is currently up, or the last <tt>./run</tt> process was not +killed by a signal, <tt>-1</tt> is printed instead. </li> + <li> <tt>updownsince</tt>: print a <a href="http://cr.yp.to/libtai/tai64.html">TAI64N +label</a> representing the absolute date when the service last changed states. </li> + <li> <tt>readysince</tt>: print a <a href="http://cr.yp.to/libtai/tai64.html">TAI64N +label</a> representing the absolute date when the service last became ready. Note that +this can either mean "service readiness" (if the service is currently up and ready), or +"down readiness", i.e. the last time when the service was down and ready to be started +(if the service is <em>not</em> currently up and ready). </li> + <li> <tt>updownfor</tt>: print the number of seconds that have elapsed since the +service last changed states. </li> + <li> <tt>readyfor</tt>: print the number of seconds that have elapsed since the +service last became ready (or ready to be started if it's currently not up and ready). </li> + </ul> + <li> <tt>-u</tt>: equivalent to <tt>-o&nbsp;up</tt>. </li> + <li> <tt>-w</tt>: equivalent to <tt>-o&nbsp;wantedup</tt>. </li> + <li> <tt>-N</tt>: equivalent to <tt>-o&nbsp;normallyup</tt>. </li> + <li> <tt>-r</tt>: equivalent to <tt>-o&nbsp;ready</tt>. </li> + <li> <tt>-p</tt>: equivalent to <tt>-o&nbsp;pid</tt>. </li> + <li> <tt>-e</tt>: equivalent to <tt>-o&nbsp;exitcode</tt>. </li> + <li> <tt>-s</tt>: equivalent to <tt>-o&nbsp;signal</tt>. </li> + <li> <tt>-t</tt>: equivalent to <tt>-o&nbsp;updownfor</tt>. </li> </ul> <h2> Exit codes </h2> @@ -70,5 +131,23 @@ printed instead. </li> <li> 111: system call failed </li> </ul> +<h2> Examples </h2> + +<ul> + <li> <tt>s6-svstat -o up,ready</tt> (or its equivalent <tt>s6-svstat -ur</tt>) +will print <tt>true true</tt> if the service is up and +ready, <tt>true false</tt> if the service has been started but has not notified +readiness yet, <tt>false true</tt> if it is down and can be started, and +<tt>false false</tt> if it is down and there's a <tt>./finish</tt> script running +that needs to exit before the service can be restarted. </li> + <li> <tt>s6-svstat -o pid,exitcode,signal</tt> (or its equivalent <tt>s6-svstat -pes</tt>) +will print <tt>42 -1 NA</tt> if the service has +been started and <tt>./run</tt>'s pid is 42; it will print <tt>-1 0 NA</tt> if the +service is down and <tt>./run</tt> last exited 0; it will print <tt>-1 -1 SIGTERM</tt> +if the service is down and <tt>./run</tt> was last killed by a SIGTERM - as can +happen, for instance, when you down the service via a call to +<a href="s6-svc.html">s6-svc -d</a>. </li> +</ul> + </body> </html> diff --git a/doc/s6-svwait.html b/doc/s6-svwait.html @@ -64,7 +64,7 @@ given services comes up or down. </li> given services come up or down. This is the default. </li> <li> <tt>-t&nbsp;<em>timeout</em></tt>&nbsp;: if the requested events have not happened after <em>timeout</em> milliseconds, s6-svwait will print a message -to stderr and exit 1. By default, <em>timeout</em> is 0, which means no time +to stderr and exit 99. By default, <em>timeout</em> is 0, which means no time limit. </li> </ul> diff --git a/src/include/s6/s6-supervise.h b/src/include/s6/s6-supervise.h @@ -26,7 +26,7 @@ struct s6_svstatus_s int wstat ; unsigned int flagpaused : 1 ; unsigned int flagfinishing : 1 ; - unsigned int flagwant : 1 ; + unsigned int flagwant : 1 ; /* unused */ unsigned int flagwantup : 1 ; unsigned int flagready : 1 ; } ; diff --git a/src/supervision/s6-supervise.c b/src/supervision/s6-supervise.c @@ -297,13 +297,13 @@ static void trystart (void) static void downtimeout (void) { - if (status.flagwant && status.flagwantup) trystart() ; + if (status.flagwantup) trystart() ; else settimeout_infinite() ; } static void down_O (void) { - status.flagwant = 0 ; + status.flagwantup = 0 ; announce() ; } @@ -315,7 +315,6 @@ static void down_o (void) static void down_u (void) { - status.flagwant = 1 ; status.flagwantup = 1 ; announce() ; trystart() ; @@ -323,7 +322,6 @@ static void down_u (void) static void down_d (void) { - status.flagwant = 1 ; status.flagwantup = 0 ; announce() ; } @@ -391,13 +389,12 @@ static void uptimeout (void) static void up_o (void) { - status.flagwant = 0 ; + status.flagwantup = 0 ; announce() ; } static void up_d (void) { - status.flagwant = 1 ; status.flagwantup = 0 ; killt() ; killc() ; @@ -405,7 +402,6 @@ static void up_d (void) static void up_u (void) { - status.flagwant = 1 ; status.flagwantup = 1 ; announce() ; } @@ -439,7 +435,7 @@ static void finish_z (void) int wstat = (int)status.pid ; if (WIFEXITED(wstat) && WEXITSTATUS(wstat) == 125) { - status.flagwant = 0 ; + status.flagwantup = 0 ; set_down_and_ready("OD", 2) ; } else set_down_and_ready("D", 1) ; @@ -447,7 +443,6 @@ static void finish_z (void) static void finish_u (void) { - status.flagwant = 1 ; status.flagwantup = 1 ; announce() ; } diff --git a/src/supervision/s6-svstat.c b/src/supervision/s6-svstat.c @@ -6,6 +6,7 @@ #include <errno.h> #include <skalibs/uint64.h> #include <skalibs/types.h> +#include <skalibs/bytestr.h> #include <skalibs/buffer.h> #include <skalibs/strerr2.h> #include <skalibs/sgetopt.h> @@ -14,53 +15,169 @@ #include <skalibs/djbunix.h> #include <s6/s6-supervise.h> -#define USAGE "s6-svstat [ -n ] servicedir" +#define USAGE "s6-svstat [ -uwNrpest | -o up,wantedup,normallyup,ready,paused,pid,exitcode,signal,signum,updownsince,readysince,updownfor,readyfor ] [ -n ] servicedir" #define dieusage() strerr_dieusage(100, USAGE) -int main (int argc, char const *const *argv) +#define MAXFIELDS 16 +#define checkfields() if (n >= MAXFIELDS) strerr_dief1x(100, "too many option fields") + +static int normallyup ; + +typedef void prfunc_t (buffer *, s6_svstatus_t const *) ; +typedef prfunc_t * prfunc_t_ref ; + +typedef struct funcmap_s funcmap_t ; +struct funcmap_s { - s6_svstatus_t status ; - int flagnum = 0 ; - int isup, normallyup ; + char const *s ; + prfunc_t_ref f ; +} ; + +static void pr_up (buffer *b, s6_svstatus_t const *st) +{ + buffer_putsnoflush(b, st->pid && !st->flagfinishing ? "true" : "false") ; +} + +static void pr_wantedup (buffer *b, s6_svstatus_t const *st) +{ + buffer_putsnoflush(b, st->flagwantup ? "true" : "false") ; +} + +static void pr_ready (buffer *b, s6_svstatus_t const *st) +{ + buffer_putsnoflush(b, st->pid && st->flagready ? "true" : "false") ; +} + +static void pr_paused (buffer *b, s6_svstatus_t const *st) +{ + buffer_putsnoflush(b, st->flagpaused ? "true" : "false") ; +} + +static void pr_pid (buffer *b, s6_svstatus_t const *st) +{ + if (st->pid && !st->flagfinishing) + { + char fmt[PID_FMT] ; + buffer_putnoflush(b, fmt, pid_fmt(fmt, st->pid)) ; + } + else buffer_putsnoflush(b, "-1") ; +} + +static void pr_tain (buffer *b, tain_t const *a) +{ + char fmt[TIMESTAMP] ; + buffer_putnoflush(b, fmt, timestamp_fmt(fmt, a)) ; +} + +static void pr_stamp (buffer *b, s6_svstatus_t const *st) +{ + pr_tain(b, &st->stamp) ; +} + +static void pr_readystamp (buffer *b, s6_svstatus_t const *st) +{ + pr_tain(b, &st->readystamp) ; +} + +static void pr_seconds (buffer *b, tain_t const *a) +{ + tain_t d ; char fmt[UINT64_FMT] ; - PROG = "s6-svstat" ; + tain_sub(&d, &STAMP, a) ; + buffer_putnoflush(b, fmt, uint64_fmt(fmt, tai_sec(tain_secp(&d)))) ; +} + +static void pr_upseconds (buffer *b, s6_svstatus_t const *st) +{ + pr_seconds(b, &st->stamp) ; +} + +static void pr_readyseconds (buffer *b, s6_svstatus_t const *st) +{ + pr_seconds(b, &st->readystamp) ; +} + +static void pr_exitcode (buffer *b, s6_svstatus_t const *st) +{ + int e = st->pid && !st->flagfinishing ? -1 : + WIFEXITED(st->wstat) ? WEXITSTATUS(st->wstat) : -1 ; + char fmt[INT_FMT] ; + buffer_putnoflush(b, fmt, int_fmt(fmt, e)) ; +} + +static void pr_signum (buffer *b, s6_svstatus_t const *st) +{ + int e = st->pid && !st->flagfinishing ? -1 : + WIFSIGNALED(st->wstat) ? WTERMSIG(st->wstat) : -1 ; + char fmt[INT_FMT] ; + buffer_putnoflush(b, fmt, int_fmt(fmt, e)) ; +} + +static void pr_signal (buffer *b, s6_svstatus_t const *st) +{ + int e = st->pid && !st->flagfinishing ? -1 : + WIFSIGNALED(st->wstat) ? WTERMSIG(st->wstat) : -1 ; + if (e == -1) buffer_putsnoflush(b, "NA") ; + else { - subgetopt_t l = SUBGETOPT_ZERO ; - for (;;) - { - int opt = subgetopt_r(argc, argv, "n", &l) ; - if (opt == -1) break ; - switch (opt) - { - case 'n' : flagnum = 1 ; break ; - default : dieusage() ; - } - } - argc -= l.ind ; argv += l.ind ; + buffer_putsnoflush(b, "SIG") ; + buffer_putsnoflush(b, sig_name(e)) ; } - if (!argc) dieusage() ; +} - if (!s6_svstatus_read(*argv, &status)) - strerr_diefu2sys(111, "read status for ", *argv) ; - isup = s6_svc_ok(argv[0]) ; - if (isup < 0) strerr_diefu2sys(111, "check ", argv[0]) ; - if (!isup) strerr_diefu3x(1, "read status for ", argv[0], ": s6-supervise not running") ; +static void pr_normallyup (buffer *b, s6_svstatus_t const *st) +{ + buffer_putsnoflush(b, normallyup ? "true" : "false") ; + (void)st ; +} - tain_now_g() ; - if (tain_future(&status.stamp)) tain_copynow(&status.stamp) ; +static funcmap_t const fmtable[] = +{ + { .s = "up", .f = &pr_up }, + { .s = "wantedup", .f = &pr_wantedup }, + { .s = "normallyup", .f = &pr_normallyup }, + { .s = "ready", .f = &pr_ready }, + { .s = "paused", .f = &pr_paused }, + { .s = "pid", .f = &pr_pid }, + { .s = "exitcode", .f = &pr_exitcode }, + { .s = "signal", .f = &pr_signal }, + { .s = "signum", .f = &pr_signum }, + { .s = "updownsince", .f = &pr_stamp }, + { .s = "readysince", .f = &pr_readystamp }, + { .s = "updownfor", .f = &pr_upseconds }, + { .s = "readyfor", .f = &pr_readyseconds }, + { .s = 0, .f = 0 } +} ; + +static unsigned int parse_options (char const *arg, prfunc_t_ref *fields, unsigned int n) +{ + while (*arg) { - size_t dirlen = strlen(*argv) ; - char fn[dirlen + 6] ; - memcpy(fn, *argv, dirlen) ; - memcpy(fn + dirlen, "/down", 6) ; - if (access(fn, F_OK) < 0) - if (errno != ENOENT) strerr_diefu2sys(111, "access ", fn) ; - else normallyup = 1 ; - else normallyup = 0 ; + size_t pos = str_chr(arg, ',') ; + funcmap_t const *p = fmtable ; + if (!pos) strerr_dief1x(100, "invalid null option field") ; + for (; p->s ; p++) if (!strncmp(arg, p->s, pos)) break ; + if (!p->s) + { + char blah[pos+1] ; + memcpy(blah, arg, pos) ; + blah[pos] = 0 ; + strerr_dief2x(100, "invalid option field: ", blah) ; + } + checkfields() ; + fields[n++] = p->f ; + arg += pos ; if (*arg) arg++ ; } + return n ; +} + +static void legacy (s6_svstatus_t *st, int flagnum) +{ + s6_svstatus_t status = *st ; + int isup = status.pid && !status.flagfinishing ; + char fmt[UINT64_FMT] ; - isup = status.pid && !status.flagfinishing ; if (isup) { buffer_putnoflush(buffer_1small,"up (pid ", 8) ; @@ -99,9 +216,9 @@ int main (int argc, char const *const *argv) buffer_putnoflush(buffer_1small, ", normally up", 13) ; if (isup && status.flagpaused) buffer_putnoflush(buffer_1small, ", paused", 8) ; - if (!isup && status.flagwant) + if (!isup && status.flagwantup) buffer_putnoflush(buffer_1small, ", want up", 10) ; - if (isup && !status.flagwant) + if (isup && !status.flagwantup) buffer_putnoflush(buffer_1small, ", want down", 12) ; if (status.flagready) @@ -111,6 +228,76 @@ int main (int argc, char const *const *argv) buffer_putnoflush(buffer_1small, fmt, uint64_fmt(fmt, status.readystamp.sec.x)) ; buffer_putnoflush(buffer_1small, " seconds", 8) ; } +} + +int main (int argc, char const *const *argv) +{ + s6_svstatus_t status ; + int flagnum = 0 ; + prfunc_t_ref fields[MAXFIELDS] ; + unsigned int n = 0 ; + PROG = "s6-svstat" ; + + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "no:uWNrpest", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'n' : flagnum = 1 ; break ; + case 'o' : n = parse_options(l.arg, fields, n) ; break ; + case 'u' : checkfields() ; fields[n++] = &pr_up ; break ; + case 'w' : checkfields() ; fields[n++] = &pr_wantedup ; break ; + case 'N' : checkfields() ; fields[n++] = &pr_normallyup ; break ; + case 'r' : checkfields() ; fields[n++] = &pr_ready ; break ; + case 'p' : checkfields() ; fields[n++] = &pr_pid ; break ; + case 'e' : checkfields() ; fields[n++] = &pr_exitcode ; break ; + case 's' : checkfields() ; fields[n++] = &pr_signal ; break ; + case 't' : checkfields() ; fields[n++] = &pr_upseconds ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (!argc) dieusage() ; + fields[n] = 0 ; + + { + int r = s6_svc_ok(argv[0]) ; + if (r < 0) strerr_diefu2sys(111, "check ", argv[0]) ; + if (!r) strerr_diefu3x(1, "read status for ", argv[0], ": s6-supervise not running") ; + } + if (!s6_svstatus_read(argv[0], &status)) + strerr_diefu2sys(111, "read status for ", argv[0]) ; + + tain_now_g() ; + if (tain_future(&status.stamp)) tain_copynow(&status.stamp) ; + + { + size_t dirlen = strlen(*argv) ; + char fn[dirlen + 6] ; + memcpy(fn, *argv, dirlen) ; + memcpy(fn + dirlen, "/down", 6) ; + if (access(fn, F_OK) < 0) + if (errno != ENOENT) strerr_diefu2sys(111, "access ", fn) ; + else normallyup = 1 ; + else normallyup = 0 ; + } + + if (!n) legacy(&status, flagnum) ; + else + { + unsigned int i = 0 ; + for (; fields[i] ; i++) + { + (*fields[i])(buffer_1small, &status) ; + buffer_putsnoflush(buffer_1small, " ") ; + } + buffer_unput(buffer_1small, 1) ; + } + if (buffer_putflush(buffer_1small, "\n", 1) < 0) strerr_diefu1sys(111, "write to stdout") ; return 0 ; diff --git a/src/supervision/s6_svlisten_loop.c b/src/supervision/s6_svlisten_loop.c @@ -52,7 +52,7 @@ int s6_svlisten_loop (s6_svlisten_t *foo, int wantup, int wantready, int or, tai { int r = iopause_g(x, 1 + (spfd >= 0), deadline) ; if (r < 0) strerr_diefu1sys(111, "iopause") ; - else if (!r) strerr_dief1x(1, "timed out") ; + else if (!r) strerr_dief1x(99, "timed out") ; if (x[1].revents & IOPAUSE_READ) (*handler)() ; if (x[0].revents & IOPAUSE_READ) {