Implemented native functions and types for blastula management.

This patch adds native support for spawning and managing blastula pools,
as well as several code cleanups and modernizations.

Changes includes:
* A function to fork blastulas
* A table for managing blastula-related data
* Functions for adding and removing blastula data from the
aforementioned table
* Switching from NULL to nullptr
* Replacing string-passing error handling with a curried failure
function
* Utility functions for handling managed objects
* JNI functions for blastula pool management

Topic: zygot-prefork
Test: make & flash & launch apps & check log for messages
Bug: 68253328
Change-Id: I12cd9f2c87a2e3c00d64b683edf3631e29a51551
Merged-In: I12cd9f2c87a2e3c00d64b683edf3631e29a51551
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 8b3829b3..6e14558 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -94,7 +94,7 @@
     private Zygote() {}
 
     /** Called for some security initialization before any fork. */
-    native static void nativeSecurityInit();
+    static native void nativeSecurityInit();
 
     /**
      * Forks a new VM instance.  The current VM must have been started
@@ -131,14 +131,14 @@
      * if this is the parent, or -1 on error.
      */
     public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
-          int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
-          int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) {
+            int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
+            int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) {
         VM_HOOKS.preFork();
         // Resets nice priority for zygote process.
         resetNicePriority();
         int pid = nativeForkAndSpecialize(
-                  uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
-                  fdsToIgnore, startChildZygote, instructionSet, appDataDir);
+                uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
+                fdsToIgnore, startChildZygote, instructionSet, appDataDir);
         // Enable tracing as soon as possible for the child process.
         if (pid == 0) {
             Trace.setTracingEnabled(true, runtimeFlags);
@@ -150,14 +150,19 @@
         return pid;
     }
 
-    native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags,
-          int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
-          int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir);
+    private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
+            int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+            int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
+            String appDataDir);
+
+    private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
+            int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
+            boolean startChildZygote, String instructionSet, String appDataDir);
 
     /**
      * Called to do any initialization before starting an application.
      */
-    native static void nativePreApplicationInit();
+    static native void nativePreApplicationInit();
 
     /**
      * Special method to start the system server process. In addition to the
@@ -188,7 +193,8 @@
         // Resets nice priority for zygote process.
         resetNicePriority();
         int pid = nativeForkSystemServer(
-                uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
+                uid, gid, gids, runtimeFlags, rlimits,
+                permittedCapabilities, effectiveCapabilities);
         // Enable tracing as soon as we enter the system_server.
         if (pid == 0) {
             Trace.setTracingEnabled(true, runtimeFlags);
@@ -197,19 +203,34 @@
         return pid;
     }
 
-    native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
+    private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
 
     /**
      * Lets children of the zygote inherit open file descriptors to this path.
      */
-    native protected static void nativeAllowFileAcrossFork(String path);
+    protected static native void nativeAllowFileAcrossFork(String path);
 
     /**
      * Zygote unmount storage space on initializing.
      * This method is called once.
      */
-    native protected static void nativeUnmountStorageOnInit();
+    protected static native void nativeUnmountStorageOnInit();
+
+    protected static native void nativeGetSocketFDs(boolean isPrimary);
+
+    private static native int nativeGetBlastulaPoolCount();
+
+    private static native int nativeGetBlastulaPoolEventFD();
+
+    private static native int nativeForkBlastula(int readPipeFD,
+                                                 int writePipeFD,
+                                                 int[] sessionSocketRawFDs);
+
+    private static native int[] nativeGetBlastulaPipeFDs();
+
+    private static native boolean nativeRemoveBlastulaTableEntry(int blastulaPID);
+
 
     private static void callPostForkSystemServerHooks() {
         // SystemServer specific post fork hooks run before child post fork hooks.
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 827d13f..569a894 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -29,13 +29,17 @@
 #include <sys/mount.h>
 #include <linux/fs.h>
 
+#include <array>
+#include <atomic>
 #include <functional>
 #include <list>
 #include <optional>
 #include <sstream>
 #include <string>
+#include <string_view>
 
 #include <android/fdsan.h>
+#include <arpa/inet.h>
 #include <fcntl.h>
 #include <grp.h>
 #include <inttypes.h>
@@ -46,9 +50,11 @@
 #include <stdlib.h>
 #include <sys/capability.h>
 #include <sys/cdefs.h>
+#include <sys/eventfd.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
@@ -56,10 +62,11 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
-#include "android-base/logging.h"
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
 #include <private/android_filesystem_config.h>
@@ -81,6 +88,9 @@
 
 namespace {
 
+// TODO (chriswailes): Add a function to initialize native Zygote data.
+// TODO (chriswailes): Fix mixed indentation style (2 and 4 spaces).
+
 using namespace std::placeholders;
 
 using android::String8;
@@ -91,6 +101,9 @@
 #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
                               append(StringPrintf(__VA_ARGS__))
 
+// This type is duplicated in fd_utils.h
+typedef const std::function<void(std::string)>& fail_fn_t;
+
 static pid_t gSystemServerPid = 0;
 
 static const char kZygoteClassName[] = "com/android/internal/os/Zygote";
@@ -100,6 +113,152 @@
 
 static bool g_is_security_enforced = true;
 
+/**
+ * The maximum number of characters (not including a null terminator) that a
+ * process name may contain.
+ */
+static constexpr size_t MAX_NAME_LENGTH = 15;
+
+/**
+ * The prefix string for environmental variables storing socket FDs created by
+ * init.
+ */
+
+static constexpr std::string_view ANDROID_SOCKET_PREFIX("ANDROID_SOCKET_");
+
+/**
+ * The file descriptor for the Zygote socket opened by init.
+ */
+
+static int gZygoteSocketFD = -1;
+
+/**
+ * The file descriptor for the Blastula pool socket opened by init.
+ */
+
+static int gBlastulaPoolSocketFD = -1;
+
+/**
+ * The number of Blastulas currently in this Zygote's pool.
+ */
+static std::atomic_uint32_t gBlastulaPoolCount = 0;
+
+/**
+ * Event file descriptor used to communicate reaped blastulas to the
+ * ZygoteServer.
+ */
+static int gBlastulaPoolEventFD = -1;
+
+/**
+ * The maximum value that the gBlastulaPoolMax variable may take.  This value
+ * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT
+ */
+static constexpr int BLASTULA_POOL_MAX_LIMIT = 10;
+
+/**
+ * A helper class containing accounting information for Blastulas.
+ */
+class BlastulaTableEntry {
+ public:
+  struct EntryStorage {
+    int32_t pid;
+    int32_t read_pipe_fd;
+
+    bool operator!=(const EntryStorage& other) {
+      return pid != other.pid || read_pipe_fd != other.read_pipe_fd;
+    }
+  };
+
+ private:
+  static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1};
+
+  std::atomic<EntryStorage> mStorage;
+  static_assert(decltype(mStorage)::is_always_lock_free);
+
+ public:
+  constexpr BlastulaTableEntry() : mStorage(INVALID_ENTRY_VALUE) {}
+
+  /**
+   * If the provided PID matches the one stored in this entry, the entry will
+   * be invalidated and the associated file descriptor will be closed.  If the
+   * PIDs don't match nothing will happen.
+   *
+   * @param pid The ID of the process who's entry we want to clear.
+   * @return True if the entry was cleared; false otherwise
+   */
+  bool ClearForPID(int32_t pid) {
+    EntryStorage storage = mStorage.load();
+
+    if (storage.pid == pid) {
+      /*
+       * There are three possible outcomes from this compare-and-exchange:
+       *   1) It succeeds, in which case we close the FD
+       *   2) It fails and the new value is INVALID_ENTRY_VALUE, in which case
+       *      the entry has already been cleared.
+       *   3) It fails and the new value isn't INVALID_ENTRY_VALUE, in which
+       *      case the entry has already been cleared and re-used.
+       *
+       * In all three cases the goal of the caller has been met and we can
+       * return true.
+       */
+      if (mStorage.compare_exchange_strong(storage, INVALID_ENTRY_VALUE)) {
+        close(storage.read_pipe_fd);
+      }
+
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * @return A copy of the data stored in this entry.
+   */
+  std::optional<EntryStorage> GetValues() {
+    EntryStorage storage = mStorage.load();
+
+    if (storage != INVALID_ENTRY_VALUE) {
+      return storage;
+    } else {
+      return std::nullopt;
+    }
+  }
+
+  /**
+   * Sets the entry to the given values if it is currently invalid.
+   *
+   * @param pid  The process ID for the new entry.
+   * @param read_pipe_fd  The read end of the blastula control pipe for this
+   * process.
+   * @return True if the entry was set; false otherwise.
+   */
+  bool SetIfInvalid(int32_t pid, int32_t read_pipe_fd) {
+    EntryStorage new_value_storage;
+
+    new_value_storage.pid = pid;
+    new_value_storage.read_pipe_fd = read_pipe_fd;
+
+    EntryStorage expected = INVALID_ENTRY_VALUE;
+
+    return mStorage.compare_exchange_strong(expected, new_value_storage);
+  }
+};
+
+/**
+ * A table containing information about the Blastulas currently in the pool.
+ *
+ * Multiple threads may be attempting to modify the table, either from the
+ * signal handler or from the ZygoteServer poll loop.  Atomic loads/stores in
+ * the BlastulaTableEntry class prevent data races during these concurrent
+ * operations.
+ */
+static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable;
+
+/**
+ * The list of open zygote file descriptors.
+ */
+static FileDescriptorTable* gOpenFdTable = nullptr;
+
 // Must match values in com.android.internal.os.Zygote.
 enum MountExternalKind {
   MOUNT_EXTERNAL_NONE = 0,
@@ -113,6 +272,9 @@
   DEBUG_ENABLE_JDWP = 1,
 };
 
+// Forward declaration so we don't have to move the signal handler.
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid);
+
 static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
   std::ostringstream oss;
   oss << __FILE__ << ":" << line << ": " << msg;
@@ -123,6 +285,7 @@
 static void SigChldHandler(int /*signal_number*/) {
   pid_t pid;
   int status;
+  int64_t blastulas_removed = 0;
 
   // It's necessary to save and restore the errno during this function.
   // Since errno is stored per thread, changing it here modifies the errno
@@ -156,6 +319,11 @@
       ALOGE("Exit zygote because system server (%d) has terminated", pid);
       kill(getpid(), SIGKILL);
     }
+
+    // Check to see if the PID is in the blastula pool and remove it if it is.
+    if (RemoveBlastulaTableEntry(pid)) {
+      ++blastulas_removed;
+    }
   }
 
   // Note that we shouldn't consider ECHILD an error because
@@ -164,6 +332,15 @@
     ALOGW("Zygote SIGCHLD error in waitpid: %s", strerror(errno));
   }
 
+  if (blastulas_removed > 0) {
+    if (write(gBlastulaPoolEventFD, &blastulas_removed, sizeof(blastulas_removed)) == -1) {
+      // If this write fails something went terribly wrong.  We will now kill
+      // the zygote and let the system bring it back up.
+      ALOGE("Zygote failed to write to blastula pool event FD: %s", strerror(errno));
+      kill(getpid(), SIGKILL);
+    }
+  }
+
   errno = saved_errno;
 }
 
@@ -188,13 +365,13 @@
   struct sigaction sig_chld = {};
   sig_chld.sa_handler = SigChldHandler;
 
-  if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) {
+  if (sigaction(SIGCHLD, &sig_chld, nullptr) < 0) {
     ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
   }
 
   struct sigaction sig_hup = {};
   sig_hup.sa_handler = SIG_IGN;
-  if (sigaction(SIGHUP, &sig_hup, NULL) < 0) {
+  if (sigaction(SIGHUP, &sig_hup, nullptr) < 0) {
     ALOGW("Error setting SIGHUP handler: %s", strerror(errno));
   }
 }
@@ -205,64 +382,57 @@
   memset(&sa, 0, sizeof(sa));
   sa.sa_handler = SIG_DFL;
 
-  if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+  if (sigaction(SIGCHLD, &sa, nullptr) < 0) {
     ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno));
   }
 }
 
 // Calls POSIX setgroups() using the int[] object as an argument.
-// A NULL argument is tolerated.
-static bool SetGids(JNIEnv* env, jintArray javaGids, std::string* error_msg) {
-  if (javaGids == NULL) {
-    return true;
+// A nullptr argument is tolerated.
+static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) {
+  if (managed_gids == nullptr) {
+    return;
   }
 
-  ScopedIntArrayRO gids(env, javaGids);
-  if (gids.get() == NULL) {
-    *error_msg = CREATE_ERROR("Getting gids int array failed");
-    return false;
-  }
-  int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
-  if (rc == -1) {
-    *error_msg = CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size());
-    return false;
+  ScopedIntArrayRO gids(env, managed_gids);
+  if (gids.get() == nullptr) {
+    fail_fn(CREATE_ERROR("Getting gids int array failed"));
   }
 
-  return true;
+  if (setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])) == -1) {
+    fail_fn(CREATE_ERROR("setgroups failed: %s, gids.size=%zu", strerror(errno), gids.size()));
+  }
 }
 
 // Sets the resource limits via setrlimit(2) for the values in the
 // two-dimensional array of integers that's passed in. The second dimension
-// contains a tuple of length 3: (resource, rlim_cur, rlim_max). NULL is
+// contains a tuple of length 3: (resource, rlim_cur, rlim_max). nullptr is
 // treated as an empty array.
-static bool SetRLimits(JNIEnv* env, jobjectArray javaRlimits, std::string* error_msg) {
-  if (javaRlimits == NULL) {
-    return true;
+static void SetRLimits(JNIEnv* env, jobjectArray managed_rlimits, fail_fn_t fail_fn) {
+  if (managed_rlimits == nullptr) {
+    return;
   }
 
   rlimit rlim;
   memset(&rlim, 0, sizeof(rlim));
 
-  for (int i = 0; i < env->GetArrayLength(javaRlimits); ++i) {
-    ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
-    ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
-    if (javaRlimit.size() != 3) {
-      *error_msg = CREATE_ERROR("rlimits array must have a second dimension of size 3");
-      return false;
+  for (int i = 0; i < env->GetArrayLength(managed_rlimits); ++i) {
+    ScopedLocalRef<jobject>
+        managed_rlimit_object(env, env->GetObjectArrayElement(managed_rlimits, i));
+    ScopedIntArrayRO rlimit_handle(env, reinterpret_cast<jintArray>(managed_rlimit_object.get()));
+
+    if (rlimit_handle.size() != 3) {
+      fail_fn(CREATE_ERROR("rlimits array must have a second dimension of size 3"));
     }
 
-    rlim.rlim_cur = javaRlimit[1];
-    rlim.rlim_max = javaRlimit[2];
+    rlim.rlim_cur = rlimit_handle[1];
+    rlim.rlim_max = rlimit_handle[2];
 
-    int rc = setrlimit(javaRlimit[0], &rlim);
-    if (rc == -1) {
-      *error_msg = CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
-            rlim.rlim_max);
-      return false;
+    if (setrlimit(rlimit_handle[0], &rlim) == -1) {
+      fail_fn(CREATE_ERROR("setrlimit(%d, {%ld, %ld}) failed",
+                           rlimit_handle[0], rlim.rlim_cur, rlim.rlim_max));
     }
   }
-
-  return true;
 }
 
 static void EnableDebugger() {
@@ -322,32 +492,26 @@
   }
 }
 
-static bool EnableKeepCapabilities(std::string* error_msg) {
-  int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
-  if (rc == -1) {
-    *error_msg = CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno));
-    return false;
+static void EnableKeepCapabilities(fail_fn_t fail_fn) {
+  if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
+    fail_fn(CREATE_ERROR("prctl(PR_SET_KEEPCAPS) failed: %s", strerror(errno)));
   }
-  return true;
 }
 
-static bool DropCapabilitiesBoundingSet(std::string* error_msg) {
-  for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
-    int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
-    if (rc == -1) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+  for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+    if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
       if (errno == EINVAL) {
         ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
               "your kernel is compiled with file capabilities support");
       } else {
-        *error_msg = CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno));
-        return false;
+        fail_fn(CREATE_ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno)));
       }
     }
   }
-  return true;
 }
 
-static bool SetInheritable(uint64_t inheritable, std::string* error_msg) {
+static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
   capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -355,23 +519,19 @@
 
   __user_cap_data_struct capdata[2];
   if (capget(&capheader, &capdata[0]) == -1) {
-    *error_msg = CREATE_ERROR("capget failed: %s", strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("capget failed: %s", strerror(errno)));
   }
 
   capdata[0].inheritable = inheritable;
   capdata[1].inheritable = inheritable >> 32;
 
   if (capset(&capheader, &capdata[0]) == -1) {
-    *error_msg = CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno)));
   }
-
-  return true;
 }
 
-static bool SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
-                            std::string* error_msg) {
+static void SetCapabilities(uint64_t permitted, uint64_t effective, uint64_t inheritable,
+                            fail_fn_t fail_fn) {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
   capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -387,27 +547,23 @@
   capdata[1].inheritable = inheritable >> 32;
 
   if (capset(&capheader, &capdata[0]) == -1) {
-    *error_msg = CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
-                              "failed: %s", permitted, effective, inheritable, strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") "
+                         "failed: %s", permitted, effective, inheritable, strerror(errno)));
   }
-  return true;
 }
 
-static bool SetSchedulerPolicy(std::string* error_msg) {
+static void SetSchedulerPolicy(fail_fn_t fail_fn) {
   errno = -set_sched_policy(0, SP_DEFAULT);
   if (errno != 0) {
-    *error_msg = CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno));
-    return false;
+    fail_fn(CREATE_ERROR("set_sched_policy(0, SP_DEFAULT) failed: %s", strerror(errno)));
   }
-  return true;
 }
 
 static int UnmountTree(const char* path) {
     size_t path_len = strlen(path);
 
     FILE* fp = setmntent("/proc/mounts", "r");
-    if (fp == NULL) {
+    if (fp == nullptr) {
         ALOGE("Error opening /proc/mounts: %s", strerror(errno));
         return -errno;
     }
@@ -416,7 +572,7 @@
     // reverse order to give us the best chance of success.
     std::list<std::string> toUnmount;
     mntent* mentry;
-    while ((mentry = getmntent(fp)) != NULL) {
+    while ((mentry = getmntent(fp)) != nullptr) {
         if (strncmp(mentry->mnt_dir, path, path_len) == 0) {
             toUnmount.push_front(std::string(mentry->mnt_dir));
         }
@@ -433,8 +589,8 @@
 
 // Create a private mount namespace and bind mount appropriate emulated
 // storage for the given user.
-static bool MountEmulatedStorage(uid_t uid, jint mount_mode,
-        bool force_mount_namespace, std::string* error_msg) {
+static void MountEmulatedStorage(uid_t uid, jint mount_mode,
+                                 bool force_mount_namespace, fail_fn_t fail_fn) {
     // See storage config details at http://source.android.com/tech/storage/
 
     String8 storageSource;
@@ -446,44 +602,39 @@
         storageSource = "/mnt/runtime/write";
     } else if (!force_mount_namespace) {
         // Sane default of no storage visible
-        return true;
+        return;
     }
 
     // Create a second private mount namespace for our process
     if (unshare(CLONE_NEWNS) == -1) {
-        *error_msg = CREATE_ERROR("Failed to unshare(): %s", strerror(errno));
-        return false;
+        fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));
     }
 
     // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
     if (mount_mode == MOUNT_EXTERNAL_NONE) {
-        return true;
+        return;
     }
 
     if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage",
-            NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) {
-        *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s",
-                                  storageSource.string(),
-                                  strerror(errno));
-        return false;
+                                 nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
+        fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
+                             storageSource.string(),
+                             strerror(errno)));
     }
 
     // Mount user-specific symlink helper into place
     userid_t user_id = multiuser_get_user_id(uid);
     const String8 userSource(String8::format("/mnt/user/%d", user_id));
     if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) {
-        *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string());
-        return false;
-    }
-    if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
-            NULL, MS_BIND, NULL)) == -1) {
-        *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s",
-                                  userSource.string(),
-                                  strerror(errno));
-        return false;
+        fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s (%s)",
+                             userSource.string(), strerror(errno)));
     }
 
-    return true;
+    if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self",
+                                 nullptr, MS_BIND, nullptr)) == -1) {
+        fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
+                             userSource.string(), strerror(errno)));
+    }
 }
 
 static bool NeedsNoRandomizeWorkaround() {
@@ -511,55 +662,45 @@
 // descriptor (if any) is closed via dup2(), replacing it with a valid
 // (open) descriptor to /dev/null.
 
-static bool DetachDescriptors(JNIEnv* env, jintArray fdsToClose, std::string* error_msg) {
-  if (!fdsToClose) {
-    return true;
-  }
-  jsize count = env->GetArrayLength(fdsToClose);
-  ScopedIntArrayRO ar(env, fdsToClose);
-  if (ar.get() == NULL) {
-    *error_msg = "Bad fd array";
-    return false;
-  }
-  jsize i;
-  int devnull;
-  for (i = 0; i < count; i++) {
-    devnull = open("/dev/null", O_RDWR);
-    if (devnull < 0) {
-      *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
-      return false;
+static void DetachDescriptors(JNIEnv* env,
+                              const std::vector<int>& fds_to_close,
+                              fail_fn_t fail_fn) {
+
+  if (fds_to_close.size() > 0) {
+    android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR));
+    if (devnull_fd == -1) {
+      fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
     }
-    ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
-    if (dup2(devnull, ar[i]) < 0) {
-      *error_msg = StringPrintf("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
-      return false;
+
+    for (int fd : fds_to_close) {
+      ALOGV("Switching descriptor %d to /dev/null", fd);
+      if (dup2(devnull_fd, fd) == -1) {
+        fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno)));
+      }
     }
-    close(devnull);
   }
-  return true;
 }
 
-void SetThreadName(const char* thread_name) {
+void SetThreadName(const std::string& thread_name) {
   bool hasAt = false;
   bool hasDot = false;
-  const char* s = thread_name;
-  while (*s) {
-    if (*s == '.') {
+
+  for (const char str_el : thread_name) {
+    if (str_el == '.') {
       hasDot = true;
-    } else if (*s == '@') {
+    } else if (str_el == '@') {
       hasAt = true;
     }
-    s++;
   }
-  const int len = s - thread_name;
-  if (len < 15 || hasAt || !hasDot) {
-    s = thread_name;
-  } else {
-    s = thread_name + len - 15;
+
+  const char* name_start_ptr = thread_name.c_str();
+  if (thread_name.length() >= MAX_NAME_LENGTH && !hasAt && hasDot) {
+    name_start_ptr += thread_name.length() - MAX_NAME_LENGTH;
   }
+
   // pthread_setname_np fails rather than truncating long strings.
   char buf[16];       // MAX_TASK_COMM_LEN=16 is hard-coded into bionic
-  strlcpy(buf, s, sizeof(buf)-1);
+  strlcpy(buf, name_start_ptr, sizeof(buf) - 1);
   errno = pthread_setname_np(pthread_self(), buf);
   if (errno != 0) {
     ALOGW("Unable to set the name of current thread to '%s': %s", buf, strerror(errno));
@@ -568,28 +709,16 @@
   android::base::SetDefaultTag(buf);
 }
 
-// The list of open zygote file descriptors.
-static FileDescriptorTable* gOpenFdTable = NULL;
-
-static bool FillFileDescriptorVector(JNIEnv* env,
-                                     jintArray managed_fds,
-                                     std::vector<int>* fds,
-                                     std::string* error_msg) {
-  CHECK(fds != nullptr);
-  if (managed_fds != nullptr) {
-    ScopedIntArrayRO ar(env, managed_fds);
-    if (ar.get() == nullptr) {
-      *error_msg = "Bad fd array";
-      return false;
-    }
-    fds->reserve(ar.size());
-    for (size_t i = 0; i < ar.size(); ++i) {
-      fds->push_back(ar[i]);
-    }
-  }
-  return true;
-}
-
+/**
+ * A failure function used to report fatal errors to the managed runtime.  This
+ * function is often curried with the process name information and then passed
+ * to called functions.
+ *
+ * @param env  Managed runtime environment
+ * @param process_name  A native representation of the process name
+ * @param managed_process_name  A managed representation of the process name
+ * @param msg  The error message to be reported
+ */
 [[noreturn]]
 static void ZygoteFailure(JNIEnv* env,
                           const char* process_name,
@@ -610,12 +739,25 @@
   __builtin_unreachable();
 }
 
+/**
+ * A helper method for converting managed strings to native strings.  A fatal
+ * error is generated if a problem is encountered in extracting a non-null
+ * string.
+ *
+ * @param env  Managed runtime environment
+ * @param process_name  A native representation of the process name
+ * @param managed_process_name  A managed representation of the process name
+ * @param managed_string  The managed string to extract
+ *
+ * @return An empty option if the managed string is null.  A optional-wrapped
+ * string otherwise.
+ */
 static std::optional<std::string> ExtractJString(JNIEnv* env,
                                                  const char* process_name,
                                                  jstring managed_process_name,
                                                  jstring managed_string) {
   if (managed_string == nullptr) {
-    return std::optional<std::string>();
+    return std::nullopt;
   } else {
     ScopedUtfChars scoped_string_chars(env, managed_string);
 
@@ -627,15 +769,85 @@
   }
 }
 
-// Utility routine to fork a zygote.
-static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
-                        jintArray managed_fds_to_close, jintArray managed_fds_to_ignore) {
-  SetSignalHandlers();
+/**
+ * A helper method for converting managed integer arrays to native vectors.  A
+ * fatal error is generated if a problem is encountered in extracting a non-null array.
+ *
+ * @param env  Managed runtime environment
+ * @param process_name  A native representation of the process name
+ * @param managed_process_name  A managed representation of the process name
+ * @param managed_array  The managed integer array to extract
+ *
+ * @return An empty option if the managed array is null.  A optional-wrapped
+ * vector otherwise.
+ */
+static std::optional<std::vector<int>> ExtractJIntArray(JNIEnv* env,
+                                                        const char* process_name,
+                                                        jstring managed_process_name,
+                                                        jintArray managed_array) {
+  if (managed_array == nullptr) {
+    return std::nullopt;
+  } else {
+    ScopedIntArrayRO managed_array_handle(env, managed_array);
 
-  // Block SIGCHLD prior to fork.
-  sigset_t sigchld;
-  sigemptyset(&sigchld);
-  sigaddset(&sigchld, SIGCHLD);
+    if (managed_array_handle.get() != nullptr) {
+      std::vector<int> native_array;
+      native_array.reserve(managed_array_handle.size());
+
+      for (size_t array_index = 0; array_index < managed_array_handle.size(); ++array_index) {
+        native_array.push_back(managed_array_handle[array_index]);
+      }
+
+      return std::move(native_array);
+
+    } else {
+      ZygoteFailure(env, process_name, managed_process_name, "Failed to extract JIntArray.");
+    }
+  }
+}
+
+/**
+ * A utility function for blocking signals.
+ *
+ * @param signum  Signal number to block
+ * @param fail_fn  Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void BlockSignal(int signum, fail_fn_t fail_fn) {
+  sigset_t sigs;
+  sigemptyset(&sigs);
+  sigaddset(&sigs, signum);
+
+  if (sigprocmask(SIG_BLOCK, &sigs, nullptr) == -1) {
+    fail_fn(CREATE_ERROR("Failed to block signal %s: %s", strsignal(signum), strerror(errno)));
+  }
+}
+
+
+/**
+ * A utility function for unblocking signals.
+ *
+ * @param signum  Signal number to unblock
+ * @param fail_fn  Fatal error reporting function
+ *
+ * @see ZygoteFailure
+ */
+static void UnblockSignal(int signum, fail_fn_t fail_fn) {
+  sigset_t sigs;
+  sigemptyset(&sigs);
+  sigaddset(&sigs, signum);
+
+  if (sigprocmask(SIG_UNBLOCK, &sigs, nullptr) == -1) {
+    fail_fn(CREATE_ERROR("Failed to un-block signal %s: %s", strsignal(signum), strerror(errno)));
+  }
+}
+
+// Utility routine to fork a process from the zygote.
+static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
+                        const std::vector<int>& fds_to_close,
+                        const std::vector<int>& fds_to_ignore) {
+  SetSignalHandlers();
 
   // Curry a failure function.
   auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
@@ -646,9 +858,7 @@
   // This would cause failures because the FDs are not whitelisted.
   //
   // Note that the zygote process is single threaded at this point.
-  if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) {
-    fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
-  }
+  BlockSignal(SIGCHLD, fail_fn);
 
   // Close any logging related FDs before we start evaluating the list of
   // file descriptors.
@@ -658,19 +868,10 @@
   // If this is the first fork for this zygote, create the open FD table.  If
   // it isn't, we just need to check whether the list of open files has changed
   // (and it shouldn't in the normal case).
-  std::string error_msg;
-  std::vector<int> fds_to_ignore;
-  if (!FillFileDescriptorVector(env, managed_fds_to_ignore, &fds_to_ignore, &error_msg)) {
-    fail_fn(error_msg);
-  }
-
   if (gOpenFdTable == nullptr) {
-    gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, &error_msg);
-    if (gOpenFdTable == nullptr) {
-      fail_fn(error_msg);
-    }
-  } else if (!gOpenFdTable->Restat(fds_to_ignore, &error_msg)) {
-    fail_fn(error_msg);
+    gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
+  } else {
+    gOpenFdTable->Restat(fds_to_ignore, fail_fn);
   }
 
   android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level();
@@ -682,24 +883,19 @@
     PreApplicationInit();
 
     // Clean up any descriptors which must be closed immediately
-    if (!DetachDescriptors(env, managed_fds_to_close, &error_msg)) {
-      fail_fn(error_msg);
-    }
+    DetachDescriptors(env, fds_to_close, fail_fn);
 
     // Re-open all remaining open file descriptors so that they aren't shared
     // with the zygote across a fork.
-    if (!gOpenFdTable->ReopenOrDetach(&error_msg)) {
-      fail_fn(error_msg);
-    }
+    gOpenFdTable->ReopenOrDetach(fail_fn);
 
     // Turn fdsan back on.
     android_fdsan_set_error_level(fdsan_error_level);
   }
 
   // We blocked SIGCHLD prior to a fork, we unblock it here.
-  if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) {
-    fail_fn(CREATE_ERROR("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)));
-  }
+  UnblockSignal(SIGCHLD, fail_fn);
+
   return pid;
 }
 
@@ -711,32 +907,23 @@
                              jstring managed_nice_name, bool is_system_server,
                              bool is_child_zygote, jstring managed_instruction_set,
                              jstring managed_app_data_dir) {
-  auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
-                           managed_nice_name, _1);
-  auto extract_fn = std::bind(ExtractJString, env, is_system_server ? "system_server" : "zygote",
-                              managed_nice_name, _1);
+  const char* process_name = is_system_server ? "system_server" : "zygote";
+  auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
+  auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
 
   auto se_info = extract_fn(managed_se_info);
   auto nice_name = extract_fn(managed_nice_name);
   auto instruction_set = extract_fn(managed_instruction_set);
   auto app_data_dir = extract_fn(managed_app_data_dir);
 
-  std::string error_msg;
-
   // Keep capabilities across UID change, unless we're staying root.
   if (uid != 0) {
-    if (!EnableKeepCapabilities(&error_msg)) {
-      fail_fn(error_msg);
-    }
+    EnableKeepCapabilities(fail_fn);
   }
 
-  if (!SetInheritable(permitted_capabilities, &error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetInheritable(permitted_capabilities, fail_fn);
 
-  if (!DropCapabilitiesBoundingSet(&error_msg)) {
-    fail_fn(error_msg);
-  }
+  DropCapabilitiesBoundingSet(fail_fn);
 
   bool use_native_bridge = !is_system_server &&
                            instruction_set.has_value() &&
@@ -753,23 +940,12 @@
     ALOGW("Native bridge will not be used because managed_app_data_dir == nullptr.");
   }
 
-  if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg)) {
-    ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno));
-    if (errno == ENOTCONN || errno == EROFS) {
-      // When device is actively encrypting, we get ENOTCONN here
-      // since FUSE was mounted before the framework restarted.
-      // When encrypted device is booting, we get EROFS since
-      // FUSE hasn't been created yet by init.
-      // In either case, continue without external storage.
-    } else {
-      fail_fn(error_msg);
-    }
-  }
+  MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);
 
   // If this zygote isn't root, it won't be able to create a process group,
   // since the directory is owned by root.
   if (!is_system_server && getuid() == 0) {
-      int rc = createProcessGroup(uid, getpid());
+      const int rc = createProcessGroup(uid, getpid());
       if (rc != 0) {
           if (rc == -EROFS) {
               ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?");
@@ -779,13 +955,8 @@
       }
   }
 
-  if (!SetGids(env, gids, &error_msg)) {
-    fail_fn(error_msg);
-  }
-
-  if (!SetRLimits(env, rlimits, &error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetGids(env, gids, fail_fn);
+  SetRLimits(env, rlimits, fail_fn);
 
   if (use_native_bridge) {
     // Due to the logic behind use_native_bridge we know that both app_data_dir
@@ -844,14 +1015,9 @@
       }
   }
 
-  if (!SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
-                       &error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities, fail_fn);
 
-  if (!SetSchedulerPolicy(&error_msg)) {
-    fail_fn(error_msg);
-  }
+  SetSchedulerPolicy(fail_fn);
 
   const char* se_info_ptr = se_info.has_value() ? se_info.value().c_str() : nullptr;
   const char* nice_name_ptr = nice_name.has_value() ? nice_name.value().c_str() : nullptr;
@@ -864,7 +1030,7 @@
   // Make it easier to debug audit logs by setting the main thread's name to the
   // nice name rather than "app_process".
   if (nice_name.has_value()) {
-    SetThreadName(nice_name.value().c_str());
+    SetThreadName(nice_name.value());
   } else if (is_system_server) {
     SetThreadName("system_server");
   }
@@ -877,6 +1043,7 @@
     if (env->ExceptionCheck()) {
       fail_fn("Error calling post fork system server hooks.");
     }
+
     // TODO(oth): Remove hardcoded label here (b/117874058).
     static const char* kSystemServerLabel = "u:r:system_server:s0";
     if (selinux_android_setcon(kSystemServerLabel) != 0) {
@@ -980,6 +1147,74 @@
 
   return capabilities & GetEffectiveCapabilityMask(env);
 }
+
+/**
+ * Adds the given information about a newly created blastula to the Zygote's
+ * blastula table.
+ *
+ * @param blastula_pid  Process ID of the newly created blastula
+ * @param read_pipe_fd  File descriptor for the read end of the blastula
+ * reporting pipe.  Used in the ZygoteServer poll loop to track blastula
+ * specialization.
+ */
+static void AddBlastulaTableEntry(pid_t blastula_pid, int read_pipe_fd) {
+  static int sBlastulaTableInsertIndex = 0;
+
+  int search_index = sBlastulaTableInsertIndex;
+
+  do {
+    if (gBlastulaTable[search_index].SetIfInvalid(blastula_pid, read_pipe_fd)) {
+      // Start our next search right after where we finished this one.
+      sBlastulaTableInsertIndex = (search_index + 1) % gBlastulaTable.size();
+
+      return;
+    }
+
+    search_index = (search_index + 1) % gBlastulaTable.size();
+  } while (search_index != sBlastulaTableInsertIndex);
+
+  // Much like money in the banana stand, there should always be an entry
+  // in the blastula table.
+  __builtin_unreachable();
+}
+
+/**
+ * Invalidates the entry in the BlastulaTable corresponding to the provided
+ * process ID if it is present.  If an entry was removed the blastula pool
+ * count is decremented.
+ *
+ * @param blastula_pid  Process ID of the blastula entry to invalidate
+ * @return True if an entry was invalidated; false otherwise
+ */
+static bool RemoveBlastulaTableEntry(pid_t blastula_pid) {
+  for (BlastulaTableEntry& entry : gBlastulaTable) {
+    if (entry.ClearForPID(blastula_pid)) {
+      --gBlastulaPoolCount;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/**
+ * @return A vector of the read pipe FDs for each of the active blastulas.
+ */
+std::vector<int> MakeBlastulaPipeReadFDVector() {
+  std::vector<int> fd_vec;
+  fd_vec.reserve(gBlastulaTable.size());
+
+  for (BlastulaTableEntry& entry : gBlastulaTable) {
+    auto entry_values = entry.GetValues();
+
+    if (entry_values.has_value()) {
+      fd_vec.push_back(entry_values.value().read_pipe_fd);
+    }
+  }
+
+  return fd_vec;
+}
+
 }  // anonymous namespace
 
 namespace android {
@@ -998,11 +1233,34 @@
         JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
         jint runtime_flags, jobjectArray rlimits,
         jint mount_external, jstring se_info, jstring nice_name,
-        jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote,
+        jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
         jstring instruction_set, jstring app_data_dir) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
+    if (UNLIKELY(managed_fds_to_close == nullptr)) {
+      ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector.");
+    }
+
+    std::vector<int> fds_to_close =
+        ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_close).value();
+    std::vector<int> fds_to_ignore =
+        ExtractJIntArray(env, "zygote", nice_name, managed_fds_to_ignore)
+            .value_or(std::vector<int>());
+
+    std::vector<int> blastula_pipes = MakeBlastulaPipeReadFDVector();
+
+    fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end());
+    fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end());
+
+//    fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+    if (gBlastulaPoolEventFD != -1) {
+      fds_to_close.push_back(gBlastulaPoolEventFD);
+      fds_to_ignore.push_back(gBlastulaPoolEventFD);
+    }
+
     pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore);
+
     if (pid == 0) {
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                        capabilities, capabilities,
@@ -1016,9 +1274,19 @@
         JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
         jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
         jlong effective_capabilities) {
+  std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+                   fds_to_ignore(fds_to_close);
+
+//  fds_to_close.push_back(gBlastulaPoolSocketFD);
+
+  if (gBlastulaPoolEventFD != -1) {
+    fds_to_close.push_back(gBlastulaPoolEventFD);
+    fds_to_ignore.push_back(gBlastulaPoolEventFD);
+  }
+
   pid_t pid = ForkCommon(env, true,
-                         /* managed_fds_to_close= */ nullptr,
-                         /* managed_fds_to_ignore= */ nullptr);
+                         fds_to_close,
+                         fds_to_ignore);
   if (pid == 0) {
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                        permitted_capabilities, effective_capabilities,
@@ -1052,6 +1320,52 @@
   return pid;
 }
 
+/**
+ * A JNI function that forks a blastula from the Zygote while ensuring proper
+ * file descriptor hygiene.
+ *
+ * @param env  Managed runtime environment
+ * @param read_pipe_fd  The read FD for the blastula reporting pipe.  Manually closed by blastlas
+ * in managed code.
+ * @param write_pipe_fd  The write FD for the blastula reporting pipe.  Manually closed by the
+ * zygote in managed code.
+ * @param managed_session_socket_fds  A list of anonymous session sockets that must be ignored by
+ * the FD hygiene code and automatically "closed" in the new blastula.
+ * @return
+ */
+static jint com_android_internal_os_Zygote_nativeForkBlastula(JNIEnv* env, jclass,
+    jint read_pipe_fd, jint write_pipe_fd, jintArray managed_session_socket_fds) {
+  std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()),
+                   fds_to_ignore(fds_to_close);
+
+  std::vector<int> session_socket_fds =
+      ExtractJIntArray(env, "blastula", nullptr, managed_session_socket_fds)
+          .value_or(std::vector<int>());
+
+  // The Blastula Pool Event FD is created during the initialization of the
+  // blastula pool and should always be valid here.
+
+  fds_to_close.push_back(gZygoteSocketFD);
+  fds_to_close.push_back(gBlastulaPoolEventFD);
+  fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+  fds_to_ignore.push_back(gZygoteSocketFD);
+  fds_to_ignore.push_back(gBlastulaPoolSocketFD);
+  fds_to_ignore.push_back(gBlastulaPoolEventFD);
+  fds_to_ignore.push_back(read_pipe_fd);
+  fds_to_ignore.push_back(write_pipe_fd);
+  fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+  pid_t blastula_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore);
+
+  if (blastula_pid != 0) {
+    ++gBlastulaPoolCount;
+    AddBlastulaTableEntry(blastula_pid, read_pipe_fd);
+  }
+
+  return blastula_pid;
+}
+
 static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork(
         JNIEnv* env, jclass, jstring path) {
     ScopedUtfChars path_native(env, path);
@@ -1100,6 +1414,119 @@
     UnmountTree("/storage");
 }
 
+/**
+ * Called from a blastula to specialize the process for a specific application.
+ *
+ * @param env  Managed runtime environment
+ * @param uid  User ID of the new application
+ * @param gid  Group ID of the new application
+ * @param gids  Extra groups that the process belongs to
+ * @param runtime_flags  Flags for changing the behavior of the managed runtime
+ * @param rlimits  Resource limits
+ * @param mount_external  The mode (read/write/normal) that external storage will be mounted with
+ * @param se_info  SELinux policy information
+ * @param nice_name  New name for this process
+ * @param is_child_zygote  If the process is to become a WebViewZygote
+ * @param instruction_set  The instruction set expected/requested by the new application
+ * @param app_data_dir  Path to the application's data directory
+ */
+static void com_android_internal_os_Zygote_nativeSpecializeBlastula(
+    JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
+    jint runtime_flags, jobjectArray rlimits,
+    jint mount_external, jstring se_info, jstring nice_name,
+    jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
+  jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+
+  SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
+                   capabilities, capabilities,
+                   mount_external, se_info, nice_name, false,
+                   is_child_zygote == JNI_TRUE, instruction_set, app_data_dir);
+}
+
+/**
+ * A helper method for fetching socket file descriptors that were opened by init from the
+ * environment.
+ *
+ * @param env  Managed runtime environment
+ * @param is_primary  If this process is the primary or secondary Zygote; used to compute the name
+ * of the environment variable storing the file descriptors.
+ */
+static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass,
+                                                              jboolean is_primary) {
+  std::string android_socket_prefix(ANDROID_SOCKET_PREFIX);
+  std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary");
+  char* env_var_val = getenv(env_var_name.c_str());
+
+  if (env_var_val != nullptr) {
+    gZygoteSocketFD = atoi(env_var_val);
+    ALOGV("Zygote:zygoteSocketFD = %d", gZygoteSocketFD);
+  } else {
+    ALOGE("Unable to fetch Zygote socket file descriptor");
+  }
+
+  env_var_name = android_socket_prefix + (is_primary ? "blastula_pool" : "blastula_pool_secondary");
+  env_var_val = getenv(env_var_name.c_str());
+
+  if (env_var_val != nullptr) {
+    gBlastulaPoolSocketFD = atoi(env_var_val);
+    ALOGV("Zygote:blastulaPoolSocketFD = %d", gBlastulaPoolSocketFD);
+  } else {
+    ALOGE("Unable to fetch Blastula pool socket file descriptor");
+  }
+}
+
+/**
+ * @param env  Managed runtime environment
+ * @return  A managed array of raw file descriptors for the read ends of the blastula reporting
+ * pipes.
+ */
+static jintArray com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs(JNIEnv* env, jclass) {
+  std::vector<int> blastula_fds = MakeBlastulaPipeReadFDVector();
+
+  jintArray managed_blastula_fds = env->NewIntArray(blastula_fds.size());
+  env->SetIntArrayRegion(managed_blastula_fds, 0, blastula_fds.size(), blastula_fds.data());
+
+  return managed_blastula_fds;
+}
+
+/**
+ * A JNI wrapper around RemoveBlastulaTableEntry.
+ *
+ * @param env  Managed runtime environment
+ * @param blastula_pid  Process ID of the blastula entry to invalidate
+ * @return  True if an entry was invalidated; false otherwise.
+ */
+static jboolean com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry(JNIEnv* env, jclass,
+                                                                              jint blastula_pid) {
+  return RemoveBlastulaTableEntry(blastula_pid);
+}
+
+/**
+ * Creates the blastula pool event FD if it doesn't exist and returns it.  This is used by the
+ * ZygoteServer poll loop to know when to re-fill the blastula pool.
+ *
+ * @param env  Managed runtime environment
+ * @return A raw event file descriptor used to communicate (from the signal handler) when the
+ * Zygote receives a SIGCHLD for a blastula
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD(JNIEnv* env, jclass) {
+  if (gBlastulaPoolEventFD == -1) {
+    if ((gBlastulaPoolEventFD = eventfd(0, 0)) == -1) {
+      ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno)));
+    }
+  }
+
+  return gBlastulaPoolEventFD;
+}
+
+/**
+ * @param env  Managed runtime environment
+ * @return The number of blastulas currently in the blastula pool
+ */
+static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* env, jclass) {
+  return gBlastulaPoolCount;
+}
+
 static const JNINativeMethod gMethods[] = {
     { "nativeSecurityInit", "()V",
       (void *) com_android_internal_os_Zygote_nativeSecurityInit },
@@ -1113,7 +1540,22 @@
     { "nativeUnmountStorageOnInit", "()V",
       (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit },
     { "nativePreApplicationInit", "()V",
-      (void *) com_android_internal_os_Zygote_nativePreApplicationInit }
+      (void *) com_android_internal_os_Zygote_nativePreApplicationInit },
+    { "nativeForkBlastula", "(II[I)I",
+      (void *) com_android_internal_os_Zygote_nativeForkBlastula },
+    { "nativeSpecializeBlastula",
+      "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
+      (void *) com_android_internal_os_Zygote_nativeSpecializeBlastula },
+    { "nativeGetSocketFDs", "(Z)V",
+      (void *) com_android_internal_os_Zygote_nativeGetSocketFDs },
+    { "nativeGetBlastulaPipeFDs", "()[I",
+      (void *) com_android_internal_os_Zygote_nativeGetBlastulaPipeFDs },
+    { "nativeRemoveBlastulaTableEntry", "(I)Z",
+      (void *) com_android_internal_os_Zygote_nativeRemoveBlastulaTableEntry },
+    { "nativeGetBlastulaPoolEventFD", "()I",
+      (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD },
+    { "nativeGetBlastulaPoolCount", "()I",
+      (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount }
 };
 
 int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 9e7ccfd..0ed8c0c 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -131,15 +131,14 @@
 // open zygote file descriptor.
 class FileDescriptorInfo {
  public:
-  // Create a FileDescriptorInfo for a given file descriptor. Returns
-  // |NULL| if an error occurred.
-  static FileDescriptorInfo* CreateFromFd(int fd, std::string* error_msg);
+  // Create a FileDescriptorInfo for a given file descriptor.
+  static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
 
   // Checks whether the file descriptor associated with this object
   // refers to the same description.
-  bool Restat() const;
+  bool RefersToSameFile() const;
 
-  bool ReopenOrDetach(std::string* error_msg) const;
+  void ReopenOrDetach(fail_fn_t fail_fn) const;
 
   const int fd;
   const struct stat stat;
@@ -165,19 +164,18 @@
   //   address).
   static bool GetSocketName(const int fd, std::string* result);
 
-  bool DetachSocket(std::string* error_msg) const;
+  void DetachSocket(fail_fn_t fail_fn) const;
 
   DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
 };
 
 // static
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, std::string* error_msg) {
+FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
   struct stat f_stat;
   // This should never happen; the zygote should always have the right set
   // of permissions required to stat all its open files.
   if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
-    *error_msg = android::base::StringPrintf("Unable to stat %d", fd);
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Unable to stat %d", fd));
   }
 
   const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
@@ -185,15 +183,13 @@
   if (S_ISSOCK(f_stat.st_mode)) {
     std::string socket_name;
     if (!GetSocketName(fd, &socket_name)) {
-      *error_msg = "Unable to get socket name";
-      return nullptr;
+      fail_fn("Unable to get socket name");
     }
 
     if (!whitelist->IsAllowed(socket_name)) {
-      *error_msg = android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
-                                               socket_name.c_str(),
-                                               fd);
-      return nullptr;
+      fail_fn(android::base::StringPrintf("Socket name not whitelisted : %s (fd=%d)",
+                                          socket_name.c_str(),
+                                          fd));
     }
 
     return new FileDescriptorInfo(fd);
@@ -206,26 +202,35 @@
   // S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
   // S_ISLINK : Not supported.
   // S_ISBLK : Not supported.
-  // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate
-  // with the child process across forks but those should have been closed
-  // before we got to this point.
+  // S_ISFIFO : Not supported. Note that the Zygote and blastulas use pipes to
+  // communicate with the child processes across forks but those should have been
+  // added to the redirection exemption list.
   if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
-    *error_msg = android::base::StringPrintf("Unsupported st_mode %u", f_stat.st_mode);
-    return nullptr;
+    std::string mode = "Unknown";
+
+    if (S_ISDIR(f_stat.st_mode)) {
+      mode = "DIR";
+    } else if (S_ISLNK(f_stat.st_mode)) {
+      mode = "LINK";
+    } else if (S_ISBLK(f_stat.st_mode)) {
+      mode = "BLOCK";
+    } else if (S_ISFIFO(f_stat.st_mode)) {
+      mode = "FIFO";
+    }
+
+    fail_fn(android::base::StringPrintf("Unsupported st_mode for FD %d:  %s", fd, mode.c_str()));
   }
 
   std::string file_path;
   const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
   if (!android::base::Readlink(fd_path, &file_path)) {
-    *error_msg = android::base::StringPrintf("Could not read fd link %s: %s",
-                                             fd_path.c_str(),
-                                             strerror(errno));
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Could not read fd link %s: %s",
+                                        fd_path.c_str(),
+                                        strerror(errno)));
   }
 
   if (!whitelist->IsAllowed(file_path)) {
-    *error_msg = std::string("Not whitelisted : ").append(file_path);
-    return nullptr;
+    fail_fn(std::string("Not whitelisted : ").append(file_path));
   }
 
   // File descriptor flags : currently on FD_CLOEXEC. We can set these
@@ -233,11 +238,10 @@
   // there won't be any races.
   const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
   if (fd_flags == -1) {
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
-                                             fd,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFD) (%s): %s",
+                                        fd,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   // File status flags :
@@ -254,11 +258,10 @@
   //   their presence and pass them in to open().
   int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
   if (fs_flags == -1) {
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
-                                             fd,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return nullptr;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_GETFL) (%s): %s",
+                                        fd,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   // File offset : Ignore the offset for non seekable files.
@@ -273,7 +276,7 @@
   return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
 }
 
-bool FileDescriptorInfo::Restat() const {
+bool FileDescriptorInfo::RefersToSameFile() const {
   struct stat f_stat;
   if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
     PLOG(ERROR) << "Unable to restat fd " << fd;
@@ -283,9 +286,9 @@
   return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
 }
 
-bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const {
+void FileDescriptorInfo::ReopenOrDetach(fail_fn_t fail_fn) const {
   if (is_sock) {
-    return DetachSocket(error_msg);
+    return DetachSocket(fail_fn);
   }
 
   // NOTE: This might happen if the file was unlinked after being opened.
@@ -294,57 +297,50 @@
   const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
 
   if (new_fd == -1) {
-    *error_msg = android::base::StringPrintf("Failed open(%s, %i): %s",
-                                             file_path.c_str(),
-                                             open_flags,
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed open(%s, %i): %s",
+                                        file_path.c_str(),
+                                        open_flags,
+                                        strerror(errno)));
   }
 
   if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
-                                             new_fd,
-                                             fd_flags,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFD, %d) (%s): %s",
+                                        new_fd,
+                                        fd_flags,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
-                                             new_fd,
-                                             fs_flags,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed fcntl(%d, F_SETFL, %d) (%s): %s",
+                                        new_fd,
+                                        fs_flags,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
-                                             new_fd,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed lseek64(%d, SEEK_SET) (%s): %s",
+                                        new_fd,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
-  int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
-  if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) {
+  int dup_flags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
+  if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dup_flags)) == -1) {
     close(new_fd);
-    *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
-                                             fd,
-                                             new_fd,
-                                             dupFlags,
-                                             file_path.c_str(),
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
+                                        fd,
+                                        new_fd,
+                                        dup_flags,
+                                        file_path.c_str(),
+                                        strerror(errno)));
   }
 
   close(new_fd);
-
-  return true;
 }
 
 FileDescriptorInfo::FileDescriptorInfo(int fd) :
@@ -370,7 +366,6 @@
   is_sock(false) {
 }
 
-// static
 bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
   sockaddr_storage ss;
   sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
@@ -414,86 +409,75 @@
   return true;
 }
 
-bool FileDescriptorInfo::DetachSocket(std::string* error_msg) const {
+void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
   const int dev_null_fd = open("/dev/null", O_RDWR);
   if (dev_null_fd < 0) {
-    *error_msg = std::string("Failed to open /dev/null: ").append(strerror(errno));
-    return false;
+    fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
   }
 
   if (dup2(dev_null_fd, fd) == -1) {
-    *error_msg = android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
-                                             fd,
-                                             strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+                                        fd,
+                                        strerror(errno)));
   }
 
   if (close(dev_null_fd) == -1) {
-    *error_msg = android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno));
-    return false;
+    fail_fn(android::base::StringPrintf("Failed close(%d): %s", dev_null_fd, strerror(errno)));
   }
-
-  return true;
 }
 
 // static
 FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
-                                                 std::string* error_msg) {
-  DIR* d = opendir(kFdPath);
-  if (d == nullptr) {
-    *error_msg = std::string("Unable to open directory ").append(kFdPath);
-    return nullptr;
+                                                 fail_fn_t fail_fn) {
+  DIR* proc_fd_dir = opendir(kFdPath);
+  if (proc_fd_dir == nullptr) {
+    fail_fn(std::string("Unable to open directory ").append(kFdPath));
   }
-  int dir_fd = dirfd(d);
-  dirent* e;
+
+  int dir_fd = dirfd(proc_fd_dir);
+  dirent* dir_entry;
 
   std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
-  while ((e = readdir(d)) != NULL) {
-    const int fd = ParseFd(e, dir_fd);
+  while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+    const int fd = ParseFd(dir_entry, dir_fd);
     if (fd == -1) {
       continue;
     }
+
     if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
       LOG(INFO) << "Ignoring open file descriptor " << fd;
       continue;
     }
 
-    FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
-    if (info == NULL) {
-      if (closedir(d) == -1) {
-        PLOG(ERROR) << "Unable to close directory";
-      }
-      return NULL;
-    }
-    open_fd_map[fd] = info;
+    open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
   }
 
-  if (closedir(d) == -1) {
-    *error_msg = "Unable to close directory";
-    return nullptr;
+  if (closedir(proc_fd_dir) == -1) {
+    fail_fn("Unable to close directory");
   }
+
   return new FileDescriptorTable(open_fd_map);
 }
 
-bool FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg) {
+void FileDescriptorTable::Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn) {
   std::set<int> open_fds;
 
   // First get the list of open descriptors.
-  DIR* d = opendir(kFdPath);
-  if (d == NULL) {
-    *error_msg = android::base::StringPrintf("Unable to open directory %s: %s",
-                                             kFdPath,
-                                             strerror(errno));
-    return false;
+  DIR* proc_fd_dir = opendir(kFdPath);
+  if (proc_fd_dir == nullptr) {
+    fail_fn(android::base::StringPrintf("Unable to open directory %s: %s",
+                                        kFdPath,
+                                        strerror(errno)));
   }
 
-  int dir_fd = dirfd(d);
-  dirent* e;
-  while ((e = readdir(d)) != NULL) {
-    const int fd = ParseFd(e, dir_fd);
+  int dir_fd = dirfd(proc_fd_dir);
+  dirent* dir_entry;
+  while ((dir_entry = readdir(proc_fd_dir)) != nullptr) {
+    const int fd = ParseFd(dir_entry, dir_fd);
     if (fd == -1) {
       continue;
     }
+
     if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
       LOG(INFO) << "Ignoring open file descriptor " << fd;
       continue;
@@ -502,27 +486,24 @@
     open_fds.insert(fd);
   }
 
-  if (closedir(d) == -1) {
-    *error_msg = android::base::StringPrintf("Unable to close directory: %s", strerror(errno));
-    return false;
+  if (closedir(proc_fd_dir) == -1) {
+    fail_fn(android::base::StringPrintf("Unable to close directory: %s", strerror(errno)));
   }
 
-  return RestatInternal(open_fds, error_msg);
+  RestatInternal(open_fds, fail_fn);
 }
 
-// Reopens all file descriptors that are contained in the table. Returns true
-// if all descriptors were successfully re-opened or detached, and false if an
-// error occurred.
-bool FileDescriptorTable::ReopenOrDetach(std::string* error_msg) {
+// Reopens all file descriptors that are contained in the table.
+void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
   std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
   for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
     const FileDescriptorInfo* info = it->second;
-    if (info == NULL || !info->ReopenOrDetach(error_msg)) {
-      return false;
+    if (info == nullptr) {
+      return;
+    } else {
+      info->ReopenOrDetach(fail_fn);
     }
   }
-
-  return true;
 }
 
 FileDescriptorTable::FileDescriptorTable(
@@ -530,9 +511,7 @@
     : open_fd_map_(map) {
 }
 
-bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds, std::string* error_msg) {
-  bool error = false;
-
+void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
   // Iterate through the list of file descriptors we've already recorded
   // and check whether :
   //
@@ -555,28 +534,18 @@
     } else {
       // The entry from the file descriptor table is still open. Restat
       // it and check whether it refers to the same file.
-      const bool same_file = it->second->Restat();
-      if (!same_file) {
+      if (!it->second->RefersToSameFile()) {
         // The file descriptor refers to a different description. We must
         // update our entry in the table.
         delete it->second;
-        it->second = FileDescriptorInfo::CreateFromFd(*element, error_msg);
-        if (it->second == NULL) {
-          // The descriptor no longer no longer refers to a whitelisted file.
-          // We flag an error and remove it from the list of files we're
-          // tracking.
-          error = true;
-          it = open_fd_map_.erase(it);
-        } else {
-          // Successfully restatted the file, move on to the next open FD.
-          ++it;
-        }
+        it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
       } else {
         // It's the same file. Nothing to do here. Move on to the next open
         // FD.
-        ++it;
       }
 
+      ++it;
+
       // Finally, remove the FD from the set of open_fds. We do this last because
       // |element| will not remain valid after a call to erase.
       open_fds.erase(element);
@@ -595,25 +564,15 @@
     std::set<int>::const_iterator it;
     for (it = open_fds.begin(); it != open_fds.end(); ++it) {
       const int fd = (*it);
-      FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd, error_msg);
-      if (info == NULL) {
-        // A newly opened file is not on the whitelist. Flag an error and
-        // continue.
-        error = true;
-      } else {
-        // Track the newly opened file.
-        open_fd_map_[fd] = info;
-      }
+      open_fd_map_[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
     }
   }
-
-  return !error;
 }
 
 // static
-int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) {
+int FileDescriptorTable::ParseFd(dirent* dir_entry, int dir_fd) {
   char* end;
-  const int fd = strtol(e->d_name, &end, 10);
+  const int fd = strtol(dir_entry->d_name, &end, 10);
   if ((*end) != '\0') {
     return -1;
   }
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 09022a2..2caf157 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -30,6 +30,9 @@
 
 class FileDescriptorInfo;
 
+// This type is duplicated in com_android_internal_os_Zygote.cpp
+typedef const std::function<void(std::string)>& fail_fn_t;
+
 // Whitelist of open paths that the zygote is allowed to keep open.
 //
 // In addition to the paths listed in kPathWhitelist in file_utils.cpp, and
@@ -76,19 +79,19 @@
   // /proc/self/fd for the list of open file descriptors and collects
   // information about them. Returns NULL if an error occurs.
   static FileDescriptorTable* Create(const std::vector<int>& fds_to_ignore,
-                                     std::string* error_msg);
+                                     fail_fn_t fail_fn);
 
-  bool Restat(const std::vector<int>& fds_to_ignore, std::string* error_msg);
+  void Restat(const std::vector<int>& fds_to_ignore, fail_fn_t fail_fn);
 
   // Reopens all file descriptors that are contained in the table. Returns true
   // if all descriptors were successfully re-opened or detached, and false if an
   // error occurred.
-  bool ReopenOrDetach(std::string* error_msg);
+  void ReopenOrDetach(fail_fn_t fail_fn);
 
  private:
   explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
 
-  bool RestatInternal(std::set<int>& open_fds, std::string* error_msg);
+  void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
 
   static int ParseFd(dirent* e, int dir_fd);