rats

RAdix iniT Suite
git clone https://git.daat.foo/repos/rats.git
Log | Files | Refs | README | LICENSE

commit e08f498d65f64eb6e7701fe318c8a55580ab22c6
parent 6880d2a9d9a124d4d22ebc0ac0f428785cee3101
Author: cowmonk <cowmonk@based.pt>
Date:   Mon, 24 Aug 2026 14:17:45 -0700

serva/svc per service status

serva is able to output per-service status.

TODO: finish test suite for serva/svc

Diffstat:
MREADME | 8++++----
Mserva.c | 100++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msvc.c | 10+++++++---
3 files changed, 80 insertions(+), 38 deletions(-)

diff --git a/README b/README @@ -108,7 +108,7 @@ Talks to /run/serva.sock over a Unix socket. usage: svc [-udrkts] [-a] [name...] -u bring up -d bring down -r restart - -k kill (SIGKILL) -t terminate -s status + -k kill (SIGKILL) -t terminate -s status (optionally per-service) -a all services (with -u or -d) slog: simple logging daemon. @@ -226,9 +226,9 @@ manpages - someone else needs to do this or I'm going to die learning troff serva -- primarily with handling errrors coming from the services itself, | or at least returning ERR when that's the case. | - |- additionally, would be nice to return log/better status from - using svc when requesting the status of per service (e.g. - svc -s <svc_name>) + |- svc -s now reports per-service run count, uptime, and last + exit status/signal; svc -s <name> focuses a single service. + Streaming service logs through svc is still open. user - still thinking on this one, I think it would be interesting to have something like dinit. It might be an overreach for this project but it can be used for "bloated" setups where users need dbus-session, diff --git a/serva.c b/serva.c @@ -23,8 +23,12 @@ #include <time.h> #include <unistd.h> +#ifndef SVC_DIR #define SVC_DIR "/etc/ssv" +#endif +#ifndef SOCK_PATH #define SOCK_PATH "/run/serva.sock" +#endif #define MAX_SVCS 256 #define MAX_BACKOFF 60 #define MAX_DEPS 16 @@ -41,6 +45,9 @@ struct Service { time_t last_start; time_t next_restart; int backoff; + unsigned long nruns; + int last_exit; + int last_sig; int want_up; int noreset; int once; @@ -329,6 +336,8 @@ start_service(struct Service *s) } s->last_start = time(NULL); + s->last_exit = -1; + s->last_sig = 0; /* fork service process */ pid = fork(); @@ -367,6 +376,7 @@ start_service(struct Service *s) eprintf("exec %s:", runpath); } s->pid = pid; + s->nruns++; /* fork logger process if pipe was created */ if (s->pipefd[0] >= 0) { @@ -495,7 +505,7 @@ stop_cascade(void) } static void -handle_child(pid_t pid) +handle_child(pid_t pid, int status) { size_t i; time_t now; @@ -506,6 +516,10 @@ handle_child(pid_t pid) if (s->pid == pid) { s->pid = 0; + if (WIFEXITED(status)) + s->last_exit = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + s->last_sig = WTERMSIG(status); /* kill logger too - pipe is broken */ if (s->logpid > 0) { kill(s->logpid, SIGTERM); @@ -544,9 +558,10 @@ static void reap_all(void) { pid_t pid; + int status; - while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) - handle_child(pid); + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) + handle_child(pid, status); } /* check_restarts: start services that are ready (deps met, backoff @@ -622,14 +637,51 @@ status_str(struct Service *s) return "DOWN"; } +/* format_status: append one status line for s to resp */ +static size_t +format_status(struct Service *s, char *resp, size_t size, size_t off) +{ + char full[2 * NAME_MAX + 2]; + size_t j; + + snprintf(full, sizeof(full), "%s/%s", s->stage, s->name); + off += snprintf(resp + off, size - off, + "%-32s %s%s pid=%d runs=%lu uptime=%lds", + full, + status_str(s), + s->noreset ? " (noreset)" : "", + s->pid, + s->nruns, + s->pid > 0 ? (long)(time(NULL) - s->last_start) : 0L); + if (s->last_exit >= 0) + off += snprintf(resp + off, size - off, + " exit=%d", s->last_exit); + if (s->last_sig > 0) + off += snprintf(resp + off, size - off, + " sig=%d", s->last_sig); + if (s->nneed > 0) { + off += snprintf(resp + off, size - off, " need="); + for (j = 0; j < s->nneed; j++) + off += snprintf(resp + off, size - off, "%s%s", + j ? "," : "", s->need[j]); + } + if (s->nafter > 0) { + off += snprintf(resp + off, size - off, " after="); + for (j = 0; j < s->nafter; j++) + off += snprintf(resp + off, size - off, "%s%s", + j ? "," : "", s->after[j]); + } + off += snprintf(resp + off, size - off, "\n"); + return off; +} + /* handle_conn: process one control connection */ static void handle_conn(int conn) { char buf[4096], resp[8192], cmd, *name, *p; - char full[2 * NAME_MAX + 2]; ssize_t n; - size_t i, j, off; + size_t i, off; struct Service *s; n = read(conn, buf, sizeof(buf) - 1); @@ -691,6 +743,7 @@ handle_conn(int conn) off = snprintf(resp, sizeof(resp), "OK: %s down\n", name); } + stop_cascade(); break; case 'r': s = find_service(name); @@ -729,36 +782,21 @@ handle_conn(int conn) break; case 's': off = 0; + if (name[0]) { + s = find_service(name); + if (!s) { + off = snprintf(resp, sizeof(resp), + "ERR: no such service: %s\n", name); + break; + } + off = format_status(s, resp, sizeof(resp), 0); + break; + } for (i = 0; i < nsvcs; i++) { s = &svcs[i]; if (off + 128 >= sizeof(resp)) break; - snprintf(full, sizeof(full), "%s/%s", - s->stage, s->name); - off += snprintf(resp + off, sizeof(resp) - off, - "%-32s %s%s pid=%d", - full, - status_str(s), - s->noreset ? " (noreset)" : "", - s->pid); - if (s->nneed > 0) { - off += snprintf(resp + off, - sizeof(resp) - off, " need="); - for (j = 0; j < s->nneed; j++) - off += snprintf(resp + off, - sizeof(resp) - off, "%s%s", - j ? "," : "", s->need[j]); - } - if (s->nafter > 0) { - off += snprintf(resp + off, - sizeof(resp) - off, " after="); - for (j = 0; j < s->nafter; j++) - off += snprintf(resp + off, - sizeof(resp) - off, "%s%s", - j ? "," : "", s->after[j]); - } - off += snprintf(resp + off, sizeof(resp) - off, - "\n"); + off = format_status(s, resp, sizeof(resp), off); } break; default: diff --git a/svc.c b/svc.c @@ -9,6 +9,10 @@ #include <string.h> #include <unistd.h> +#ifndef SOCK_PATH +#define SOCK_PATH "/run/serva.sock" +#endif + static void usage(void) { @@ -48,12 +52,12 @@ main(int argc, char *argv[]) eprintf("socket:"); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; - strlcpy(addr.sun_path, "/run/serva.sock", sizeof(addr.sun_path)); + strlcpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path)); if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) - eprintf("connect /run/serva.sock:"); + eprintf("connect %s:", SOCK_PATH); /* build command line */ - if (action == 's') { + if (action == 's' && argc == 0) { strlcpy(buf, "s\n", sizeof(buf)); } else if (aflag) { snprintf(buf, sizeof(buf), "%c a\n", action);