Merge changes Id931d441,I83faf974
* changes:
Zygote: Improve logging and error handling during connections.
Zygote: Fix race condition on package preloads.
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 7045200..93826d80 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -463,8 +463,8 @@
* Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function.
*/
- public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
- String abi) throws ZygoteStartFailedEx, IOException {
+ public boolean preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
+ String abi) throws ZygoteStartFailedEx, IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
state.writer.write("4");
@@ -483,6 +483,8 @@
state.writer.newLine();
state.writer.flush();
+
+ return (state.inputStream.readInt() == 0);
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index f4be128..66475e4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -31,6 +31,7 @@
import com.android.internal.logging.AndroidConfig;
import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.VMRuntime;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.TimeZone;
@@ -228,8 +229,8 @@
* @param argv Argument vector for main()
* @param classLoader the classLoader to load {@className} with
*/
- private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
- throws Zygote.MethodAndArgsCaller {
+ private static Runnable findStaticMain(String className, String[] argv,
+ ClassLoader classLoader) {
Class<?> cl;
try {
@@ -263,7 +264,7 @@
* clears up all the stack frames that were required in setting
* up the process.
*/
- throw new Zygote.MethodAndArgsCaller(m, argv);
+ return new MethodAndArgsCaller(m, argv);
}
public static final void main(String[] argv) {
@@ -286,8 +287,8 @@
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
- protected static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
- throws Zygote.MethodAndArgsCaller {
+ protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
+ ClassLoader classLoader) {
// If the application calls System.exit(), terminate the process
// immediately without running any shutdown hooks. It is not possible to
// shutdown an Android application gracefully. Among other things, the
@@ -300,20 +301,13 @@
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
- final Arguments args;
- try {
- args = new Arguments(argv);
- } catch (IllegalArgumentException ex) {
- Slog.e(TAG, ex.getMessage());
- // let the process exit
- return;
- }
+ final Arguments args = new Arguments(argv);
// The end of of the RuntimeInit event (see #zygoteInit).
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// Remaining arguments are passed to the start class's static main
- invokeStaticMain(args.startClass, args.startArgs, classLoader);
+ return findStaticMain(args.startClass, args.startArgs, classLoader);
}
/**
@@ -422,4 +416,37 @@
System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
}
}
+
+ /**
+ * Helper class which holds a method and arguments and can call them. This is used as part of
+ * a trampoline to get rid of the initial process setup stack frames.
+ */
+ static class MethodAndArgsCaller implements Runnable {
+ /** method to call */
+ private final Method mMethod;
+
+ /** argument array */
+ private final String[] mArgs;
+
+ public MethodAndArgsCaller(Method method, String[] args) {
+ mMethod = method;
+ mArgs = args;
+ }
+
+ public void run() {
+ try {
+ mMethod.invoke(null, new Object[] { mArgs });
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ } catch (InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index e28079f..7f46a0c 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -26,6 +26,7 @@
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -68,8 +69,7 @@
}
@Override
- protected boolean handlePreloadPackage(String packagePath, String libsPath,
- String cacheKey) {
+ protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
Log.i(TAG, "Beginning package preload");
// Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
// our children will reuse the same classloader instead of creating their own.
@@ -87,19 +87,28 @@
// Once we have the classloader, look up the WebViewFactoryProvider implementation and
// call preloadInZygote() on it to give it the opportunity to preload the native library
// and perform any other initialisation work that should be shared among the children.
+ boolean preloadSucceeded = false;
try {
Class<WebViewFactoryProvider> providerClass =
WebViewFactory.getWebViewProviderClass(loader);
Object result = providerClass.getMethod("preloadInZygote").invoke(null);
- if (!((Boolean)result).booleanValue()) {
+ preloadSucceeded = ((Boolean) result).booleanValue();
+ if (!preloadSucceeded) {
Log.e(TAG, "preloadInZygote returned false");
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
IllegalAccessException | InvocationTargetException e) {
Log.e(TAG, "Exception while preloading package", e);
}
+
+ try {
+ DataOutputStream socketOut = getSocketOutputStream();
+ socketOut.writeInt(preloadSucceeded ? 1 : 0);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+
Log.i(TAG, "Package preload done");
- return false;
}
}
@@ -113,16 +122,23 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
+ final Runnable caller;
try {
sServer.registerServerSocket("webview_zygote");
- sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
- sServer.closeServerSocket();
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
+ // 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));
} catch (RuntimeException e) {
Log.e(TAG, "Fatal exception:", e);
+ throw e;
+ } finally {
+ sServer.closeServerSocket();
}
- System.exit(0);
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
+ }
}
}
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 608bc9f..89328b2 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -25,7 +25,6 @@
import android.system.StructCapUserHeader;
import android.util.BootTimingsTraceLog;
import android.util.Slog;
-import com.android.internal.os.Zygote.MethodAndArgsCaller;
import dalvik.system.VMRuntime;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
@@ -61,37 +60,35 @@
* @param args The command-line arguments.
*/
public static void main(String[] args) {
- try {
- // Parse our mandatory arguments.
- int fdNum = Integer.parseInt(args[0], 10);
- int targetSdkVersion = Integer.parseInt(args[1], 10);
+ // Parse our mandatory arguments.
+ int fdNum = Integer.parseInt(args[0], 10);
+ int targetSdkVersion = Integer.parseInt(args[1], 10);
- // Tell the Zygote what our actual PID is (since it only knows about the
- // wrapper that it directly forked).
- if (fdNum != 0) {
- try {
- FileDescriptor fd = new FileDescriptor();
- fd.setInt$(fdNum);
- DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
- os.writeInt(Process.myPid());
- os.close();
- IoUtils.closeQuietly(fd);
- } catch (IOException ex) {
- Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
- }
+ // Tell the Zygote what our actual PID is (since it only knows about the
+ // wrapper that it directly forked).
+ if (fdNum != 0) {
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(fdNum);
+ DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+ os.writeInt(Process.myPid());
+ os.close();
+ IoUtils.closeQuietly(fd);
+ } catch (IOException ex) {
+ Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
}
-
- // Mimic system Zygote preloading.
- ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
- Trace.TRACE_TAG_DALVIK));
-
- // Launch the application.
- String[] runtimeArgs = new String[args.length - 2];
- System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
- WrapperInit.wrapperInit(targetSdkVersion, runtimeArgs);
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
}
+
+ // Mimic system Zygote preloading.
+ ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
+ Trace.TRACE_TAG_DALVIK));
+
+ // Launch the application.
+ String[] runtimeArgs = new String[args.length - 2];
+ System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
+ Runnable r = wrapperInit(targetSdkVersion, runtimeArgs);
+
+ r.run();
}
/**
@@ -142,8 +139,7 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- private static void wrapperInit(int targetSdkVersion, String[] argv)
- throws Zygote.MethodAndArgsCaller {
+ private static Runnable wrapperInit(int targetSdkVersion, String[] argv) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper");
}
@@ -165,7 +161,7 @@
// Perform the same initialization that would happen after the Zygote forks.
Zygote.nativePreApplicationInit();
- RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
/**
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e159495..c85f417 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -221,39 +221,4 @@
command.append(" '").append(arg.replace("'", "'\\''")).append("'");
}
}
-
- /**
- * Helper exception class which holds a method and arguments and
- * can call them. This is used as part of a trampoline to get rid of
- * the initial process setup stack frames.
- */
- public static class MethodAndArgsCaller extends Exception
- implements Runnable {
- /** method to call */
- private final Method mMethod;
-
- /** argument array */
- private final String[] mArgs;
-
- public MethodAndArgsCaller(Method method, String[] args) {
- mMethod = method;
- mArgs = args;
- }
-
- public void run() {
- try {
- mMethod.invoke(null, new Object[] { mArgs });
- } catch (IllegalAccessException ex) {
- throw new RuntimeException(ex);
- } catch (InvocationTargetException ex) {
- Throwable cause = ex.getCause();
- if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- } else if (cause instanceof Error) {
- throw (Error) cause;
- }
- throw new RuntimeException(ex);
- }
- }
- }
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 45cb840..0bb7326 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -30,7 +30,6 @@
import android.net.LocalSocket;
import android.os.FactoryTest;
import android.os.Process;
-import android.os.SELinux;
import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
@@ -42,14 +41,13 @@
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.io.EOFException;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
import libcore.io.IoUtils;
/**
@@ -73,6 +71,7 @@
private final BufferedReader mSocketReader;
private final Credentials peer;
private final String abiList;
+ private boolean isEof;
/**
* Constructs instance from connected socket.
@@ -99,6 +98,8 @@
Log.e(TAG, "Cannot read peer credentials", ex);
throw ex;
}
+
+ isEof = false;
}
/**
@@ -111,21 +112,14 @@
}
/**
- * Reads one start command from the command socket. If successful,
- * a child is forked and a {@link Zygote.MethodAndArgsCaller}
- * exception is thrown in that child while in the parent process,
- * the method returns normally. On failure, the child is not
- * spawned and messages are printed to the log and stderr. Returns
- * a boolean status value indicating whether an end-of-file on the command
- * socket has been encountered.
+ * Reads one start command from the command socket. If successful, a child is forked and a
+ * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
+ * process. {@code null} is always returned in the parent process (the zygote).
*
- * @return false if command socket should continue to be read from, or
- * true if an end-of-file has been encountered.
- * @throws Zygote.MethodAndArgsCaller trampoline to invoke main()
- * method in child process
+ * If the client closes the socket, an {@code EOF} condition is set, which callers can test
+ * for by calling {@code ZygoteConnection.isClosedByPeer}.
*/
- boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller {
-
+ Runnable processOneCommand(ZygoteServer zygoteServer) {
String args[];
Arguments parsedArgs = null;
FileDescriptor[] descriptors;
@@ -134,130 +128,120 @@
args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
- Log.w(TAG, "IOException on command socket " + ex.getMessage());
- closeSocket();
- return true;
+ throw new IllegalStateException("IOException on command socket", ex);
}
+ // readArgumentList returns null only when it has reached EOF with no available
+ // data to read. This will only happen when the remote socket has disconnected.
if (args == null) {
- // EOF reached.
- closeSocket();
- return true;
- }
-
- /** the stderr of the most recent request, if avail */
- PrintStream newStderr = null;
-
- if (descriptors != null && descriptors.length >= 3) {
- newStderr = new PrintStream(
- new FileOutputStream(descriptors[2]));
+ isEof = true;
+ return null;
}
int pid = -1;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
- try {
- parsedArgs = new Arguments(args);
+ parsedArgs = new Arguments(args);
- if (parsedArgs.abiListQuery) {
- return handleAbiListQuery();
- }
+ if (parsedArgs.abiListQuery) {
+ handleAbiListQuery();
+ return null;
+ }
- if (parsedArgs.preloadDefault) {
- return handlePreload();
- }
+ if (parsedArgs.preloadDefault) {
+ handlePreload();
+ return null;
+ }
- if (parsedArgs.preloadPackage != null) {
- return handlePreloadPackage(parsedArgs.preloadPackage,
- parsedArgs.preloadPackageLibs, parsedArgs.preloadPackageCacheKey);
- }
+ if (parsedArgs.preloadPackage != null) {
+ handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
+ parsedArgs.preloadPackageCacheKey);
+ return null;
+ }
- if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
- throw new ZygoteSecurityException("Client may not specify capabilities: " +
- "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
- ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
- }
+ if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: " +
+ "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
+ ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+ }
- applyUidSecurityPolicy(parsedArgs, peer);
- applyInvokeWithSecurityPolicy(parsedArgs, peer);
+ applyUidSecurityPolicy(parsedArgs, peer);
+ applyInvokeWithSecurityPolicy(parsedArgs, peer);
- applyDebuggerSystemProperty(parsedArgs);
- applyInvokeWithSystemProperty(parsedArgs);
+ applyDebuggerSystemProperty(parsedArgs);
+ applyInvokeWithSystemProperty(parsedArgs);
- int[][] rlimits = null;
+ int[][] rlimits = null;
- if (parsedArgs.rlimits != null) {
- rlimits = parsedArgs.rlimits.toArray(intArray2d);
- }
+ if (parsedArgs.rlimits != null) {
+ rlimits = parsedArgs.rlimits.toArray(intArray2d);
+ }
- int[] fdsToIgnore = null;
+ int[] fdsToIgnore = null;
- if (parsedArgs.invokeWith != null) {
+ if (parsedArgs.invokeWith != null) {
+ try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
Os.fcntlInt(childPipeFd, F_SETFD, 0);
- fdsToIgnore = new int[] { childPipeFd.getInt$(), serverPipeFd.getInt$() };
+ fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
}
-
- /**
- * In order to avoid leaking descriptors to the Zygote child,
- * the native code must close the two Zygote socket descriptors
- * in the child process before it switches from Zygote-root to
- * the UID and privileges of the application being launched.
- *
- * In order to avoid "bad file descriptor" errors when the
- * two LocalSocket objects are closed, the Posix file
- * descriptors are released via a dup2() call which closes
- * the socket and substitutes an open descriptor to /dev/null.
- */
-
- int [] fdsToClose = { -1, -1 };
-
- FileDescriptor fd = mSocket.getFileDescriptor();
-
- if (fd != null) {
- fdsToClose[0] = fd.getInt$();
- }
-
- fd = zygoteServer.getServerSocketFileDescriptor();
-
- if (fd != null) {
- fdsToClose[1] = fd.getInt$();
- }
-
- fd = null;
-
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
- parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
- parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
- parsedArgs.appDataDir);
- } catch (ErrnoException ex) {
- logAndPrintError(newStderr, "Exception creating pipe", ex);
- } catch (IllegalArgumentException ex) {
- logAndPrintError(newStderr, "Invalid zygote arguments", ex);
- } catch (ZygoteSecurityException ex) {
- logAndPrintError(newStderr,
- "Zygote security policy prevents request: ", ex);
}
+ /**
+ * In order to avoid leaking descriptors to the Zygote child,
+ * the native code must close the two Zygote socket descriptors
+ * in the child process before it switches from Zygote-root to
+ * the UID and privileges of the application being launched.
+ *
+ * In order to avoid "bad file descriptor" errors when the
+ * two LocalSocket objects are closed, the Posix file
+ * descriptors are released via a dup2() call which closes
+ * the socket and substitutes an open descriptor to /dev/null.
+ */
+
+ int [] fdsToClose = { -1, -1 };
+
+ FileDescriptor fd = mSocket.getFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[0] = fd.getInt$();
+ }
+
+ fd = zygoteServer.getServerSocketFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[1] = fd.getInt$();
+ }
+
+ fd = null;
+
+ pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
+ parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
+ parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
+ parsedArgs.appDataDir);
+
try {
if (pid == 0) {
// in child
+ zygoteServer.setForkChild();
+
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
- handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
- // should never get here, the child is expected to either
- // throw Zygote.MethodAndArgsCaller or exec().
- return true;
+ return handleChildProc(parsedArgs, descriptors, childPipeFd);
} else {
- // in parent...pid of < 0 means failure
+ // In the parent. A pid < 0 indicates a failure and will be handled in
+ // handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
- return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
+ handleParentProc(pid, descriptors, serverPipeFd);
+ return null;
}
} finally {
IoUtils.closeQuietly(childPipeFd);
@@ -265,15 +249,13 @@
}
}
- private boolean handleAbiListQuery() {
+ private void handleAbiListQuery() {
try {
final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
mSocketOutStream.writeInt(abiListBytes.length);
mSocketOutStream.write(abiListBytes);
- return false;
} catch (IOException ioe) {
- Log.e(TAG, "Error writing to command socket", ioe);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ioe);
}
}
@@ -283,7 +265,7 @@
* if no preload was initiated. The latter implies that the zygote is not configured to load
* resources lazy or that the zygote has already handled a previous request to handlePreload.
*/
- private boolean handlePreload() {
+ private void handlePreload() {
try {
if (isPreloadComplete()) {
mSocketOutStream.writeInt(1);
@@ -291,11 +273,8 @@
preload();
mSocketOutStream.writeInt(0);
}
-
- return false;
} catch (IOException ioe) {
- Log.e(TAG, "Error writing to command socket", ioe);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ioe);
}
}
@@ -307,7 +286,11 @@
return ZygoteInit.isPreloadComplete();
}
- protected boolean handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
+ protected DataOutputStream getSocketOutputStream() {
+ return mSocketOutStream;
+ }
+
+ protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
throw new RuntimeException("Zyogte does not support package preloading");
}
@@ -323,6 +306,10 @@
}
}
+ boolean isClosedByPeer() {
+ return isEof;
+ }
+
/**
* Handles argument parsing for args related to the zygote spawner.
*
@@ -753,15 +740,9 @@
* @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 newStderr null-ok; stream to use for stderr until stdio
- * is reopened.
- *
- * @throws Zygote.MethodAndArgsCaller on success to
- * trampoline to code that invokes static main.
*/
- private void handleChildProc(Arguments parsedArgs,
- FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
- throws Zygote.MethodAndArgsCaller {
+ private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+ FileDescriptor pipeFd) {
/**
* 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,7 +759,6 @@
for (FileDescriptor fd: descriptors) {
IoUtils.closeQuietly(fd);
}
- newStderr = System.err;
} catch (ErrnoException ex) {
Log.e(TAG, "Error reopening stdio", ex);
}
@@ -795,9 +775,12 @@
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
pipeFd, parsedArgs.remainingArgs);
+
+ // Should not get here.
+ throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else {
- ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion,
- parsedArgs.remainingArgs, null /* classLoader */);
+ return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
+ null /* classLoader */);
}
}
@@ -809,13 +792,8 @@
* @param descriptors null-ok; file descriptors for child's new stdio if
* specified.
* @param pipeFd null-ok; pipe for communication with child.
- * @param parsedArgs non-null; zygote args
- * @return true for "exit command loop" and false for "continue command
- * loop"
*/
- private boolean handleParentProc(int pid,
- FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
-
+ private void handleParentProc(int pid, FileDescriptor[] descriptors, FileDescriptor pipeFd) {
if (pid > 0) {
setChildPgid(pid);
}
@@ -907,11 +885,8 @@
mSocketOutStream.writeInt(pid);
mSocketOutStream.writeBoolean(usingWrapper);
} catch (IOException ex) {
- Log.e(TAG, "Error writing to command socket", ex);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ex);
}
-
- return false;
}
private void setChildPgid(int pid) {
@@ -927,20 +902,4 @@
+ "normal if peer is not in our session");
}
}
-
- /**
- * Logs an error message and prints it to the specified stream, if
- * provided
- *
- * @param newStderr null-ok; a standard error stream
- * @param message non-null; error message
- * @param ex null-ok an exception
- */
- private static void logAndPrintError (PrintStream newStderr,
- String message, Throwable ex) {
- Log.e(TAG, message, ex);
- if (newStderr != null) {
- newStderr.println(message + (ex == null ? "" : ex));
- }
- }
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 03459c0..ee19163 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -24,7 +24,6 @@
import android.icu.impl.CacheValue;
import android.icu.text.DecimalFormatSymbols;
import android.icu.util.ULocale;
-import android.net.LocalServerSocket;
import android.opengl.EGL14;
import android.os.Build;
import android.os.IInstalld;
@@ -447,10 +446,7 @@
/**
* Finish remaining work for the newly forked system server process.
*/
- private static void handleSystemServerProcess(
- ZygoteConnection.Arguments parsedArgs)
- throws Zygote.MethodAndArgsCaller {
-
+ private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
// set umask to 0077 so new files and directories will default to owner-only permissions.
Os.umask(S_IRWXG | S_IRWXO);
@@ -496,6 +492,8 @@
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(), null, args);
+
+ throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
@@ -507,7 +505,7 @@
/*
* Pass the remaining arguments to SystemServer.
*/
- ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+ return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
/* should never reach here */
@@ -589,10 +587,13 @@
}
/**
- * Prepare the arguments and fork for the system server process.
+ * Prepare the arguments and forks for the system server process.
+ *
+ * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
+ * child process, and {@code null} in the parent.
*/
- private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)
- throws Zygote.MethodAndArgsCaller, RuntimeException {
+ private static Runnable forkSystemServer(String abiList, String socketName,
+ ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL,
@@ -657,10 +658,10 @@
}
zygoteServer.closeServerSocket();
- handleSystemServerProcess(parsedArgs);
+ return handleSystemServerProcess(parsedArgs);
}
- return true;
+ return null;
}
/**
@@ -691,6 +692,7 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
+ final Runnable caller;
try {
// Report Zygote start time to tron unless it is a runtime restart
if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -760,19 +762,32 @@
ZygoteHooks.stopZygoteNoThreadCreation();
if (startSystemServer) {
- startSystemServer(abiList, socketName, zygoteServer);
+ Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
+
+ // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
+ // child (system_server) process.
+ if (r != null) {
+ r.run();
+ return;
+ }
}
Log.i(TAG, "Accepting command socket connections");
- zygoteServer.runSelectLoop(abiList);
- zygoteServer.closeServerSocket();
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
- zygoteServer.closeServerSocket();
throw ex;
+ } finally {
+ zygoteServer.closeServerSocket();
+ }
+
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
}
}
@@ -830,8 +845,7 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- public static final void zygoteInit(int targetSdkVersion, String[] argv,
- ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
+ public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
@@ -841,7 +855,7 @@
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
- RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, argv, 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 126d9e7..8baa15a 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -25,6 +25,7 @@
import android.system.StructPollfd;
import android.util.Log;
+import android.util.Slog;
import java.io.IOException;
import java.io.FileDescriptor;
import java.util.ArrayList;
@@ -45,9 +46,18 @@
private LocalServerSocket mServerSocket;
+ /**
+ * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
+ */
+ private boolean mIsForkChild;
+
ZygoteServer() {
}
+ void setForkChild() {
+ mIsForkChild = true;
+ }
+
/**
* Registers a server socket for zygote command connections
*
@@ -129,11 +139,8 @@
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
* worth at a time.
- *
- * @throws Zygote.MethodAndArgsCaller in a child process when a main()
- * should be executed.
*/
- void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
+ Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
@@ -156,15 +163,62 @@
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
+
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
- boolean done = peers.get(i).runOnce(this);
- if (done) {
- peers.remove(i);
- fds.remove(i);
+ try {
+ ZygoteConnection connection = peers.get(i);
+ final Runnable command = connection.processOneCommand(this);
+
+ if (mIsForkChild) {
+ // We're in the child. We should always have a command to run at this
+ // stage if processOneCommand hasn't called "exec".
+ if (command == null) {
+ throw new IllegalStateException("command == null");
+ }
+
+ return command;
+ } else {
+ // We're in the server - we should never have any commands to run.
+ if (command != null) {
+ throw new IllegalStateException("command != null");
+ }
+
+ // We don't know whether the remote side of the socket was closed or
+ // not until we attempt to read from it from processOneCommand. This shows up as
+ // a regular POLLIN event in our regular processing loop.
+ if (connection.isClosedByPeer()) {
+ connection.closeSocket();
+ peers.remove(i);
+ fds.remove(i);
+ }
+ }
+ } catch (Exception e) {
+ if (!mIsForkChild) {
+ // We're in the server so any exception here is one that has taken place
+ // pre-fork while processing commands or reading / writing from the
+ // control socket. Make a loud noise about any such exceptions so that
+ // we know exactly what failed and why.
+
+ Slog.e(TAG, "Exception executing zygote command: ", e);
+
+ // Make sure the socket is closed so that the other end knows immediately
+ // that something has gone wrong and doesn't time out waiting for a
+ // response.
+ ZygoteConnection conn = peers.remove(i);
+ conn.closeSocket();
+
+ fds.remove(i);
+ } else {
+ // We're in the child so any exception caught here has happened post
+ // fork and before we execute ActivityThread.main (or any other main()
+ // method). Log the details of the exception and bring down the process.
+ Log.e(TAG, "Caught post-fork exception in child process.", e);
+ throw e;
+ }
}
}
}