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:
| M | README | | | 8 | ++++---- |
| M | serva.c | | | 100 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
| M | svc.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);