s6

Mirror/fork of https://skarnet.org/software/s6/
git clone https://ccx.te2000.cz/git/s6
Log | Files | Refs | README | LICENSE

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 }