chiark / gitweb /
prefork-interp: wip
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 15 Jul 2022 21:13:29 +0000 (22:13 +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>
perl/Prefork.pm
prefork-interp.c

index 162d5e471f5161571bd992e27e25daca2f3833df..fa986c1c739045b9e15436fd13355eb70947e710 100644 (file)
@@ -18,8 +18,83 @@ sub server_quit ($) {
   _exit(0);
 }
 
+# Returns in the executor process
 sub become_monitor () {
-  
+  my $child = fork // fail("fork executor: $!");or 
+  if (!$child) {
+    #---- executor ----
+    open ::STDIN , "<& $call_fds[0]" or fail("dup for fd0");
+    open ::STDOUT, ">& $call_fds[1]" or fail("dup for fd1");
+    open ::STDERR, ">& $call_fds[2]" or fail("dup for fd2");
+    POSIX::close($_) foreach @call_fds;
+    close CALL;
+    return;
+  }
+
+  #---- monitor [2] ----
+  my $got = waitpid $child, 0 // fail("wait for executor: $!");
+  $got == $child or fail("wait for esecutor gave $got, expected $child");
+
+  protocol_write(pack "L", $?);
+  _exit(0);
+}
+
+sub protocol_write ($) {
+  my ($d) = @_;
+  return if (print CALL $d and flush CALL);
+  _exit(0) if $!==EPIPE || $!==ECONNRESET;
+  fail("protocol write: $!");
+}
+
+sub eintr_retry ($) {
+  my ($f) = @_;
+  for (;;) {
+    my $r = $f->();
+    return $r if defined $r;
+    next if $!==EINTR;
+    return $r;
+  }
+}
+
+sub protocol_read_fail ($) {
+  my ($what) = @_;
+  _exit(0) if $!==ECONNRESET;
+  fail("recv $what: $!");
+}
+
+sub protocol_exchange () {
+  protocol_write('\n');
+
+  @call_fds = map {
+    my $r;
+    for (;;) {
+      $! = 0;
+      $r = IO::FDPass::recv(fileno(CALL));
+      last if $r >= 0;
+      _exit(0) if $!==0;
+      protocol_read_fail("fd $_");
+    }
+    $r;
+  } 0..2;
+
+  my $len;
+  $r = read(CALL, $len, 4) // protocol_read_fail("message length");
+  $r == 4 or _exit(0);
+
+  $len = unpack "L", $len;
+  my $data;
+  $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)");
+  $r == $len or _exit(0);
+
+  @ARGV = split /\0/, $data;
+  @ARGV >= 2 or fail("message data has too few strings");
+  length(pop(@ARGV)) and fail("message data missing trailing nul");
+  %ENV = ();
+  while (my $s = shift @ARGV) {
+    last if !length $s;
+    $s =~ m/=/ or fail("message data env var missing equals");
+    $ENV{$`} = $';
+  }
 }
 
 sub initialisation_complete {
@@ -61,7 +136,7 @@ sub initialisation_complete {
   $child = fork // croak "second fork failed: $!";
   if (!$child) {
     # we are the child, i.e. the one fa-monitor
-    become_monitor();
+    return become_monitor();
   }
 
   our %children;
@@ -111,7 +186,10 @@ sub initialisation_complete {
       if (accept(CALL, LISTEN)) {
        $child = fork // fail("fork for accepted call failed: $!");
        if (!$child) {
-         become_monitor();
+         #---- monitor [1] ----
+         close LISTEN;
+         protocol_exchange();
+         return become_monitor();
        }
        close(CALL);
        $errcount = 0;
index f37c8fdcb367f1b39993976109b9874b338a2f68..07e9fb0dd94eb4ee32cbac996b92fc27aff938fc 100644 (file)
@@ -53,7 +53,7 @@
  *
  *        [server (script)]   accepts, forks monitor
  *
- *          monitor           [fd[012]: null]
+ *          monitor [1]       [fd[012]: null]
  *                            other fds: syslog, call(server-end)
  *                            sends ack byte
  *                            receives args, env, fds
@@ -62,7 +62,7 @@
  *            executor        sorts out fds:
  *                            fd0, fd1, fd2: from-outer-caller
  *                            close fds: call(server-end)
- *                            implicitly closed fds: syslog
+ *                            retained fds: syslog
  *
  *                            sets cmdline, env
  *                            runs main part of script
@@ -128,9 +128,122 @@ static void propagate_exit_status(int status, const char *what) {
   die("setup failed with weird wait status %d 0x%x", status, status);
 }
 
+static void die_data_overflow __attribute((noreturn)) {
+  die("cannot handle data with length >2^32");
+}
+
+static void prepare_data(size_t *len, char **buf,
+                        const void *data, size_t dl) {
+  if (len) {
+    if (dl >= SIZE_MAX - *len)
+      die_data_overlow();
+    *len += dl;
+  }
+  if (buf) {
+    memcpy(*buf, data, dl);
+    *buf += dl;
+  }
+}
+  
+static void prepare_length(size_t *len, char **buf, size_t dl) {
+  if (dl > UINT32_MAX) die_data_overflow();
+  uint32_t dl = htonl(dl);
+  prepare_data(len, buf, &dl, sizeof(dl));
+}
+
+static void prepare_string(size_t *len, char **buf, const char *string) {
+  size_t sl = strlen(s);
+  prepare_data(len, buf, s, sl+1);
+}
+
+static void prepare_message(size_t *len, char **buf,
+                           const char *const *argv) {
+  const char *s;
+
+  const char *const *p = environ;
+  while ((s = *p++)) {
+    if (strchr(s, '='))
+      prepare_string(len, buf, s);
+  }
+
+  prepare_string(len, buf, "");
+
+  p = argv;
+  while ((s = *p++))
+    prepare_string(len, buf, s);
+}
+
+static void send_fd(int via_fd, int payload_fd) {
+  union {
+    struct cmsghdr align;
+    char buf[CMSG_SPACE(sizeof(payload_fd))];
+  } msg;
+  struct msghdr msg;
+  FILLZERO(msg);
+
+  struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SCM_RIGHTS;
+  cmsg->cmsg_len = CMSG_LEN(sizeof(payload_fd));
+  *(int*)CMSG_DATA(cmsg) = payload_fd;
+
+  char dummy_byte = 0;
+
+  struct iovec iov;
+  FIULLZERO(iov);
+  iov.iov_base = &dummy_byte;
+  iov.iov_len = 1;
+
+  msg.msg_name = 0;
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  msg.msg_control = msg.buf;
+  msg.msg_controllen = sizeof(msg.buf);
+
+  for (;;) {
+    ssize_t r = sendmsg(via_fd, &msg, 0);
+    if (r == -1) {
+      if (errno == EINTR) continue;
+      diee("send fd");
+    }
+    assert!(r == 1);
+    break;
+  }
+}
+
+static void send_request(int call_fd, const char *const *argv) {
+  // Sending these first makes it easier for the script to
+  // use buffered IO for the message.
+  send_fd(call_fd, 0);
+  send_fd(call_fd, 1);
+  send_fd(call_fd, 2);
+
+  size_t len = 4;
+  prepare_message(&len, 0, argv);
+  char *m = malloc(len);
+  if (!m) diee("failed to allocate for message");
+  char *p = m;
+  prepare_length(0, &p, len - 4);
+  prepare_message(0, &p, argv);
+  assert(p == m + len);
+
+  p = m;
+  while (len) {
+    ssize_t r = write(call_fd, p, len);
+    if (r==-1) {
+      if (errno == EINTR) continue;
+      diee("write request");
+    }
+    assert(r <= len);
+    assert(r > 0);
+    len -= r;
+    p += r;
+  }
+}
+
 // Returns: call(client-end) fd, or -1 to mean "is garbage"
 // find_socket_path must have been called
-static int attempt_connect_existing(void) {
+static int connect_existing(void) {
   int r;
   int fd = -1;
 
@@ -157,9 +270,14 @@ static int attempt_connect_existing(void) {
     }
     if (sr == 0) { goto x_garbage; }
     if (ack != '\n') die("got ack byte 0x%02x, not '\n'", ack);
-    return fd;
+    break;
   }
 
+  // We're committed now, send the request (or bail out)
+  send_request(call, argv);
+
+  return fd;
+
  x_garbage:
   if (fd >= 0) close(fd);
   return -1;