chiark / gitweb /
prefork-interp: protocol documentation
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 21 Aug 2022 12:42:11 +0000 (13:42 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 21 Aug 2022 20:21:10 +0000 (21:21 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
cprogs/prefork-interp.c

index 62e5ff05b72b2e5f78c91f98a8137e797c12ce75..8987dcf32b0c47415ff6c4fc1286af7c99825e22 100644 (file)
@@ -81,7 +81,7 @@
          |         #          |
          |         #       script initialisation
          |         #          |                                  application
-         |         #  ########|#############################################
+         |         ###########|#############################################
          |         #          |                               prefork-interp
          |         #       identify fds from envirnment               (Perl)
          |         #       open syslog
                              receives status, exits appropriately
                              (if was bad signal, reports to stderr, exits 127)
 
+***************************************************************************
+\f
+  Protocol, and functions of the script
+
+  1. Script interpreter will be spawned apparently as normal;
+     should run synchronously in the normal way until
+     "initialisation complete" point.  At initialisation complete:
+
+  2. Env var PREFORK_INTERP contains:
+
+         v1,SECS.NSECS[,...] LISTEN,CALL,WATCHE,WATCHI[,...][ ...]
+
+     To parse it: split on ASCII space (or any whitespace), taking
+     first two words.  There may or may not be further "words".
+     Then split each of the first two words on comma,
+     again taking the initial items as specified.  The items are:
+
+        v1    Protocol version indicator - literal.  If something else,
+              fail (installation is incompatible somehow).
+
+        SECS.NSECS
+              timestamp before script started running, as a decimal
+              time_t.  NSECS is exactly 9 digits.
+              To be used for auto reloading.
+
+     These items are file descriptors:
+
+        LISTEN   listening socket                 nonblocking
+        CALL     call socket for initial call     blocking
+        WATCHE   liveness watcher stderr          nonblocking
+        WATCHI   liveness sentinel                unspecified
+
+  3. Library should do the following:
+
+     1. Read and understand the PREFORK_INTERP env var.
+        If it is not set, initialisation complete should simply return.
+        This allows simple synchronous operation.
+
+     2. Open syslog
+     3. fork/exit (fork and have parent exit) (to make server)
+     4. setsid (to become session leader)
+     5. fork initial service (monitor) child, using CALL (see below)
+     6. Replace stdin/stdout/stderr with /dev/null,
+        and make a note to send all error messages to syslog
+     7. Enter select loop, looking for the following:
+
+         * accept on LISTEN:
+            i. see if we need to reload: is any file forming part
+               of the program newer than the SECS.NSECS ?
+               If so, log at LOG_INFO, and exit immediately
+               (dropping CALL, LISTEN, WATCHI, etc.)
+            ii. see if we can reap any children, possibly waiting
+               for children if we are at our concurrency limit
+               (limit should be configured through library, default 4)
+            iii. fork service (monitor) child, using accepted fd
+
+         * WATCHE is readable:
+            * EOF:: log at LOG_INFO, and exit
+            * data to read: read what is available immediately,
+              log it as a message at LOG_ERR, and exit
+
+  4. service (monitor) child does the following:
+
+      1. close all of LISTEN, WATCHI, WATCHE
+      2. setpgrp
+      3. send a greeting (on CALL) "PFI\n\0\0\0\0" (8 bytes)
+      4. read a single byte, fail if it's not zero
+      5. three times, receive a single byte with a file descriptor
+         attached as ancillary data.  (These descriptors will be
+         service stdin, stdout, stderr.)
+      6. read a 4-byte big-endian length
+      7. read that many bytes, the initial service request message,
+         which contains the following nul-terminated strings:
+            * environment variable settings in the format NAME=value
+            * an empty string
+            * arguments NOT INCLUDING argv[0] or script filename
+         (not that this means the service request must end in a nul)
+      8. make a new pipe EXECTERM
+      9. fork for the service executor; in the child
+            i. redirect stdin/stdout/stderr to the recevied fds
+            ii. replace environment and arguments with those received,
+            iii. close descriptors: close the original received descriptors;
+                 close CALL; keep only the writing end of EXECTERM
+            iv. if the script programming language does things with SIGINT,
+                set it set back to default handling (immediate termination).
+            v. return back to script, now in the grandchild
+
+      10. in the parent, close EXECTERM writing end, and
+      11. select, looking for one of the following:
+           * CALL is readable
+           * EXECTERM reading end is readable
+          No need to actually read, since these shouldn't produce
+          spurious wakeups (but do loop on EINTR).
+      12. set SIGINT to ignored
+      13. send SIGINT to the entire process group
+      14. wait, blocking, for the executor child
+      15. write the wait status, in 32-bit big-endian, to CALL
+          (this may generate SIGPIPE/EPIPE;
+          if so, die with SIGPIPE or exit 0; do treat that as failure)
+      16. exit 0
+
 ***************************************************************************
 \f
 */