Merge "Create the WebViewZygote and implement WebViewZygoteInit."

This commit is contained in:
Treehugger Robot
2016-10-22 03:03:10 +00:00
committed by Gerrit Code Review
15 changed files with 589 additions and 80 deletions

View File

@ -0,0 +1,49 @@
#
# 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := webview_zygote
LOCAL_SRC_FILES := webview_zygote.cpp
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libbinder \
liblog \
libcutils \
libutils
LOCAL_LDFLAGS_32 := -Wl,--version-script,art/sigchainlib/version-script32.txt -Wl,--export-dynamic
LOCAL_LDFLAGS_64 := -Wl,--version-script,art/sigchainlib/version-script64.txt -Wl,--export-dynamic
LOCAL_WHOLE_STATIC_LIBRARIES := libsigchain
LOCAL_INIT_RC := webview_zygote32.rc
# Always include the 32-bit version of webview_zygote. If the target is 64-bit,
# also include the 64-bit webview_zygote.
ifeq ($(TARGET_SUPPORTS_64_BIT_APPS),true)
LOCAL_INIT_RC += webview_zygote64.rc
endif
LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32 := webview_zygote32
LOCAL_MODULE_STEM_64 := webview_zygote64
include $(BUILD_EXECUTABLE)

View File

@ -0,0 +1,76 @@
/*
* 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.
*/
#define LOG_TAG "WebViewZygote"
#include <sys/prctl.h>
#include <android_runtime/AndroidRuntime.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include <utils/Vector.h>
namespace android {
class WebViewRuntime : public AndroidRuntime {
public:
WebViewRuntime(char* argBlockStart, size_t argBlockSize)
: AndroidRuntime(argBlockStart, argBlockSize) {}
~WebViewRuntime() override {}
void onStarted() override {
// Nothing to do since this is a zygote server.
}
void onVmCreated(JNIEnv*) override {
// Nothing to do when the VM is created in the zygote.
}
void onZygoteInit() override {
// Called after a new process is forked.
sp<ProcessState> proc = ProcessState::self();
proc->startThreadPool();
}
void onExit(int code) override {
IPCThreadState::self()->stopProcess();
AndroidRuntime::onExit(code);
}
};
} // namespace android
int main(int argc, char* const argv[]) {
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
return 12;
}
size_t argBlockSize = 0;
for (int i = 0; i < argc; ++i) {
argBlockSize += strlen(argv[i]) + 1;
}
android::WebViewRuntime runtime(argv[0], argBlockSize);
runtime.addOption("-Xzygote");
android::Vector<android::String8> args;
runtime.start("com.android.internal.os.WebViewZygoteInit", args, /*zygote=*/ true);
}

View File

@ -0,0 +1,22 @@
#
# 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.
#
service webview_zygote32 /system/bin/webview_zygote32
user webview_zygote
socket webview_zygote stream 660 webview_zygote system
on property:init.svc.zygote=stopped
stop webview_zygote32

View File

@ -0,0 +1,22 @@
#
# 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.
#
service webview_zygote64 /system/bin/webview_zygote64
user webview_zygote
socket webview_zygote stream 660 webview_zygote system
on property:init.svc.zygote=stopped
stop webview_zygote64

View File

@ -18,6 +18,7 @@ package android.os;
import android.system.Os;
import android.util.Log;
import android.webkit.WebViewZygote;
import dalvik.system.VMRuntime;
/**
@ -130,6 +131,12 @@ public class Process {
*/
public static final int CAMERASERVER_UID = 1047;
/**
* Defines the UID/GID for the WebView zygote process.
* @hide
*/
public static final int WEBVIEW_ZYGOTE_UID = 1051;
/**
* Defines the start of a range of UIDs (and GIDs), going from this
* number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
@ -417,6 +424,22 @@ public class Process {
abi, instructionSet, appDataDir, zygoteArgs);
}
/** @hide */
public static final ProcessStartResult startWebView(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) {
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
debugFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, zygoteArgs);
}
/**
* Returns elapsed milliseconds of the time this process has run.
* @return Returns the number of milliseconds this process has return.

View File

@ -19,7 +19,9 @@ package android.os;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;
import com.android.internal.util.Preconditions;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.IOException;
@ -110,7 +112,8 @@ public class ZygoteProcess {
}
String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);
Log.i("Zygote", "Process: zygote socket " + socketAddress + " opened, supported ABIS: "
+ abiListString);
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
Arrays.asList(abiListString.split(",")));
@ -135,6 +138,13 @@ public class ZygoteProcess {
}
}
/**
* Lock object to protect access to the two ZygoteStates below. This lock must be
* acquired while communicating over the ZygoteState's socket, to prevent
* interleaved access.
*/
private final Object mLock = new Object();
/**
* The state of the connection to the primary zygote.
*/
@ -207,6 +217,7 @@ public class ZygoteProcess {
*
* @throws ZygoteStartFailedEx if the query failed.
*/
@GuardedBy("mLock")
private static String getAbiList(BufferedWriter writer, DataInputStream inputStream)
throws IOException {
// Each query starts with the argument count (1 in this case)
@ -233,6 +244,7 @@ public class ZygoteProcess {
*
* @throws ZygoteStartFailedEx if process start failed for any reason
*/
@GuardedBy("mLock")
private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
ZygoteState zygoteState, ArrayList<String> args)
throws ZygoteStartFailedEx {
@ -311,7 +323,6 @@ public class ZygoteProcess {
String appDataDir,
String[] extraArgs)
throws ZygoteStartFailedEx {
synchronized(Process.class) {
ArrayList<String> argsForZygote = new ArrayList<String>();
// --runtime-args, --setuid=, --setgid=,
@ -395,6 +406,7 @@ public class ZygoteProcess {
}
}
synchronized(mLock) {
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
}
@ -406,7 +418,9 @@ public class ZygoteProcess {
*/
public void establishZygoteConnectionForAbi(String abi) {
try {
synchronized(mLock) {
openZygoteSocketIfNeeded(abi);
}
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Unable to connect to zygote for abi: " + abi, ex);
}
@ -414,9 +428,12 @@ public class ZygoteProcess {
/**
* Tries to open socket to Zygote process if not already open. If
* already open, does nothing. May block and retry.
* already open, does nothing. May block and retry. Requires that mLock be held.
*/
@GuardedBy("mLock")
private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
try {
primaryZygoteState = ZygoteState.connect(mSocket);
@ -444,4 +461,28 @@ public class ZygoteProcess {
throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
}
/**
* 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 abi)
throws ZygoteStartFailedEx, IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
state.writer.write("3");
state.writer.newLine();
state.writer.write("--preload-package");
state.writer.newLine();
state.writer.write(packagePath);
state.writer.newLine();
state.writer.write(libsPath);
state.writer.newLine();
state.writer.flush();
}
}
}

View File

@ -472,6 +472,9 @@ public final class WebViewFactory {
// Log and discard errors at this stage as we must not crash the system server.
Log.e(LOGTAG, "error preparing webview native library", t);
}
WebViewZygote.onWebViewProviderChanged(packageInfo);
return prepareWebViewInSystemServer(nativeLibs);
}

View File

@ -0,0 +1,135 @@
/*
* 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.webkit;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.SystemService;
import android.os.ZygoteProcess;
import android.util.Log;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeoutException;
/** @hide */
public class WebViewZygote {
private static final String LOGTAG = "WebViewZygote";
private static final String WEBVIEW_ZYGOTE_SERVICE_32 = "webview_zygote32";
private static final String WEBVIEW_ZYGOTE_SERVICE_64 = "webview_zygote64";
private static ZygoteProcess sZygote;
private static PackageInfo sPackage;
private static boolean sMultiprocessEnabled = false;
public static ZygoteProcess getProcess() {
connectToZygoteIfNeeded();
return sZygote;
}
public static String getPackageName() {
return sPackage.packageName;
}
public static void setMultiprocessEnabled(boolean enabled) {
sMultiprocessEnabled = enabled;
// When toggling between multi-process being on/off, start or stop the
// service. If it is enabled and the zygote is not yet started, bring up the service.
// Otherwise, bring down the service. The name may be null if the package
// information has not yet been resolved.
final String serviceName = getServiceName();
if (serviceName == null) return;
if (enabled && sZygote == null) {
SystemService.start(serviceName);
} else {
SystemService.stop(serviceName);
sZygote = null;
}
}
public static void onWebViewProviderChanged(PackageInfo packageInfo) {
sPackage = packageInfo;
// If multi-process is not enabled, then do not start the zygote service.
if (!sMultiprocessEnabled) {
return;
}
final String serviceName = getServiceName();
if (SystemService.isStopped(serviceName)) {
SystemService.start(serviceName);
} else if (sZygote != null) {
SystemService.restart(serviceName);
}
try {
SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
} catch (TimeoutException e) {
Log.e(LOGTAG, "Timed out waiting for " + serviceName);
return;
}
connectToZygoteIfNeeded();
}
private static String getServiceName() {
if (sPackage == null)
return null;
if (Arrays.asList(Build.SUPPORTED_64_BIT_ABIS).contains(
sPackage.applicationInfo.primaryCpuAbi)) {
return WEBVIEW_ZYGOTE_SERVICE_64;
}
return WEBVIEW_ZYGOTE_SERVICE_32;
}
private static void connectToZygoteIfNeeded() {
if (sZygote != null)
return;
if (sPackage == null) {
Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
return;
}
final String serviceName = getServiceName();
if (!SystemService.isRunning(serviceName)) {
Log.e(LOGTAG, serviceName + " is not running");
return;
}
try {
sZygote = new ZygoteProcess("webview_zygote", null);
String packagePath = sPackage.applicationInfo.sourceDir;
String libsPath = sPackage.applicationInfo.nativeLibraryDir;
Log.d(LOGTAG, "Preloading package " + packagePath + " " + libsPath);
sZygote.preloadPackageForAbi(packagePath, libsPath, Build.SUPPORTED_ABIS[0]);
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to " + serviceName, e);
sZygote = null;
}
}
}

View File

@ -16,6 +16,15 @@
package com.android.internal.os;
import android.net.LocalSocket;
import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
/**
* Startup class for the WebView zygote process.
*
@ -26,7 +35,48 @@ package com.android.internal.os;
class WebViewZygoteInit {
public static final String TAG = "WebViewZygoteInit";
private static ZygoteServer sServer;
private static class WebViewZygoteServer extends ZygoteServer {
@Override
protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
throws IOException {
return new WebViewZygoteConnection(socket, abiList);
}
}
private static class WebViewZygoteConnection extends ZygoteConnection {
WebViewZygoteConnection(LocalSocket socket, String abiList) throws IOException {
super(socket, abiList);
}
@Override
protected boolean handlePreloadPackage(String packagePath, String libsPath) {
// TODO: Use preload information to setup the ClassLoader.
return false;
}
}
public static void main(String argv[]) {
throw new RuntimeException("Not implemented yet");
sServer = new WebViewZygoteServer();
// Zygote goes into its own process group.
try {
Os.setpgid(0, 0);
} catch (ErrnoException ex) {
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
try {
sServer.registerServerSocket("webview_zygote");
sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
sServer.closeServerSocket();
} catch (Zygote.MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException e) {
Log.e(TAG, "Fatal exception:", e);
}
System.exit(0);
}
}

View File

@ -43,6 +43,7 @@ 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;
/**
@ -169,6 +170,11 @@ class ZygoteConnection {
return handleAbiListQuery();
}
if (parsedArgs.preloadPackage != null) {
return handlePreloadPackage(parsedArgs.preloadPackage,
parsedArgs.preloadPackageLibs);
}
if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
throw new ZygoteSecurityException("Client may not specify capabilities: " +
"permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
@ -270,6 +276,10 @@ class ZygoteConnection {
}
}
protected boolean handlePreloadPackage(String packagePath, String libsPath) {
throw new RuntimeException("Zyogte does not support package preloading");
}
/**
* Closes socket associated with this connection.
*/
@ -374,6 +384,12 @@ class ZygoteConnection {
*/
String appDataDir;
/**
* Whether to preload a package, with the package path in the remainingArgs.
*/
String preloadPackage;
String preloadPackageLibs;
/**
* Constructs instance and parses args
* @param args zygote command-line args
@ -532,6 +548,9 @@ class ZygoteConnection {
instructionSet = arg.substring(arg.indexOf('=') + 1);
} else if (arg.startsWith("--app-data-dir=")) {
appDataDir = arg.substring(arg.indexOf('=') + 1);
} else if (arg.equals("--preload-package")) {
preloadPackage = args[++curArg];
preloadPackageLibs = args[++curArg];
} else {
break;
}
@ -541,6 +560,11 @@ class ZygoteConnection {
if (args.length - curArg > 0) {
throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
}
} else if (preloadPackage != null) {
if (args.length - curArg > 0) {
throw new IllegalArgumentException(
"Unexpected arguments after --preload-package.");
}
} else {
if (!seenRuntimeArgs) {
throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);

View File

@ -19,6 +19,7 @@ package com.android.internal.os;
import static android.system.OsConstants.POLLIN;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.system.Os;
import android.system.ErrnoException;
import android.system.StructPollfd;
@ -80,13 +81,18 @@ class ZygoteServer {
*/
private ZygoteConnection acceptCommandPeer(String abiList) {
try {
return new ZygoteConnection(mServerSocket.accept(), abiList);
return createNewConnection(mServerSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
}
protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
throws IOException {
return new ZygoteConnection(socket, abiList);
}
/**
* Close and clean up zygote sockets. Called on shutdown and on the
* child's exit path.

View File

@ -36,6 +36,7 @@ import android.util.AndroidRuntimeException;
import android.util.Log;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewZygote;
import com.android.internal.util.XmlUtils;
@ -268,6 +269,11 @@ public class SystemImpl implements SystemInterface {
return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
}
@Override
public void setMultiprocessEnabled(boolean enabled) {
WebViewZygote.setMultiprocessEnabled(enabled);
}
// flags declaring we want extra info from the package manager for webview providers
private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING

View File

@ -48,4 +48,6 @@ public interface SystemInterface {
public boolean systemIsDebuggable();
public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
throws NameNotFoundException;
public void setMultiprocessEnabled(boolean enabled);
}

View File

@ -20,7 +20,12 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Base64;
import android.util.Slog;
import android.webkit.WebViewFactory;
@ -42,6 +47,7 @@ public class WebViewUpdateServiceImpl {
private SystemInterface mSystemInterface;
private WebViewUpdater mWebViewUpdater;
private SettingsObserver mSettingsObserver;
private Context mContext;
public WebViewUpdateServiceImpl(Context context, SystemInterface systemInterface) {
@ -61,6 +67,10 @@ public class WebViewUpdateServiceImpl {
void prepareWebViewInSystemServer() {
updateFallbackStateOnBoot();
mWebViewUpdater.prepareWebViewInSystemServer();
// Register for changes in the multiprocess developer option. This has to be done
// here, since the update service gets created before the ContentResolver service.
mSettingsObserver = new SettingsObserver();
}
private boolean existsValidNonFallbackProvider(WebViewProviderInfo[] providers) {
@ -667,4 +677,41 @@ public class WebViewUpdateServiceImpl {
& ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 0));
}
/**
* Watches for changes in the WEBVIEW_MULTIPROCESS setting and lets
* the WebViewZygote know, so it can start or stop the zygote process
* appropriately.
*/
private class SettingsObserver extends ContentObserver {
private final ContentResolver mResolver;
SettingsObserver() {
super(new Handler());
mResolver = mContext.getContentResolver();
mResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.WEBVIEW_MULTIPROCESS),
false, this);
// Push the current value of the setting immediately.
notifyZygote();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
notifyZygote();
}
private void notifyZygote() {
boolean enableMultiprocess = false;
try {
enableMultiprocess = Settings.Global.getInt(mResolver,
Settings.Global.WEBVIEW_MULTIPROCESS) == 1;
} catch (Settings.SettingNotFoundException ex) {
}
mSystemInterface.setMultiprocessEnabled(enableMultiprocess);
}
}
}

View File

@ -113,4 +113,7 @@ public class TestSystemImpl implements SystemInterface {
public int getFactoryPackageVersion(String packageName) {
return 0;
}
@Override
public void setMultiprocessEnabled(boolean enabled) {}
}