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