/* ISC license. */ #include #if defined(SKALIBS_HASPRCTL) || defined(SKALIBS_HASPROCCTL) || defined(SKALIBS_HASKEVENT) #include #include #include #include #include #undef NEEDS_KEVENT #if defined(SKALIBS_HASPRCTL) # include # define NEEDS_KEVENT 0 #elif defined(SKALIBS_HASPROCCTL) # include # define NEEDS_KEVENT 0 #elif defined (SKALIBS_HASKEVENT) # include # include # include # include # include # include # include # define NEEDS_KEVENT 1 #endif #include #include #include #include #include #include #include #include #include #include #include #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 iske, 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 || !iske) strerr_diefu1sys(111, "wait") ; else break ; else if (!r) break ; *code = wait_estatus(wstat) ; return 1 ; } default : kill(pid, sig) ; break ; } } 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[1 + NEEDS_KEVENT] = { { .events = IOPAUSE_READ } } ; 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], ¬if)) 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_init() == -1) strerr_diefu1sys(111, "init selfpipe") ; if (!selfpipe_trapset(&full)) strerr_diefu1sys(111, "trap all signals") ; x[0].fd = selfpipe_fd() ; } #if defined(SKALIBS_HASPRCTL) if (prctl(PR_SET_CHILD_SUBREAPER, 1) == -1) strerr_diefu1sys(111, "prctl with PR_SET_CHILD_SUBREAPER") ; #elif defined(SKALIBS_HASPROCCTL) if (procctl(P_PID, 0, PROC_REAP_ACQUIRE, 0) == -1) strerr_diefu1sys(111, "procctl with PROC_REAP_ACQUIRE") ; #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]) ; if (kill(pid, 0) == -1) strerr_diefu1sys(111, "check daemon with a null signal") ; #if NEEDS_KEVENT keventbridge kb = KEVENTBRIDGE_ZERO ; struct kevent ke ; x[1].events = IOPAUSE_READ ; x[1].fd = keventbridge_start(&kb) ; if (x[1].fd == -1) strerr_diefu1sys(111, "keventbridge_start") ; EV_SET(&ke, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0) ; if (keventbridge_write(&kb, &ke, 1) == -1) strerr_diefu1sys(111, "keventbridge_write") ; #endif if (notif) { write(notif, "\n", 1) ; close(notif) ; } for (;;) { int r = iopause_g(x, 1 + NEEDS_KEVENT, 0) ; if (r == -1) strerr_diefu1sys(111, "iopause") ; if (x[0].revents & IOPAUSE_READ) { int code = 0 ; if (handle_signals(pid, NEEDS_KEVENT, &code)) _exit(code) ; } #if NEEDS_KEVENT else if (x[1].revents & IOPAUSE_READ) { int r = keventbridge_read(&kb, &ke) ; if (r == -1) strerr_diefu1sys(111, "keventbridge_read") ; else if (r && (pid_t)ke.ident == pid && ke.filter == EVFILT_PROC && ke.fflags & NOTE_EXIT) _exit(wait_estatus(ke.data)) ; } #endif } } #else #include #include 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