chiark / gitweb /
prefork-interp: kill and fresh modes
[chiark-utils.git] / cprogs / prefork-interp.c
index 305314a8c285e9c6669bff67b37656a7c279b3a9..918083e321af6b48f69b7a5c2ccc5e46c5b54e4b 100644 (file)
  *         ident covers only env vars specified  with -E
  *         ident covers only arguments interpreter and (if present) script
  */
+
 /*
- * Process structure:
- *  client (C wrapper)        connects to server
- *                              (including reading ack byte)
- *                            if fails or garbage
- *                            === acquires lock ===
- *                            makes new listening socket
- *                            makes watcher pipes
- *                            forks watcher and awaits
- *                            makes first-instance socketpair
- *                            forks setup (script, sock fds indicated in env)
- *                            fd0, fd1, fd2: from-outer
- *                            other fd: call(client-end)(fake)
- *                            reaps setup (and reports error)
- *                            (implicitly releases lock)
- *
- *     watcher                fd[012]: watcher pipes
- *                            starts watch on socket path
- *                            sets stderr to line buffered
- *                            sets stdin to nonblocking
- *                            daemonises (one fork, becomes session leader)
- *                            when socket stat changes, quit
- *
- *     setup (pre-exec)       fd0: null,
- *                            fd[12]: fd2-from-outer
- *                            env fds: listener, call(server-end)(fake),
- *                                      watcher read, watcher write
- *                            close fd: lockfile
- *                            possibly clean env, argv
- *
- *     setup (script)         runs initialisation parts of the script
- *                            at prefork establishment point:
- *     setup (pm) [1]         opens syslog
- *                            forks for server
- *                [2]         exits
- *
- *        server (pm) [1]     [fd0: null],
- *                            [fd[12]: fd2-from-outer]
- *                            setsid
- *                            right away, forks init monitor
- *                    [2]     closes outer caller fds and call(fake)
- *        [server (pm)]       fd[012]: null
- *                            other fds: listener, syslog
- *                            runs in loop accepting and forking,
- *                            reaping and limiting children (incl init monitor)
- *                            reports failures of monitors to syslog
- *                            
- *  [client (C wrapper)]      if client connect succeeds:
- *                            now fd: call(client-end)
- *                               sends message with: cmdline, env
- *                               sends fds
- *
- *        [server (script)]   accepts, forks subseq monitor
- *
- *          monitor [1]       [fd0: null]
- *           (init            [fd[12]: init: fd2-from-outer; subseq: null]
- *             or             errors: init: fd2; subseq: syslog
- *            subseq)         other fds: syslog, call(server-end)
- *                            sends ack byte
- *                            receives args, env, fds
- *                            forks executor
- *
- *            executor        sorts out fds:
- *                            fd0, fd1, fd2: from-outer
- *                            close fds: call(server-end)
- *                            retained fds: syslog
- *
- *                            sets cmdline, env
- *                            runs main part of script
- *                            exits normally
- *
- *          [monitor]         [fd[012]: null]
- *                            [fd[12]: init: fd2-from-outer; subseq: null]
- *                            [errors: init: fd2; subseq: syslog]
- *                            reaps executor
- *                            reports status via socket
- *
- *    [client (C wrapper)]    [fd0, fd1, fd2: from-outer]
- *                            [other fd: call(client-end)]
- *                            receives status, exits appropriately
- *                            (if was bad signal, reports to stderr, exits 127)
- */
+***************************************************************************
+\f
+  State during service execution, process parentage and key fds
+
+      CALLER
+        ||
+        ||
+        ||                               listen     watch-err/in
+        ||       call                 (accept) \     ,------2
+        || ,-----------------------------.     SERVER -----0 WATCHER(C)
+      CLIENT 2--=fdpassed>=---------.     \      || &&          |      &&
+       (C)  1--=fdpassed>=---------. \     \     ||           inotify
+           0--=fdpassed>=---------. \ \     \    ||           sockpath
+                                   \ \ \     \   ||
+                                   | | |\     |  ||
+                                   | | | \    |  ||
+                                   | \ |  \   \  ||
+                                    \ \ \  \   MONITOR &
+                                     \ \ \  `12  ||  |
+                                      \ \ \      ||  |
+                                       \ \ \     ||  |execterm
+                                        \ \ \    ||  |
+                                         \ \ \   ||  |
+                                          \ \ 2  ||  |
+                                           \ 1 EXECUTOR
+                                            0
+    ----      pipes, sockets
+    012       descriptors
+    -==-      fds shared
+    ||        process parentage
+    &&        session leader (daemon)
+    &         process group leader
+
+***************************************************************************
+\f
+ Control flow and causality
+
+      CALLER
+         |
+         |fork/exec
+         |
+      CLIENT
+         |
+      attempt to connect, and read greeting
+         |failure?                \success?
+         |                         \
+      tidy up stale /run entries    *1 (continue from send_fds, below)
+      acquire lock
+         |
+      retry attempt to connect, and read greeting
+         |failure?                \success?
+         |                         \
+      create listening socket     release lock
+         |                           \
+      fork/daemonise                  *1
+         |    `------------------.
+         |                      WATCHER(C) &&
+         |
+       make "fake" initial call socketpair                               (C)
+         |                                                    prefork-interp
+       fork/exec   #########################################################
+         |      `-------------.                                  application
+         |         #        SCRIPT (setup)
+         |         #          |
+         |         #       script initialisation
+         |         #          |                                  application
+         |         #  ########|#############################################
+         |         #          |                               prefork-interp
+         |         #       identify fds from envirnment               (Perl)
+         |         #       open syslog
+         |         #          |
+         |         #       dzemonize
+         |   ,.....<....../   |
+      waitpid      #        fork for initial service
+         |         #          |child?       |parent?
+         |         #          |             |
+         |         #          |         SCRIPT [server] &&
+         |         #          |             |
+         |         #          |         ** accept / event loop **
+         |         #          |        accepted?    \      \ \
+         |         #          |            /         \ watch\ \idle
+         |         #          |        fork child     \stderr\ \timeout?
+         |         #          | _________/            |       | |
+         |         #          |/                      |read?  | |
+         |         #     SCRIPT [monitor]             |   eof?| |
+         |         #       setpgrpt &                 |       | |
+         |         #          |                     log msg   | |
+       read   ,....<.....send greeting                |       | |
+      greeting     #          |                    ___________________
+         |         #          |
+      release      #          |
+      lock    *1   #          |
+         |   /     #          |
+      send fds.....>....      |
+         |         #    \receive fds
+         |         #             |
+         |         #         fork for executor                        (Perl)
+         |         #          |parent?        \child?         prefork-interp
+         |         #          |          ######\############################
+         |         #          |          #  SCRIPT (executor)    application
+         |         #          |          #  execute service
+         |         #    wait for read    #       |
+         |         #      (select)       #   terminates
+         |         #        |   |        #       |
+         |         #            |        #    kernel closes execterm
+         |         #            | ,......<....../|
+         |         #      execterm?      #       |
+         |         #            |        #     zombie
+         |         #        |   | ,......<...../
+         |         #       waitpid       #  _______________
+         |         #          |          #
+         |    ,....<....,..send status   #
+    read status    #  ________________   #
+   _____________   #
+
+
+  ********** Or, if client is killed **********
+
+         |         #          |          #  execute service
+     terminates    #    wait for read    #       |
+         |         #      (select)       #       |
+      kernel       #        |   |        #       |
+     closes call   #        |            #       |
+                \..>......_ |            #       |
+   _____________   #       \|call?       #       |
+                   #        |            #       |
+                   #  kill whole pgrp... #    killled
+                   #        |            #     zombie
+                   #        |   | ,......<....../
+                   #       waitpid       #  _______________
+                   #          |          #
+                   #   send exit status  #
+                   #  _____SIGPIPE______ #
+
+    | - \ /    process control flow
+    ... < >    causes mediated by fds or other IPC etc.
+    &&         session leader (daemon)
+    &          process group leader
+    #          language/implementation boundary
+    *1         line continued elsewhere
+    event?     condition
+    ______     process termination (after reaping, if shown)
+
+***************************************************************************
+\f
+  Sequence of events and fd pluming.
+  NB INCOMPLETE - does not cover execterm, cleanup
+   client (C wrapper)        connects to server
+                               (including reading ack byte)
+                             if fails or garbage
+                             === acquires lock ===
+                             makes new listening socket
+                             makes watcher pipes
+                             forks watcher and awaits
+                             makes first-instance socketpair
+                             forks setup (script, sock fds indicated in env)
+                             fd0, fd1, fd2: from-outer
+                             other fd: call(client-end)(fake)
+                             reaps setup (and reports error)
+                             (implicitly releases lock)
+      watcher                fd[012]: watcher pipes
+                             starts watch on socket path
+                             sets stderr to line buffered
+                             sets stdin to nonblocking
+                             daemonises (one fork, becomes session leader)
+                             when socket stat changes, quit
+      setup (pre-exec)       fd0: null,
+                             fd[12]: fd2-from-outer
+                             env fds: listener, call(server-end)(fake),
+                                       watcher read, watcher write
+                             close fd: lockfile
+                             possibly clean env, argv
+      setup (script)         runs initialisation parts of the script
+                             at prefork establishment point:
+      setup (pm) [1]         opens syslog
+                             forks for server
+                 [2]         exits
+         server (pm) [1]     [fd0: null],
+                             [fd[12]: fd2-from-outer]
+                             setsid
+                             right away, forks init monitor
+                     [2]     closes outer caller fds and call(fake)
+         [server (pm)]       fd[012]: null
+                             other fds: listener, syslog
+                             runs in loop accepting and forking,
+                             reaping and limiting children (incl init monitor)
+                             reports failures of monitors to syslog
+   [client (C wrapper)]      if client connect succeeds:
+                             now fd: call(client-end)
+                                sends message with: cmdline, env
+                                sends fds
+         [server (script)]   accepts, forks subseq monitor
+           monitor [1]       [fd0: null]
+            (init            [fd[12]: init: fd2-from-outer; subseq: null]
+              or             errors: init: fd2; subseq: syslog
+             subseq)         other fds: syslog, call(server-end)
+                             sends ack byte
+                             receives args, env, fds
+                             forks executor
+             executor        sorts out fds:
+                             fd0, fd1, fd2: from-outer
+                             close fds: call(server-end)
+                             retained fds: syslog
+                             sets cmdline, env
+                             runs main part of script
+                             exits normally
+           [monitor]         [fd[012]: null]
+                             [fd[12]: init: fd2-from-outer; subseq: null]
+                             [errors: init: fd2; subseq: syslog]
+                             reaps executor
+                             reports status via socket
+     [client (C wrapper)]    [fd0, fd1, fd2: from-outer]
+                             [other fd: call(client-end)]
+                             receives status, exits appropriately
+                             (if was bad signal, reports to stderr, exits 127)
+
+***************************************************************************
+\f
+*/
 
 #include <arpa/inet.h>
 
@@ -118,13 +269,20 @@ void fusagemessage(FILE *f) {
 }
 
 static int laundering;
+static int mode;
 static int max_sockets = 100; // maximum entries in the run dir is 2x this
 
 static struct stat initial_stab;
 
+#define MODE_NORMAL 0
+#define MODE_KILL   'k'
+#define MODE_FRESH  'f'
+
 const struct cmdinfo cmdinfos[]= {
   PREFORK_CMDINFOS
-  { 0, 'U',   0,                    .iassignto= &laundering,    .arg= 'U' },
+  { 0,         'U',   0,    .iassignto= &laundering,    .arg= 'U'         },
+  { "kill",     0,    0,    .iassignto= &mode,          .arg= MODE_KILL   },
+  { 0,         'f',   0,    .iassignto= &mode,          .arg= MODE_FRESH  },
   { 0 }
 };
 
@@ -156,7 +314,7 @@ static void propagate_exit_status(int status, const char *what) {
       r = sigaction(sig, &sa, 0);
       if (r) diee("failed to reset signal handler while propagating %s",
                  signame);
-      
+
       sigset_t sset;
       sigemptyset(&sset);
       sigaddset(&sset, sig);
@@ -261,7 +419,7 @@ static void preclean(void) {
       int lock_fd = flock_file(l_path);
       // Recheck atime - we might have raced!
       time_t atime = preclean_stat_atime(s_path);
-      if (atime == p->atime) {
+      if (atime != p->atime) {
        // Raced.  This will leave use deleting too few things.  Whatever.
       } else {
        int r= unlink(s_path);
@@ -297,7 +455,7 @@ static void prepare_data(size_t *len, char **buf,
     *buf += dl;
   }
 }
-  
+
 static void prepare_length(size_t *len, char **buf, size_t dl_sz) {
   if (dl_sz > UINT32_MAX) die_data_overflow();
   uint32_t dl = htonl(dl_sz);
@@ -448,6 +606,8 @@ static FILE *connect_existing(void) {
   int r;
   int fd = -1;
 
+  if (mode != MODE_NORMAL) return 0;
+
   fd = socket(AF_UNIX, SOCK_STREAM, 0);
   if (fd==-1) diee("socket() for client");
 
@@ -475,7 +635,7 @@ static FILE *connect_existing(void) {
 static void watcher_cb_stdin(uv_poll_t *handle, int status, int events) {
   char c;
   int r;
-  
+
   if ((errno = -status)) diee("watcher: poll stdin");
   for (;;) {
     r= read(0, &c, 1);
@@ -546,8 +706,9 @@ void become_watcher(void) {
 }
 
 static __attribute__((noreturn))
-void become_setup(int sfd, int fake_pair[2],
+void become_setup(int sfd, int lockfd, int fake_pair[2],
                  int watcher_stdin, int watcher_stderr) {
+  close(lockfd);
   close(fake_pair[0]);
   int call_fd = fake_pair[1];
 
@@ -581,6 +742,16 @@ static void connect_or_spawn(void) {
   preclean();
 
   int lockfd = acquire_lock();
+
+  if (mode == MODE_KILL) {
+    r= unlink(socket_path);
+    if (r && errno != ENOENT) diee("remove socket %s", socket_path);
+
+    r= unlink(lock_path);
+    if (r) diee("rmeove lock %s", lock_path);
+    _exit(0);
+  }
+
   call_sock = connect_existing();
   if (call_sock) { close(lockfd); return; }
 
@@ -641,7 +812,7 @@ static void connect_or_spawn(void) {
 
   pid_t setup_pid = fork();
   if (setup_pid == (pid_t)-1) diee("fork for spawn setup");
-  if (!setup_pid) become_setup(sfd, fake_pair,
+  if (!setup_pid) become_setup(sfd, lockfd, fake_pair,
                               watcher_stdin[1], watcher_stderr[0]);
   close(fake_pair[1]);
   close(sfd);
@@ -685,7 +856,7 @@ static void make_executor_argv(const char *const *argv) {
 
   EACH_NEW_ARG( *out++ = arg; );
   *out++ = 0;
-}  
+}
 
 int main(int argc_unused, const char *const *argv) {
   process_opts(&argv);