chiark / gitweb /
prefork-interp: fix exit status and signal handling
[chiark-utils.git] / cprogs / prefork-interp.c
index 8987dcf32b0c47415ff6c4fc1286af7c99825e22..3a999ef4c629d0339f6a7ffab5cf7fe021fbfb79 100644 (file)
@@ -7,11 +7,27 @@
  *   prefork-interp  [<option>,..],<interpreter>   <script> [<args> ...]
  *   prefork-interp '[<option> ..] <interpreter>'  <script> [<args> ...]
  *
- * Options must specify argument laundering mode.
- * Currently the only mode supported is:
+ * Options must specify argument mediation approach.
+ * Currently the only argument mediation supported is:
+ *
  *   -U    unlaundered: setup and executor both get all arguments and env vars
  *         ident covers only env vars specified  with -E
  *         ident covers only arguments interpreter and (if present) script
+ *
+ * Options for setting the operation mode:
+ *
+ *   (none)     Default: start new server if needed, then run service
+ *   -f         Force a fresh service (old one is terminated)
+ *   --kill     Kill any existing service; do not actually run anything
+ *
+ * Options for controlling whether different invocations share a server:
+ *
+ *   -E VAR      ident includes env var VAR (or its absence)
+ *   -G STRING   ident includes string STRING
+ *   -g IDENT    use IDENT rather than hex(SHA256(... identity things ...))
+ *
+ * (Ordering of -E and -G options is relevant; invocations with different
+ * -E -G options are different even if the env var settings are the same)
  */
 
 /*
 
   2. Env var PREFORK_INTERP contains:
 
-         v1,SECS.NSECS[,...] LISTEN,CALL,WATCHE,WATCHI[,...][ ...]
+         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:
+     To parse it: treat as bytes and split on ASCII space, taking
+     the first two words.  (There may or may not be
+     further "words"; and if there are they might be binary data.)
+     Then split each of the first two words (which will contain only
+     ASCII printing characters) on comma.  Take the first two items:
 
         v1    Protocol version indicator - literal.  If something else,
-              fail (installation is incompatible somehow).
+              fail (means 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.
+              timestamp just before script started running, as a
+              decimal time_t.  NSECS is exactly 9 digits.
+              To be used for auto reloading (see below).
 
-     These items are file descriptors:
+     The 2nd word's items are file descriptors:
 
         LISTEN   listening socket                 nonblocking
         CALL     call socket for initial call     blocking
         WATCHE   liveness watcher stderr          nonblocking
         WATCHI   liveness sentinel                unspecified
 
+        (any further descriptors should be ignored, not closed)
+
   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.
+        (This allows simple synchronous operation.)
 
      2. Open syslog
      3. fork/exit (fork and have parent exit) (to make server)
         and make a note to send all error messages to syslog
      7. Enter select loop, looking for the following:
 
-         * accept on LISTEN:
+        A. 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
             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)
+               Report child exit status if not zero or SIGPIPE.
             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
+        B. WATCHE is readable:
+            * EOF: log at LOG_INFO, and exit
+            * data to read: read what is available immediately;
+              it will be an error message: log it at LOG_ERR, and exit
 
   4. service (monitor) child does the following:
 
       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)
+      15. write the wait status, in 32-bit big-endian, to CAL
       16. exit 0
 
+     Errors detected in the service monitor should be sent to
+     syslog, or stderr, depending on whether this is the initial
+     service monitor (from part 3 step 5) or an accepted socket
+     service monitor (from part 4 step 9); this can be achieved
+     easily by having a global flag (set at part 3 step 6),
+     or perhaps using logger(8) and redirecting stderr (but
+     then be careful to ensure everyone gets only the necessary fds).
+
+     EOF on CALL, or EPIPE/SIGPIPE writing to it, are not errors.
+     In this case, exit zero or die with SIGPIPE, so parent
+     won't report error either (part 3 step 7(A)(ii)).
+
 ***************************************************************************
 \f
 */
 
 #include <arpa/inet.h>
+#include <sys/utsname.h>
 
 #include <uv.h>
 
@@ -390,16 +421,34 @@ const struct cmdinfo cmdinfos[]= {
   { 0 }
 };
 
+static void ident_add_stat(const char *path) {
+  struct stat stab;
+  int r = stat(path, &stab);
+  if (r) diee("failed to stat %s", path);
+
+  IDENT_ADD_OBJ(path[0], stab.st_dev);
+  IDENT_ADD_OBJ('i',     stab.st_ino);
+}
+
 void ident_addinit(void) {
-  char ident_magic[1] = { 0 };
-  sha256_update(&identsc, sizeof(ident_magic), ident_magic);
+  ident_add_key_byte(1);
+
+  struct utsname uts = { };
+  size_t utslen = sizeof(uts);
+  int r = uname(&uts);
+  if (r) diee("uname failed!");
+  IDENT_ADD_OBJ('u', utslen);
+  IDENT_ADD_OBJ('u', uts);
+
+  ident_add_stat(".");
+  ident_add_stat("/");
 }
 
 static void propagate_exit_status(int status, const char *what) {
   int r;
 
   if (WIFEXITED(status)) {
-    _exit(status);
+    _exit(WEXITSTATUS(status));
   }
 
   if (WIFSIGNALED(status)) {
@@ -409,22 +458,25 @@ static void propagate_exit_status(int status, const char *what) {
 
     if (! WCOREDUMP(status) &&
        (sig == SIGINT ||
+        sig == SIGTERM ||
         sig == SIGHUP ||
         sig == SIGPIPE ||
         sig == SIGKILL)) {
       struct sigaction sa;
       FILLZERO(sa);
       sa.sa_handler = SIG_DFL;
-      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);
-      r = sigprocmask(SIG_UNBLOCK, &sset, 0);
-      if (r) diee("failed to reset signal block while propagating %s",
-                 signame);
+      if (sig != SIGKILL) {
+        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);
+        r = sigprocmask(SIG_UNBLOCK, &sset, 0);
+        if (r) diee("failed to reset signal block while propagating %s",
+                    signame);
+      }
 
       raise(sig);
       die("unexpectedly kept running after raising (to propagate) %s",
@@ -694,7 +746,8 @@ static const char *read_greeting(void) {
   char got_magic[sizeof(header_magic)];
 
   if (protocol_read_maybe(&got_magic, sizeof(got_magic)) < 0)
-    return "initial monitor process quit";
+    return "initial monitor process quit"
+      " (maybe script didn't call preform_initialisation_complete?)";
 
   if (memcmp(got_magic, header_magic, sizeof(header_magic)))
     die("got unexpected protocol magic 0x%02x%02x%02x%02x",