Repositories / jai.git

jai.git

Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git

Branch

don't use ppid to figure out if parent exited

Author
David Mazieres <dm@uun.org>
Date
2026-03-18 13:42:35 -0700
Commit
42724dcca45ad47246f87e62d589e1728eac8241
fs.cc
index 2b9bf47..cb1ae29 100644
--- a/fs.cc
+++ b/fs.cc
@@ -1,7 +1,6 @@
 #include <cassert>
 #include <cstring>
 #include <filesystem>
-#include <print>
 
 #include <libmount.h>
 #include <sys/file.h>
jai.cc
index 6f84e49..7333932 100644
--- a/jai.cc
+++ b/jai.cc
@@ -697,7 +697,7 @@ Config::sanitize_env()
     unsetenv(v.c_str());
 }
 
-// Exits if child exited, returns stop signal if it stopped, 0 in EINTR
+// Exits if child pid exited, returns stop signal if pid stopped
 static int
 propagate_exit(int pid, bool immediate)
 try {
@@ -728,7 +728,7 @@ again:
   if (WIFSIGNALED(status)) {
     signal(WTERMSIG(status), SIG_DFL);
     raise(WTERMSIG(status));
-    _exit(-1);
+    (immediate ? _exit : exit)(-1);
   }
   err("unknown child wait status 0x{:x}", status);
 } catch (const std::exception &e) {
@@ -743,9 +743,9 @@ Config::exec(int nsfd, char **argv)
   // cannot move to a new PID namespace, so we have to fork once.  But
   // the forked process will have PID 1 and behave strangely (such as
   // not receiving signals), so needs to fork again to run the actual
-  // jailed program.  Then PID1 has to propagate exit and stop events
-  // to the outer parent, which must propagate it to the process than
-  // ran jai.
+  // jailed program with a normal PID.  Then PID 1 has to propagate
+  // exit and stop events to the outer parent, which must propagate
+  // them to the process than ran jai.
   //
   // Further complicating matters, PID 1 cannot stop itself (since it
   // cannot receive a SIGSTOP from within the PID namespace).  Hence,
@@ -757,8 +757,11 @@ Config::exec(int nsfd, char **argv)
     // This is the last process in the old PID namespace
     close(nsfd);
     stop_me[1].reset();
+    char garbage[64];
+    // discard first write, whose only purpose is to test the parent
+    // didn't die before the child set PR_SET_PDEATSIG.
+    read(*stop_me[0], garbage, 1);
     for (;;) {
-      char garbage[64];
       if (read(*stop_me[0], garbage, sizeof(garbage)) > 0)
         kill(pid, SIGSTOP);
       raise(propagate_exit(pid, false));
@@ -767,9 +770,16 @@ Config::exec(int nsfd, char **argv)
   stop_me[0].reset();
 
   if (auto pid = xfork()) {
-    // This is the "init" process in the new PID namespace
+    // This is the "init" process with PID 1 in the new namespace
     prctl(PR_SET_PDEATHSIG, SIGKILL);
-    if (getppid() == 1) {
+    // If the parent exited, users will be able to unmount overlay
+    // file systems that still exist in other namespaces, which is
+    // annoying and makes it hard to re-create them.  Write one byte
+    // to the pipe to die of a SIGPIPE in case the parent died in the
+    // brief moment before we set PR_SET_PDEATHSIG.  (getppid() won't
+    // tell us if we got reparented to init since we are in a new PID
+    // namespace.)
+    if (write(*stop_me[1], "", 1) != 1) {
       warn("parent killed before PR_SET_PDEATHSIG");
       _exit(1);
     }