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