aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2025-10-20 23:59:19 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2025-10-20 23:59:19 +0000
commita384be48fd6039ed4ecd48ba98008ecf5c8505a7 (patch)
tree8ae143117e61ef4a43639848217593e727250319
parent6ae8c05d11dd822dd56b70c38c79c1b55ac5c1ee (diff)
downloads6-a384be48fd6039ed4ecd48ba98008ecf5c8505a7.tar.gz
Add s6-background-watch (untested)
-rw-r--r--package/deps.mak3
-rw-r--r--package/modes1
-rw-r--r--package/targets.mak12
-rwxr-xr-xs6-background-watchbin0 -> 34056 bytes
-rw-r--r--src/supervision/deps-exe/s6-background-watch3
-rw-r--r--src/supervision/s6-background-watch.c298
6 files changed, 316 insertions, 1 deletions
diff --git a/package/deps.mak b/package/deps.mak
index 0fee849..bad353e 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -124,6 +124,7 @@ src/pipe-tools/s6-ftrig-listen1.o src/pipe-tools/s6-ftrig-listen1.lo: src/pipe-t
src/pipe-tools/s6-ftrig-notify.o src/pipe-tools/s6-ftrig-notify.lo: src/pipe-tools/s6-ftrig-notify.c src/include/s6/ftrigw.h
src/pipe-tools/s6-ftrig-wait.o src/pipe-tools/s6-ftrig-wait.lo: src/pipe-tools/s6-ftrig-wait.c src/include/s6/ftrigr.h
src/pipe-tools/s6-mkfifodir.o src/pipe-tools/s6-mkfifodir.lo: src/pipe-tools/s6-mkfifodir.c src/include/s6/ftrigw.h
+src/supervision/s6-background-watch.o src/supervision/s6-background-watch.lo: src/supervision/s6-background-watch.c
src/supervision/s6-notifyoncheck.o src/supervision/s6-notifyoncheck.lo: src/supervision/s6-notifyoncheck.c src/include/s6/s6.h
src/supervision/s6-permafailon.o src/supervision/s6-permafailon.lo: src/supervision/s6-permafailon.c src/include/s6/supervise.h
src/supervision/s6-supervise.o src/supervision/s6-supervise.lo: src/supervision/s6-supervise.c src/include/s6/config.h src/include/s6/ftrigw.h src/include/s6/supervise.h
@@ -262,6 +263,8 @@ s6-ftrig-wait: EXTRA_LIBS := ${SOCKET_LIB} ${SYSCLOCK_LIB} ${SPAWN_LIB}
s6-ftrig-wait: src/pipe-tools/s6-ftrig-wait.o ${LIBS6} -lskarnet
s6-mkfifodir: EXTRA_LIBS :=
s6-mkfifodir: src/pipe-tools/s6-mkfifodir.o ${LIBS6} -lskarnet
+s6-background-watch: EXTRA_LIBS := ${SPAWN_LIB} ${KEVENTPTHREAD_LIB}
+s6-background-watch: src/supervision/s6-background-watch.o -lskarnet
s6-notifyoncheck: EXTRA_LIBS := ${SOCKET_LIB} ${SYSCLOCK_LIB} ${SPAWN_LIB}
s6-notifyoncheck: src/supervision/s6-notifyoncheck.o ${LIBS6} -lskarnet
s6-permafailon: EXTRA_LIBS := ${SYSCLOCK_LIB}
diff --git a/package/modes b/package/modes
index 837f0a2..dc5b2c3 100644
--- a/package/modes
+++ b/package/modes
@@ -19,6 +19,7 @@ s6-svdt-clear 0755
s6-svwait 0755
s6-svlisten1 0755
s6-svlisten 0755
+s6-background-watch 0755
s6-svperms 0755
s6-notifyoncheck 0755
s6-permafailon 0755
diff --git a/package/targets.mak b/package/targets.mak
index dbe0b68..30900d6 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -60,11 +60,21 @@ s6-instance-create \
s6-instance-delete \
s6-instance-control \
s6-instance-status \
-s6-instance-list
+s6-instance-list \
+
+LIBEXEC_TARGETS := \
+s6-background-watch \
LIB_DEFS := S6=s6
S6_DESCRIPTION :=
+$(shell grep -qFx "kevent: yes" $(sysdeps)/sysdeps)
+ifeq ($(.SHELLSTATUS),0)
+KEVENTPTHREAD_LIB := $(PTHREAD_LIB)
+else
+KEVENTPTHREAD_LIB :=
+endif
+
ifneq ($(EXECLINE_LIB),)
LIB_DEFS += S6AUTO=s6auto
S6AUTO_DESCRIPTION := The s6auto library (C helpers to create service directories)
diff --git a/s6-background-watch b/s6-background-watch
new file mode 100755
index 0000000..4fa40e2
--- /dev/null
+++ b/s6-background-watch
Binary files differ
diff --git a/src/supervision/deps-exe/s6-background-watch b/src/supervision/deps-exe/s6-background-watch
new file mode 100644
index 0000000..31dec83
--- /dev/null
+++ b/src/supervision/deps-exe/s6-background-watch
@@ -0,0 +1,3 @@
+-lskarnet
+${SPAWN_LIB}
+${KEVENTPTHREAD_LIB}
diff --git a/src/supervision/s6-background-watch.c b/src/supervision/s6-background-watch.c
new file mode 100644
index 0000000..cfb1fb7
--- /dev/null
+++ b/src/supervision/s6-background-watch.c
@@ -0,0 +1,298 @@
+/* ISC license. */
+
+#include <skalibs/sysdeps.h>
+
+#if defined(SKALIBS_HASPRSETCHILDSUBREAPER) || defined(SKALIBS_HASKEVENT)
+
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <errno.h>
+#ifdef SKALIBS_HASPRSETCHILDSUBREAPER
+# include <sys/prctl.h>
+#else
+# include <sys/types.h>
+# include <sys/event.h>
+# include <sys/time.h>
+# include <pthread.h>
+# include <fcntl.h>
+# include <limits.h>
+#endif
+
+#include <skalibs/posixplz.h>
+#include <skalibs/types.h>
+#include <skalibs/prog.h>
+#include <skalibs/gol.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/iopause.h>
+#include <skalibs/cspawn.h>
+#include <skalibs/djbunix.h>
+
+
+ /* portable parts */
+
+#define USAGE "s6-background-watch [ -t timeout ] [ -d notif ] pidfile prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static int handle_signals_early (pid_t pid, char const *pidfile, char const *prog)
+{
+ int sig ;
+ for (;;) switch (sig = selfpipe_read())
+ {
+ case -1 : strerr_diefu1sys(111, "read selfpipe") ;
+ case 0 : return 0 ;
+ case SIGCHLD :
+ {
+ int wstat ;
+ pid_t r = wait_pid_nohang(pid, &wstat) ;
+ if (r == -1) strerr_diefu1sys(111, "wait") ;
+ if (!r) break ;
+ if (WIFEXITED(wstat))
+ {
+ if (WEXITSTATUS(wstat))
+ {
+ char fmt[UINT_FMT] ;
+ fmt[uint_fmt(fmt, WEXITSTATUS(wstat))] = 0 ;
+ unlink_void(pidfile) ;
+ strerr_dief3x(wait_estatus(wstat), prog, " (parent) exited ", fmt) ;
+ }
+ else return 1 ;
+ }
+ else
+ {
+ char fmt[UINT_FMT] ;
+ fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
+ unlink_void(pidfile) ;
+ strerr_dief3x(wait_estatus(wstat), prog, " (parent) crashed with signal ", fmt) ;
+ }
+ break ;
+ }
+ default :
+ kill(pid, sig) ;
+ break ;
+ }
+}
+
+static inline int read_file (char const *file, char *buf, size_t n)
+{
+ ssize_t r = openreadnclose_nb(file, buf, n) ;
+ if (r == -1)
+ {
+ if (errno != ENOENT) strerr_diefu2sys(111, "open ", file) ;
+ return 0 ;
+ }
+ buf[byte_chr(buf, r, '\n')] = 0 ;
+ return 1 ;
+}
+
+static inline pid_t get_pid_from_pidfile (char const *pidfile, char const *prog)
+{ /* for now, assume the pidfile is there once the parent has exited */
+ pid_t pid ;
+ char buf[128] ;
+ if (!read_file(pidfile, buf, 128))
+ strerr_diefu4sys(104, prog, " (parent) exited but pidfile ", pidfile, " does not exist") ;
+ if (!pid0_scan(buf, &pid))
+ strerr_dief3x(104, "pidfile ", pidfile, " does not contain a valid pid") ;
+ return pid ;
+}
+
+static inline int handle_signals (pid_t pid, int issr, int *code)
+{
+ int sig ;
+ for (;;) switch (sig = selfpipe_read())
+ {
+ case -1 : strerr_diefu1sys(111, "read selfpipe") ;
+ case 0 : return 0 ;
+ case SIGCHLD :
+ {
+ int wstat ;
+ pid_t r = wait_pid_nohang(pid, &wstat) ;
+ if (r == -1)
+ if (errno != ECHILD || issr) strerr_diefu1sys(111, "wait") ;
+ else break ;
+ else if (!r) break ;
+ *code = wait_estatus(wstat) ;
+ return 1 ;
+ }
+ default :
+ kill(pid, sig) ;
+ break ;
+ }
+}
+
+
+/* BSD part. kevent is a unique snowflake in the shape of a dildo */
+
+#ifdef SKALIBS_HASKEVENT
+
+struct thinfo_s
+{
+ int kq ;
+ int fdw ;
+ pid_t pid ;
+} ;
+
+static void *keventwait (void *arg)
+{
+ struct thinfo_s *t = arg ;
+ struct kevent ke ;
+ for (;;)
+ {
+ int r = kevent(t->kq, 0, 0, &ke, 1, 0) ;
+ if (r == -1 && errno != EINTR) strerr_diefu2sys(111, "read ", "kevent") ;
+ else if (r && (pid_t)ke.ident == t->pid && ke.filter == EVFILT_PROC && ke.fflags & NOTE_EXIT) break ;
+ }
+ close(t->fdw) ;
+ return (void *)(intptr_t)ke.data ;
+}
+
+static void keventstart (pid_t pid, int *fd, pthread_t *th)
+{
+ struct thinfo_s t ;
+ int p[2] ;
+ pthread_attr_t attr ;
+ struct kevent ke ;
+ int e ;
+
+ t.kq = kqueue() ;
+ if (t.kq == -1) strerr_diefu1sys(111, "kqueue") ;
+ EV_SET(&ke, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0) ;
+ if (kevent(t.kq, &ke, 1, 0, 0, 0) == -1) strerr_diefu1sys(111, "register kevent") ;
+ if (pipe(p) == -1) strerr_diefu2sys(111, "pipe") ;
+ if (coe(p[0])
+ t.fdw = p[1] ;
+ t.pid = pid ;
+ e = pthread_attr_init(&attr) ;
+ if (e) { errno = e ; strerr_diefu1sys(111, "pthread_attr_init") ; }
+ pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN) ;
+ e = pthread_create(th, &attr, &keventwait, &t) ;
+ if (e) { errno = e ; strerr_diefu1sys(111, "pthread_create") ; }
+ *fd = p[0] ;
+}
+
+static int getexitcode (pthread_t th)
+{
+ void *ptr ;
+ int e = pthread_join(th, &ptr) ;
+ if (e) { errno = e ; strerr_diefu1sys(111, "pthread_join") ; }
+ return (int)(intptr_t)ptr ;
+}
+
+#endif
+
+
+ /* main */
+
+enum rgola_e
+{
+ GOLA_TIMEOUT,
+ GOLA_NOTIF,
+ GOLA_N
+} ;
+
+static gol_arg const rgola[GOLA_N] =
+{
+ { .so = 't', .lo = "timeout-ready", .i = GOLA_TIMEOUT },
+ { .so = 'd', .lo = "notification-fd", .i = GOLA_NOTIF }
+} ;
+
+int main (int argc, char const *const *argv)
+{
+ iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .events = IOPAUSE_READ, .revents = 0 } } ;
+ tain tto = TAIN_INFINITE_RELATIVE ;
+ tain deadline ;
+ unsigned int notif = 0 ;
+ pid_t pid ;
+
+ char const *wgola[GOLA_N] = { 0 } ;
+ unsigned int golc ;
+ PROG = "s6-background-watch" ;
+ golc = gol_main(argc, argv, 0, 0, rgola, GOLA_N, 0, wgola) ;
+ argc -= golc ; argv += golc ;
+ if (argc < 2) dieusage() ;
+ if (wgola[GOLA_TIMEOUT])
+ {
+ unsigned int t ;
+ if (!uint0_scan(wgola[GOLA_TIMEOUT], &t)) dieusage() ;
+ if (!tain_from_millisecs(&tto, t)) dieusage() ;
+ }
+ if (wgola[GOLA_NOTIF])
+ {
+ if (!uint0_scan(wgola[GOLA_NOTIF], &notif)) dieusage() ;
+ if (notif < 3) strerr_dief2x(100, "notification-fd", " cannot be 0, 1 or 2") ;
+ if (fcntl(notif, F_GETFD) == -1) strerr_diefu2sys(111, "check ", "notification-fd") ;
+ if (coe(notif) == -1) strerr_diefu1sys(111, "coe notification-fd") ;
+ }
+
+ {
+ sigset_t full ;
+ sigfillset(&full) ;
+ if (!selfpipe_trapset(&full))
+ strerr_diefu1sys(111, "trap all signals") ;
+ x[0].fd = selfpipe_fd() ;
+ }
+
+#ifdef SKALIBS_HASPRSETCHILDSUBREAPER
+ if (prctl(PR_SET_CHILD_SUBREAPER, 1) == -1)
+ strerr_diefu1sys(111, "prctl to become a subreaper") ;
+ x[1].fd = -2 ;
+#endif
+
+ pid = cspawn(argv[1], argv + 1, (char const *const *)environ, CSPAWN_FLAGS_SELFPIPE_FINISH, 0, 0) ;
+ if (!pid) strerr_diefu2sys(errno == ENOEXEC ? 126 : 127, "spawn ", argv[1]) ;
+
+ tain_now_set_stopwatch_g() ;
+ tain_add_g(&deadline, &tto) ;
+ for (;;)
+ {
+ int r = iopause_g(x, 1, &deadline) ;
+ if (r == -1) strerr_diefu1sys(111, "iopause") ;
+ if (!r)
+ {
+ strerr_warnw2x(argv[1], " (parent) did not exit before timeout-ready, killing it") ;
+ kill(pid, SIGKILL) ;
+ }
+ else if (handle_signals_early(pid, argv[0], argv[1])) break ;
+ }
+
+ pid = get_pid_from_pidfile(argv[0], argv[1]) ;
+#ifdef SKALIBS_HASKEVENT
+ pthread_t th ;
+ keventstart(pid, &x[1].fd, &th) ;
+#endif
+ if (notif)
+ {
+ write(notif, "\n", 1) ;
+ close(notif) ;
+ }
+
+ for (;;)
+ {
+ int r = iopause_g(x, 1 + (x[1].fd >= 0), 0) ;
+ if (r == -1) strerr_diefu1sys(111, "iopause") ;
+ if (x[0].revents & IOPAUSE_READ)
+ {
+ int code = 0 ;
+ if (handle_signals(pid, x[1].fd == -2, &code)) _exit(code) ;
+ }
+#ifdef SKALIBS_KEVENT
+ else if (x[1].revents & IOPAUSE_READ) _exit(getexitcode(th)) ;
+#endif
+ }
+}
+
+#else
+
+#include <skalibs/prog.h>
+#include <skalibs/strerr.h>
+
+int main (int argc, char const *const *argv)
+{
+ PROG = "s6-background-watch" ;
+ strerr_dief1x(112, "system does not meet the requirements to watch auto-backgrounding daemons") ;
+}
+
+#endif