chiark / gitweb /
prefork-interp: wip
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 9 Jul 2022 14:00:07 +0000 (15:00 +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>
prefork-interp.c

index 3b77f61d9adffba39b1d5720f9161418c5cbceb0..cdb7dd3778a541508978d45df868a78f59423b23 100644 (file)
@@ -19,8 +19,8 @@
  *
  *     setup (pre-exec)       fd0: null,
  *                            fd[12: fd2-from-outer-caller
- *                            env fds: orig-fd[01], listener,
- *                            env fds: call(server-end)(fake)
+ *                            env fds: listener, call(server-end)(fake)
+ *                            env fds: orig-fd[01]
  *                            close fd: lockfile
  *
  *     setup (script)         runs initialisation parts of the script
@@ -82,17 +82,57 @@ struct sockaddr_un sun;
 
 #define ACK_BYTE '\n'
 
+static struct sockaddr_unix socket_sun;
+
+static void propagate_exit_status(int status, const char *what) {
+  int r;
+
+  if (WIFEXITED(status)) {
+    _exit(status);
+  }
+
+  if (WIFSIGNALED(status)) {
+    int sig = WTERMSIG(status);
+    char *signame = strsignal(sig);
+    if (signame == 0) signame = "unknown signal";
+
+    if (! WCOREDUMP(status) &&
+       (sig == SIGINT ||
+        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(SA_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",
+         signame);
+    }
+
+    die("setup failed due to signal %d %s%s", sig, signame,
+       WCOREDUMP(status) ? " (core dumped)" : "");
+  }
+
+  die("setup failed with weird wait status %d 0x%x", status, status);
+}
+
 // Returns: call(client-end) fd, or -1 to mean "is garbage"
 // find_socket_path must have been called
 static int attempt_connect_existing(void) {
   int r;
   int fd = -1;
 
-  FILLZERO(sun);
-  sun.sun_family = AF_UNIX;
-  assert(strlen(socket_path) <= sizeof(sun.sun_path));
-  strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
-
   bool isgarbage = check_garbage();
   if (isgarbage) goto x_garbage;
 
@@ -100,7 +140,7 @@ static int attempt_connect_existing(void) {
   if (fd==-1) diee("socket() for client");
 
   salen_t salen = sizeof(sun);
-  r = connect(client, (const struct sockaddr*)sun, salen);
+  r = connect(client, (const struct sockaddr*)&socket_sun, salen);
   if (r==-1) {
     if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae;
     diee("connect() %s", socket_path);
@@ -124,16 +164,80 @@ static int attempt_connect_existing(void) {
   return -1;
 }
 
+static void become_setup(int sfd, int fake_pair[2])
+  __attribute__((noreturn))
+{
+  close(fake_pair[0]);
+  int call_fd = fake_pair[1];
+
+  int fd0_save = dup(0);  if (fd0_save < 0) diee("dup stdin");
+  int fd1_save = dup(1);  if (fd1_save < 0) diee("dup stdin");
+
+  int null_0 = open("/dev/null", O_RDONLY);  if (null_0 < 0) diee("open null");
+  if (dup2(null_0, 0)) diee("dup2 /dev/null onto stdin");
+  if (dup2(2, 1) != 1) die("dup2 stderr onto stdout");
+
+  putenv(asprintf("PREFORK_INTERP_FDS=%d,%d,%d,%d",
+                 sfd, call_fd, fd0_save, fd1_save));
+  execv(
+}
+
 static int connect_or_spawn(void) {
   int fd = connect_existing();
   if (fd >= 0) return fd;
 
-  let acquire_lock();
-    fd = connect_existing();
+  int lockfd = acquire_lock();
+  fd = connect_existing();
+  if (fd >= 0) { close(lockfd); return fd; }
+
+  // We must start a fresh one, and we hold the lock
+
+  r = unlink(socketpath);
+  if (r<0) diee("failed to remove stale socket %s", socketpath);
+
+  int fake_pair[2];
+  r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair);
+  if (r<0) diee("socketpair() for fake initial connection");
+
+  int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (sfd<0) diee("socket() for new listener");
+
+  salen_t salen = sizeof(sun);
+  r= bind(sfd, (const struct sockaddr*)&socket_sun, saledn);
+  if (r<0) diee("bind() on new listener");
+
+  // We never want callers to get ECONNREFUSED!.
+  // There is a race here: from my RTFM they may get ECONNREFUSED
+  // if they tr between our bind() and listen().  But if they do, they'll
+  // acquire the lock (serialising with us) and retry, and then it will work.
+  r = listen(sfd, INT_MAX);
+  if (r<0) diee("listen() for new listener");
+
+  pid_t setup_pid = fork();
+  if (setup_pid == (pid_t)-1) diee("fork for spawn setup");
+  if (!setup_pid) become_setup(sfd, fake_pair);
+  close(fake_pair[1]);
+  close(sfd);
+
+  int status;
+  pid_t got = waitpid(setup_pid, &status, 0);
+  if (got == (pid_t)-1) diee("waitpid setup [%ld]", (long)setup_pid);
+  if (got != setup_pid) diee("waitpid setup [%ld] gave [%ld]!",
+                            (long)setup_pid, (long)got);
+  if (status != 0) propagate_exit_status(status);
+
+  close(lockfd);
+  return fake_pair[0];
+}
 
 int main(int argc, const char *const *argv) {
   script = process_opts(argc, argv);
 
   find_socket_path();
-  int fd = connect_or_spawn();
+  FILLZERO(sun);
+  sun.sun_family = AF_UNIX;
+  assert(strlen(socket_path) <= sizeof(sun.sun_path));
+  strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
+
+  int call_fd = connect_or_spawn();
 }