Split the zygote logic out of android.os.Process into a new ZygoteProcess class.
There is no functional change. This is to support adding new types of zygotes
that all operate using the same protocol.
Bug: 21643067
(cherry picked from commit 94e824bc1b5f83024b7c9a228a8f2d0cd1347d11)
(cherry picked from commit 96b49848e7784bc4b40ffd01e2524461e1efcab7)
Change-Id: I2e12057e4c2e7567f909d699b487e70b1664cca8
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
new file mode 100644
index 0000000..d7a7296
--- /dev/null
+++ b/core/java/android/os/ZygoteProcess.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Log;
+import com.android.internal.os.Zygote;
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/*package*/ class ZygoteStartFailedEx extends Exception {
+ ZygoteStartFailedEx(String s) {
+ super(s);
+ }
+
+ ZygoteStartFailedEx(Throwable cause) {
+ super(cause);
+ }
+
+ ZygoteStartFailedEx(String s, Throwable cause) {
+ super(s, cause);
+ }
+}
+
+/**
+ * Maintains communication state with the zygote processes. This class is responsible
+ * for the sockets opened to the zygotes and for starting processes on behalf of the
+ * {@link android.os.Process} class.
+ *
+ * {@hide}
+ */
+public class ZygoteProcess {
+ private static final String LOG_TAG = "ZygoteProcess";
+
+ /**
+ * The name of the socket used to communicate with the primary zygote.
+ */
+ private final String mSocket;
+
+ /**
+ * The name of the secondary (alternate ABI) zygote socket.
+ */
+ private final String mSecondarySocket;
+
+ public ZygoteProcess(String primarySocket, String secondarySocket) {
+ mSocket = primarySocket;
+ mSecondarySocket = secondarySocket;
+ }
+
+ /**
+ * State for communicating with the zygote process.
+ */
+ public static class ZygoteState {
+ final LocalSocket socket;
+ final DataInputStream inputStream;
+ final BufferedWriter writer;
+ final List<String> abiList;
+
+ boolean mClosed;
+
+ private ZygoteState(LocalSocket socket, DataInputStream inputStream,
+ BufferedWriter writer, List<String> abiList) {
+ this.socket = socket;
+ this.inputStream = inputStream;
+ this.writer = writer;
+ this.abiList = abiList;
+ }
+
+ public static ZygoteState connect(String socketAddress) throws IOException {
+ DataInputStream zygoteInputStream = null;
+ BufferedWriter zygoteWriter = null;
+ final LocalSocket zygoteSocket = new LocalSocket();
+
+ try {
+ zygoteSocket.connect(new LocalSocketAddress(socketAddress,
+ LocalSocketAddress.Namespace.RESERVED));
+
+ zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
+
+ zygoteWriter = new BufferedWriter(new OutputStreamWriter(
+ zygoteSocket.getOutputStream()), 256);
+ } catch (IOException ex) {
+ try {
+ zygoteSocket.close();
+ } catch (IOException ignore) {
+ }
+
+ throw ex;
+ }
+
+ String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
+ Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
+
+ return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
+ Arrays.asList(abiListString.split(",")));
+ }
+
+ boolean matches(String abi) {
+ return abiList.contains(abi);
+ }
+
+ public void close() {
+ try {
+ socket.close();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG,"I/O exception on routine close", ex);
+ }
+
+ mClosed = true;
+ }
+
+ boolean isClosed() {
+ return mClosed;
+ }
+ }
+
+ /**
+ * The state of the connection to the primary zygote.
+ */
+ private ZygoteState primaryZygoteState;
+
+ /**
+ * The state of the connection to the secondary zygote.
+ */
+ private ZygoteState secondaryZygoteState;
+
+ /**
+ * Start a new process.
+ *
+ * <p>If processes are enabled, a new process is created and the
+ * static main() function of a <var>processClass</var> is executed there.
+ * The process will continue running after this function returns.
+ *
+ * <p>If processes are not enabled, a new thread in the caller's
+ * process is created and main() of <var>processClass</var> called there.
+ *
+ * <p>The niceName parameter, if not an empty string, is a custom name to
+ * give to the process instead of using processClass. This allows you to
+ * make easily identifyable processes even if you are using the same base
+ * <var>processClass</var> to start them.
+ *
+ * @param processClass The class to use as the process's main entry
+ * point.
+ * @param niceName A more readable name to use for the process.
+ * @param uid The user-id under which the process will run.
+ * @param gid The group-id under which the process will run.
+ * @param gids Additional group-ids associated with the process.
+ * @param debugFlags Additional flags.
+ * @param targetSdkVersion The target SDK version for the app.
+ * @param seInfo null-ok SELinux information for the new process.
+ * @param abi non-null the ABI this app should be started with.
+ * @param instructionSet null-ok the instruction set to use.
+ * @param appDataDir null-ok the data directory of the app.
+ * @param zygoteArgs Additional arguments to supply to the zygote process.
+ *
+ * @return An object that describes the result of the attempt to start the process.
+ * @throws RuntimeException on fatal start failure
+ */
+ public final Process.ProcessStartResult start(final String processClass,
+ final String niceName,
+ int uid, int gid, int[] gids,
+ int debugFlags, int mountExternal,
+ int targetSdkVersion,
+ String seInfo,
+ String abi,
+ String instructionSet,
+ String appDataDir,
+ String[] zygoteArgs) {
+ try {
+ return startViaZygote(processClass, niceName, uid, gid, gids,
+ debugFlags, mountExternal, targetSdkVersion, seInfo,
+ abi, instructionSet, appDataDir, zygoteArgs);
+ } catch (ZygoteStartFailedEx ex) {
+ Log.e(LOG_TAG,
+ "Starting VM process through Zygote failed");
+ throw new RuntimeException(
+ "Starting VM process through Zygote failed", ex);
+ }
+ }
+
+ /** retry interval for opening a zygote socket */
+ static final int ZYGOTE_RETRY_MILLIS = 500;
+
+ /**
+ * Queries the zygote for the list of ABIS it supports.
+ *
+ * @throws ZygoteStartFailedEx if the query failed.
+ */
+ private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
+ throws IOException {
+ // Each query starts with the argument count (1 in this case)
+ writer.write("1");
+ // ... followed by a new-line.
+ writer.newLine();
+ // ... followed by our only argument.
+ writer.write("--query-abi-list");
+ writer.newLine();
+ writer.flush();
+
+ // The response is a length prefixed stream of ASCII bytes.
+ int numBytes = inputStream.readInt();
+ byte[] bytes = new byte[numBytes];
+ inputStream.readFully(bytes);
+
+ return new String(bytes, StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Sends an argument list to the zygote process, which starts a new child
+ * and returns the child's pid. Please note: the present implementation
+ * replaces newlines in the argument list with spaces.
+ *
+ * @throws ZygoteStartFailedEx if process start failed for any reason
+ */
+ private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
+ ZygoteState zygoteState, ArrayList<String> args)
+ throws ZygoteStartFailedEx {
+ try {
+ /**
+ * See com.android.internal.os.SystemZygoteInit.readArgumentList()
+ * Presently the wire format to the zygote process is:
+ * a) a count of arguments (argc, in essence)
+ * b) a number of newline-separated argument strings equal to count
+ *
+ * After the zygote process reads these it will write the pid of
+ * the child or -1 on failure, followed by boolean to
+ * indicate whether a wrapper process was used.
+ */
+ final BufferedWriter writer = zygoteState.writer;
+ final DataInputStream inputStream = zygoteState.inputStream;
+
+ writer.write(Integer.toString(args.size()));
+ writer.newLine();
+
+ int sz = args.size();
+ for (int i = 0; i < sz; i++) {
+ String arg = args.get(i);
+ if (arg.indexOf('\n') >= 0) {
+ throw new ZygoteStartFailedEx(
+ "embedded newlines not allowed");
+ }
+ writer.write(arg);
+ writer.newLine();
+ }
+
+ writer.flush();
+
+ // Should there be a timeout on this?
+ Process.ProcessStartResult result = new Process.ProcessStartResult();
+ result.pid = inputStream.readInt();
+ if (result.pid < 0) {
+ throw new ZygoteStartFailedEx("fork() failed");
+ }
+ result.usingWrapper = inputStream.readBoolean();
+ return result;
+ } catch (IOException ex) {
+ zygoteState.close();
+ throw new ZygoteStartFailedEx(ex);
+ }
+ }
+
+ /**
+ * Starts a new process via the zygote mechanism.
+ *
+ * @param processClass Class name whose static main() to run
+ * @param niceName 'nice' process name to appear in ps
+ * @param uid a POSIX uid that the new process should setuid() to
+ * @param gid a POSIX gid that the new process shuold setgid() to
+ * @param gids null-ok; a list of supplementary group IDs that the
+ * new process should setgroup() to.
+ * @param debugFlags Additional flags.
+ * @param targetSdkVersion The target SDK version for the app.
+ * @param seInfo null-ok SELinux information for the new process.
+ * @param abi the ABI the process should use.
+ * @param instructionSet null-ok the instruction set to use.
+ * @param appDataDir null-ok the data directory of the app.
+ * @param extraArgs Additional arguments to supply to the zygote process.
+ * @return An object that describes the result of the attempt to start the process.
+ * @throws ZygoteStartFailedEx if process start failed for any reason
+ */
+ private Process.ProcessStartResult startViaZygote(final String processClass,
+ final String niceName,
+ final int uid, final int gid,
+ final int[] gids,
+ int debugFlags, int mountExternal,
+ int targetSdkVersion,
+ String seInfo,
+ String abi,
+ String instructionSet,
+ String appDataDir,
+ String[] extraArgs)
+ throws ZygoteStartFailedEx {
+ synchronized(Process.class) {
+ ArrayList<String> argsForZygote = new ArrayList<String>();
+
+ // --runtime-args, --setuid=, --setgid=,
+ // and --setgroups= must go first
+ argsForZygote.add("--runtime-args");
+ argsForZygote.add("--setuid=" + uid);
+ argsForZygote.add("--setgid=" + gid);
+ if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) {
+ argsForZygote.add("--enable-jni-logging");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
+ argsForZygote.add("--enable-safemode");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
+ argsForZygote.add("--enable-debugger");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
+ argsForZygote.add("--enable-checkjni");
+ }
+ if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
+ argsForZygote.add("--generate-debug-info");
+ }
+ if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) {
+ argsForZygote.add("--always-jit");
+ }
+ if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
+ argsForZygote.add("--native-debuggable");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
+ argsForZygote.add("--enable-assert");
+ }
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
+ argsForZygote.add("--mount-external-default");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
+ argsForZygote.add("--mount-external-read");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
+ argsForZygote.add("--mount-external-write");
+ }
+ argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
+
+ //TODO optionally enable debuger
+ //argsForZygote.add("--enable-debugger");
+
+ // --setgroups is a comma-separated list
+ if (gids != null && gids.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("--setgroups=");
+
+ int sz = gids.length;
+ for (int i = 0; i < sz; i++) {
+ if (i != 0) {
+ sb.append(',');
+ }
+ sb.append(gids[i]);
+ }
+
+ argsForZygote.add(sb.toString());
+ }
+
+ if (niceName != null) {
+ argsForZygote.add("--nice-name=" + niceName);
+ }
+
+ if (seInfo != null) {
+ argsForZygote.add("--seinfo=" + seInfo);
+ }
+
+ if (instructionSet != null) {
+ argsForZygote.add("--instruction-set=" + instructionSet);
+ }
+
+ if (appDataDir != null) {
+ argsForZygote.add("--app-data-dir=" + appDataDir);
+ }
+
+ argsForZygote.add(processClass);
+
+ if (extraArgs != null) {
+ for (String arg : extraArgs) {
+ argsForZygote.add(arg);
+ }
+ }
+
+ return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+ }
+ }
+
+ /**
+ * Tries to establish a connection to the zygote that handles a given {@code abi}. Might block
+ * and retry if the zygote is unresponsive. This method is a no-op if a connection is
+ * already open.
+ */
+ public void establishZygoteConnectionForAbi(String abi) {
+ try {
+ openZygoteSocketIfNeeded(abi);
+ } catch (ZygoteStartFailedEx ex) {
+ throw new RuntimeException("Unable to connect to zygote for abi: " + abi, ex);
+ }
+ }
+
+ /**
+ * Tries to open socket to Zygote process if not already open. If
+ * already open, does nothing. May block and retry.
+ */
+ private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
+ if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
+ try {
+ primaryZygoteState = ZygoteState.connect(mSocket);
+ } catch (IOException ioe) {
+ throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
+ }
+ }
+
+ if (primaryZygoteState.matches(abi)) {
+ return primaryZygoteState;
+ }
+
+ // The primary zygote didn't match. Try the secondary.
+ if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
+ try {
+ secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
+ } catch (IOException ioe) {
+ throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
+ }
+ }
+
+ if (secondaryZygoteState.matches(abi)) {
+ return secondaryZygoteState;
+ }
+
+ throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
+ }
+}