Add Zygote.startChildZygote() to fork a new process that itself is a zygote.

This adds a new --start-child-zygote argument that instructs the main
zygote to create a new child process that will also be a zygote. The
system_server generates a random name in the abstract socket namespace
for it and the child-zygote to communicate over, and that is passed as
an argument to the new process.

A child-zygote bypasses the normal post-fork-child of the zygote process
in order to preserve itself as a zygote. This means not starting the
Binder threadpool nor launching into ActivityThread. Instead, a
child-zygote calls into its own main function. The main function runs a
ZygoteServer select loop, listening on the socket name specified by the
system_server when it was forked.

Unlike the system zygotes, a child-zygote can be killed without bringing
down the system. Killing a child-zygote will not terminate its child
processes, which will be reparented to init for reaping when they
eventually exit.

Bug: 63749735
Test: m (with multi-project commits landed)
Change-Id: I3e7ebbdba498f8fec1d84cdf927dc43a92be4b68
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 895be08..bb5a0ad 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -230,7 +230,7 @@
      * @param argv Argument vector for main()
      * @param classLoader the classLoader to load {@className} with
      */
-    private static Runnable findStaticMain(String className, String[] argv,
+    protected static Runnable findStaticMain(String className, String[] argv,
             ClassLoader classLoader) {
         Class<?> cl;
 
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index cadb66a..b38c851 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -129,7 +129,7 @@
 
         final Runnable caller;
         try {
-            sServer.registerServerSocket("webview_zygote");
+            sServer.registerServerSocketFromEnv("webview_zygote");
             // The select loop returns early in the child process after a fork and
             // loops forever in the zygote.
             caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 89d63f6..e23cbf8 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -71,6 +71,13 @@
 
     private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
 
+    /**
+     * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
+     * in the abstract socket namespace. This socket name is what the new child zygote
+     * should listen for connections on.
+     */
+    public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket=";
+
     private Zygote() {}
 
     /** Called for some security initialization before any fork. */
@@ -102,6 +109,8 @@
      * @param fdsToIgnore null-ok an array of ints, either null or holding
      * one or more POSIX file descriptor numbers that are to be ignored
      * in the file descriptor table check.
+     * @param startChildZygote if true, the new child process will itself be a
+     * new zygote process.
      * @param instructionSet null-ok the instruction set to use.
      * @param appDataDir null-ok the data directory of the app.
      *
@@ -110,13 +119,13 @@
      */
     public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
           int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
-          int[] fdsToIgnore, String instructionSet, String appDataDir) {
+          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, instructionSet, appDataDir);
+                  fdsToIgnore, startChildZygote, instructionSet, appDataDir);
         // Enable tracing as soon as possible for the child process.
         if (pid == 0) {
             Trace.setTracingEnabled(true, runtimeFlags);
@@ -130,7 +139,7 @@
 
     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, String instructionSet, String appDataDir);
+          int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir);
 
     /**
      * Called to do any initialization before starting an application.
@@ -190,8 +199,8 @@
     native protected static void nativeUnmountStorageOnInit();
 
     private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer,
-            String instructionSet) {
-        VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, instructionSet);
+            boolean isZygote, String instructionSet) {
+        VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
     }
 
     /**
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 6a87b1f..a32fb43 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -221,8 +221,8 @@
 
         pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                 parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
-                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
-                parsedArgs.appDataDir);
+                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
+                parsedArgs.instructionSet, parsedArgs.appDataDir);
 
         try {
             if (pid == 0) {
@@ -233,7 +233,8 @@
                 IoUtils.closeQuietly(serverPipeFd);
                 serverPipeFd = null;
 
-                return handleChildProc(parsedArgs, descriptors, childPipeFd);
+                return handleChildProc(parsedArgs, descriptors, childPipeFd,
+                        parsedArgs.startChildZygote);
             } else {
                 // In the parent. A pid < 0 indicates a failure and will be handled in
                 // handleParentProc.
@@ -415,6 +416,14 @@
         boolean preloadDefault;
 
         /**
+         * Whether this is a request to start a zygote process as a child of this zygote.
+         * Set with --start-child-zygote. The remaining arguments must include the
+         * CHILD_ZYGOTE_SOCKET_NAME_ARG flag to indicate the abstract socket name that
+         * should be used for communication.
+         */
+        boolean startChildZygote;
+
+        /**
          * Constructs instance and parses args
          * @param args zygote command-line args
          * @throws IllegalArgumentException
@@ -565,6 +574,8 @@
                     preloadPackageCacheKey = args[++curArg];
                 } else if (arg.equals("--preload-default")) {
                     preloadDefault = true;
+                } else if (arg.equals("--start-child-zygote")) {
+                    startChildZygote = true;
                 } else {
                     break;
                 }
@@ -587,6 +598,20 @@
                 remainingArgs = new String[args.length - curArg];
                 System.arraycopy(args, curArg, remainingArgs, 0, remainingArgs.length);
             }
+
+            if (startChildZygote) {
+                boolean seenChildSocketArg = false;
+                for (String arg : remainingArgs) {
+                    if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) {
+                        seenChildSocketArg = true;
+                        break;
+                    }
+                }
+                if (!seenChildSocketArg) {
+                    throw new IllegalArgumentException("--start-child-zygote specified " +
+                            "without " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG);
+                }
+            }
         }
     }
 
@@ -739,9 +764,10 @@
      * @param parsedArgs non-null; zygote args
      * @param descriptors null-ok; new file descriptors for stdio if available.
      * @param pipeFd null-ok; pipe for communication back to Zygote.
+     * @param isZygote whether this new child process is itself a new Zygote.
      */
     private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
-            FileDescriptor pipeFd) {
+            FileDescriptor pipeFd, boolean isZygote) {
         /**
          * By the time we get here, the native code has closed the two actual Zygote
          * socket connections, and substituted /dev/null in their place.  The LocalSocket
@@ -778,8 +804,13 @@
             // Should not get here.
             throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
         } else {
-            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
-                    null /* classLoader */);
+            if (!isZygote) {
+                return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
+                        null /* classLoader */);
+            } else {
+                return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
+                        parsedArgs.remainingArgs, null /* classLoader */);
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 21f1fb6..f603f4f 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -755,7 +755,7 @@
                 throw new RuntimeException("No ABI list supplied.");
             }
 
-            zygoteServer.registerServerSocket(socketName);
+            zygoteServer.registerServerSocketFromEnv(socketName);
             // In some configurations, we avoid preloading resources and classes eagerly.
             // In such cases, we will preload things prior to our first fork.
             if (!enableLazyPreload) {
@@ -870,5 +870,16 @@
         return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
     }
 
+    /**
+     * The main function called when starting a child zygote process. This is used as an
+     * alternative to zygoteInit(), which skips calling into initialization routines that
+     * start the Binder threadpool.
+     */
+    static final Runnable childZygoteInit(
+            int targetSdkVersion, String[] argv, ClassLoader classLoader) {
+        RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv);
+        return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader);
+    }
+
     private static final native void nativeZygoteInit();
 }
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 8baa15a..fecf9b9 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -44,9 +44,21 @@
 
     private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
 
+    /**
+     * Listening socket that accepts new server connections.
+     */
     private LocalServerSocket mServerSocket;
 
     /**
+     * Whether or not mServerSocket's underlying FD should be closed directly.
+     * If mServerSocket is created with an existing FD, closing the socket does
+     * not close the FD and it must be closed explicitly. If the socket is created
+     * with a name instead, then closing the socket will close the underlying FD
+     * and it should not be double-closed.
+     */
+    private boolean mCloseSocketFd;
+
+    /**
      * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
      */
     private boolean mIsForkChild;
@@ -59,11 +71,12 @@
     }
 
     /**
-     * Registers a server socket for zygote command connections
+     * Registers a server socket for zygote command connections. This locates the server socket
+     * file descriptor through an ANDROID_SOCKET_ environment variable.
      *
      * @throws RuntimeException when open fails
      */
-    void registerServerSocket(String socketName) {
+    void registerServerSocketFromEnv(String socketName) {
         if (mServerSocket == null) {
             int fileDesc;
             final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
@@ -78,6 +91,7 @@
                 FileDescriptor fd = new FileDescriptor();
                 fd.setInt$(fileDesc);
                 mServerSocket = new LocalServerSocket(fd);
+                mCloseSocketFd = true;
             } catch (IOException ex) {
                 throw new RuntimeException(
                         "Error binding to local socket '" + fileDesc + "'", ex);
@@ -86,6 +100,22 @@
     }
 
     /**
+     * Registers a server socket for zygote command connections. This opens the server socket
+     * at the specified name in the abstract socket namespace.
+     */
+    void registerServerSocketAtAbstractName(String socketName) {
+        if (mServerSocket == null) {
+            try {
+                mServerSocket = new LocalServerSocket(socketName);
+                mCloseSocketFd = false;
+            } catch (IOException ex) {
+                throw new RuntimeException(
+                        "Error binding to abstract socket '" + socketName + "'", ex);
+            }
+        }
+    }
+
+    /**
      * Waits for and accepts a single command connection. Throws
      * RuntimeException on failure.
      */
@@ -112,7 +142,7 @@
             if (mServerSocket != null) {
                 FileDescriptor fd = mServerSocket.getFileDescriptor();
                 mServerSocket.close();
-                if (fd != null) {
+                if (fd != null && mCloseSocketFd) {
                     Os.close(fd);
                 }
             }
@@ -219,6 +249,11 @@
                             Log.e(TAG, "Caught post-fork exception in child process.", e);
                             throw e;
                         }
+                    } finally {
+                        // Reset the child flag, in the event that the child process is a child-
+                        // zygote. The flag will not be consulted this loop pass after the Runnable
+                        // is returned.
+                        mIsForkChild = false;
                     }
                 }
             }