s6-notifyoncheck.c (8017B)
1 /* ISC license. */ 2 3 #include <stdint.h> 4 #include <unistd.h> 5 #include <signal.h> 6 #include <fcntl.h> 7 #include <limits.h> 8 #include <sys/wait.h> 9 10 #include <skalibs/types.h> 11 #include <skalibs/allreadwrite.h> 12 #include <skalibs/bytestr.h> 13 #include <skalibs/sgetopt.h> 14 #include <skalibs/strerr.h> 15 #include <skalibs/tai.h> 16 #include <skalibs/cspawn.h> 17 #include <skalibs/djbunix.h> 18 #include <skalibs/selfpipe.h> 19 #include <skalibs/iopause.h> 20 #include <skalibs/exec.h> 21 22 #include <s6/s6.h> 23 24 #ifdef S6_USE_EXECLINE 25 #include <execline/config.h> 26 #define USAGE "s6-notifyoncheck [ -d ] [ -3 fd ] [ -s initialsleep ] [ -T globaltimeout ] [ -t localtimeout ] [ -w waitingtime ] [ -n tries ] [ -c \"checkprog...\" ] prog..." 27 #define OPTIONS "d3:s:T:t:w:n:c:" 28 #else 29 #define USAGE "s6-notifyoncheck [ -d ] [ -3 fd ] [ -s initialsleep ] [ -T globaltimeout ] [ -t localtimeout ] [ -w waitingtime ] [ -n tries ] prog..." 30 #define OPTIONS "d3:s:T:t:w:n:" 31 #endif 32 33 #define dieusage() strerr_dieusage(100, USAGE) 34 35 36 static inline int read_uint (char const *file, unsigned int *fd) 37 { 38 char buf[UINT_FMT + 1] ; 39 ssize_t r = openreadnclose_nb(file, buf, UINT_FMT) ; 40 if (r == -1) return -1 ; 41 buf[byte_chr(buf, r, '\n')] = 0 ; 42 return !!uint0_scan(buf, fd) ; 43 } 44 45 static inline int handle_signals (pid_t pid, int *w) 46 { 47 int gotit = 0 ; 48 for (;;) 49 { 50 switch (selfpipe_read()) 51 { 52 case -1 : strerr_diefu1sys(111, "selfpipe_read") ; 53 case 0 : return gotit ; 54 case SIGCHLD : 55 { 56 int wstat ; 57 if (wait_pid_nohang(pid, &wstat) == pid) 58 { 59 *w = wstat ; 60 gotit = 1 ; 61 } 62 break ; 63 } 64 } 65 } 66 } 67 68 static int handle_event (ftrigr_t *a, uint16_t id, pid_t pid) 69 { 70 int r ; 71 char what ; 72 if (ftrigr_update(a) < 0) strerr_diefu1sys(111, "ftrigr_update") ; 73 r = ftrigr_check(a, id, &what) ; 74 if (r < 0) strerr_diefu1sys(111, "ftrigr_check") ; 75 if (r && what == 'd') 76 { 77 if (pid) kill(pid, SIGTERM) ; 78 return 1 ; 79 } 80 return 0 ; 81 } 82 83 int main (int argc, char const *const *argv, char const *const *envp) 84 { 85 ftrigr_t a = FTRIGR_ZERO ; 86 iopause_fd x[2] = { { .events = IOPAUSE_READ }, { .events = IOPAUSE_READ } } ; 87 char const *childargv[4] = { "./data/check", 0, 0, 0 } ; 88 #ifdef S6_USE_EXECLINE 89 char const *checkprog = 0 ; 90 #endif 91 unsigned int fd ; 92 int df = 0 ; 93 int autodetect = 1 ; 94 int p[2] ; 95 tain globaldeadline, sleeptto, localtto, waittto ; 96 unsigned int tries = 7 ; 97 uint16_t id ; 98 PROG = "s6-notifyoncheck" ; 99 100 { 101 subgetopt l = SUBGETOPT_ZERO ; 102 unsigned int initialsleep = 10, globaltimeout = 0, localtimeout = 0, waitingtime = 1000 ; 103 for (;;) 104 { 105 int opt = subgetopt_r(argc, argv, OPTIONS, &l) ; 106 if (opt == -1) break ; 107 switch (opt) 108 { 109 case 'd' : df = 1 ; break ; 110 case '3' : if (!uint0_scan(l.arg, &fd)) dieusage() ; autodetect = 0 ; break ; 111 case 's' : if (!uint0_scan(l.arg, &initialsleep)) dieusage() ; break ; 112 case 'T' : if (!uint0_scan(l.arg, &globaltimeout)) dieusage() ; break ; 113 case 't' : if (!uint0_scan(l.arg, &localtimeout)) dieusage() ; break ; 114 case 'w' : if (!uint0_scan(l.arg, &waitingtime)) dieusage() ; break ; 115 case 'n' : if (!uint0_scan(l.arg, &tries)) dieusage() ; break ; 116 #ifdef S6_USE_EXECLINE 117 case 'c' : checkprog = l.arg ; break ; 118 #endif 119 default : dieusage() ; 120 } 121 } 122 argc -= l.ind ; argv += l.ind ; 123 if (!argc) dieusage() ; 124 125 if (!tain_from_millisecs(&sleeptto, initialsleep)) dieusage() ; 126 if (globaltimeout) tain_from_millisecs(&globaldeadline, globaltimeout) ; 127 else globaldeadline = tain_infinite_relative ; 128 if (localtimeout) tain_from_millisecs(&localtto, localtimeout) ; 129 else localtto = tain_infinite_relative ; 130 if (waitingtime) tain_from_millisecs(&waittto, waitingtime) ; 131 else waittto = tain_infinite_relative ; 132 if (!tries) tries = UINT_MAX ; 133 } 134 135 { 136 int r = s6_svc_ok(".") ; 137 if (r < 0) strerr_diefu1sys(111, "sanity-check current service directory") ; 138 if (!r) strerr_dief1x(100, "s6-supervise not running.") ; 139 } 140 #ifdef S6_USE_EXECLINE 141 if (checkprog) 142 { 143 childargv[0] = EXECLINE_EXTBINPREFIX "execlineb" ; 144 childargv[1] = "-Pc" ; 145 childargv[2] = checkprog ; 146 } 147 #endif 148 149 if (autodetect) 150 { 151 int r = read_uint("notification-fd", &fd) ; 152 if (r < 0) strerr_diefu2sys(111, "read ", "./notification-fd") ; 153 if (!r) strerr_dief2x(100, "invalid ", "./notification-fd") ; 154 } 155 if (fcntl(fd, F_GETFD) < 0) 156 strerr_dief2sys(111, "notification-fd", " sanity check failed") ; 157 158 tain_now_set_stopwatch_g() ; 159 tain_add_g(&globaldeadline, &globaldeadline) ; 160 161 162 /* 163 Fork, let the parent exec into the daemon, keep working in the child. 164 165 We want the child to die if the parent dies, because no need to keep 166 polling a dead service. And another child will be spawned next time the 167 service is relaunched by s6-supervise. 168 We could keep a pipe from the parent to the child, for death 169 notification, but that's an additional fd forever open in the parent, 170 which is not good (we need to be 100% transparent). 171 So we're using ftrigr to listen to a 'd' event in the servicedir's 172 fifodir. It's much heavier, but temporary - it doesn't use permanent 173 resources in the daemon - and we're polling anyway, so the user 174 doesn't care about being 100% lightweight. 175 176 We need some voodoo synchronization so ftrigr_start can be started 177 from the child without a race condition. 178 179 */ 180 181 if (pipecoe(p) < 0) strerr_diefu1sys(111, "pipe") ; 182 switch (df ? doublefork() : fork()) 183 { 184 case -1: strerr_diefu1sys(111, df ? "doublefork" : "fork") ; 185 case 0 : break ; 186 default: 187 { 188 char c ; 189 close((int)fd) ; 190 if (read(p[0], &c, 1) < 1) strerr_diefu1x(111, "synchronize with child") ; 191 xexec_e(argv, envp) ; 192 } 193 } 194 195 196 PROG = "s6-notifyoncheck (child)" ; 197 close(p[0]) ; 198 if (!ftrigr_startf_g(&a, &globaldeadline)) 199 strerr_diefu1sys(111, "ftrigr_startf") ; 200 id = ftrigr_subscribe_g(&a, "event", "d", 0, &globaldeadline) ; 201 if (!id) strerr_diefu1sys(111, "ftrigr_subscribe to event fifodir") ; 202 203 x[0].fd = selfpipe_init() ; 204 if (x[0].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ; 205 if (selfpipe_trap(SIGCHLD) < 0) strerr_diefu1sys(111, "trap SIGCHLD") ; 206 x[1].fd = ftrigr_fd(&a) ; 207 208 if (fd_write(p[1], "", 1) < 1) strerr_diefu1sys(2, "synchronize with parent") ; 209 close(p[1]) ; 210 211 /* Loop around a sleep and a ./data/check invocation */ 212 213 while (tries == UINT_MAX || tries--) 214 { 215 tain deadline = globaldeadline ; 216 tain localdeadline ; 217 pid_t pid ; 218 219 tain_add_g(&localdeadline, &sleeptto) ; 220 sleeptto = waittto ; 221 if (tain_less(&localdeadline, &deadline)) deadline = localdeadline ; 222 for (;;) 223 { 224 int r = iopause_g(x+1, 1, &deadline) ; 225 if (r < 0) strerr_diefu1sys(111, "iopause") ; 226 if (!r) 227 { 228 if (!tain_future(&globaldeadline)) return 3 ; 229 else break ; 230 } 231 if (handle_event(&a, id, 0)) return 2 ; 232 } 233 234 pid = cspawn(childargv[0], childargv, envp, CSPAWN_FLAGS_SELFPIPE_FINISH, 0, 0) ; 235 if (!pid) 236 { 237 strerr_warnwu2sys("spawn ", childargv[0]) ; 238 continue ; 239 } 240 deadline = globaldeadline ; 241 tain_add_g(&localdeadline, &localtto) ; 242 if (tain_less(&localdeadline, &deadline)) deadline = localdeadline ; 243 for (;;) 244 { 245 int r = iopause_g(x, 2, &deadline) ; 246 if (r < 0) strerr_diefu1sys(111, "iopause") ; 247 if (!r) 248 { 249 if (!tain_future(&globaldeadline)) 250 { 251 kill(pid, SIGTERM) ; 252 return 3 ; 253 } 254 else break ; 255 } 256 if (x[0].revents & IOPAUSE_READ) 257 { 258 int wstat ; 259 if (handle_signals(pid, &wstat)) 260 { 261 if (WIFEXITED(wstat) && !WEXITSTATUS(wstat)) 262 { 263 fd_write((int)fd, "\n", 1) ; 264 return 0 ; 265 } 266 else break ; 267 } 268 } 269 if (x[1].revents & IOPAUSE_READ && handle_event(&a, id, pid)) return 2 ; 270 } 271 } 272 273 return 1 ; 274 }