blob: 0050f3603e612600ce132cf3665013e596fc89f0 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import org.chromium.base.ContextUtils;
import org.chromium.base.CpuFeatures;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.library_loader.Linker;
import org.chromium.base.process_launcher.ChildProcessCreationParams;
import org.chromium.base.process_launcher.FileDescriptorInfo;
import org.chromium.content.app.ChromiumLinkerParams;
import org.chromium.content.common.ContentSwitches;
import java.io.IOException;
/**
* This is the java counterpart to ChildProcessLauncherHelper. It is owned by native side and
* has an explicit destroy method.
* Each public or jni methods should have explicit documentation on what threads they are called.
*/
@JNINamespace("content::internal")
public class ChildProcessLauncherHelper {
private static final String TAG = "ChildProcLH";
// Represents an invalid process handle; same as base/process/process.h kNullProcessHandle.
private static final int NULL_PROCESS_HANDLE = 0;
// The IBinder provided to the created service.
private final IBinder mIBinderCallback;
// Note native pointer is only guaranteed live until nativeOnChildProcessStarted.
private long mNativeChildProcessLauncherHelper;
// The actual service connection. Set once we have connected to the service.
private ChildProcessConnection mChildProcessConnection;
@CalledByNative
private static FileDescriptorInfo makeFdInfo(
int id, int fd, boolean autoClose, long offset, long size) {
assert LauncherThread.runningOnLauncherThread();
ParcelFileDescriptor pFd;
if (autoClose) {
// Adopt the FD, it will be closed when we close the ParcelFileDescriptor.
pFd = ParcelFileDescriptor.adoptFd(fd);
} else {
try {
pFd = ParcelFileDescriptor.fromFd(fd);
} catch (IOException e) {
Log.e(TAG, "Invalid FD provided for process connection, aborting connection.", e);
return null;
}
}
return new FileDescriptorInfo(id, pFd, offset, size);
}
@VisibleForTesting
@CalledByNative
public static ChildProcessLauncherHelper createAndStart(long nativePointer, int paramId,
final String[] commandLine, FileDescriptorInfo[] filesToBeMapped) {
assert LauncherThread.runningOnLauncherThread();
String processType =
ContentSwitches.getSwitchValue(commandLine, ContentSwitches.SWITCH_PROCESS_TYPE);
ChildProcessCreationParams params = ChildProcessCreationParams.get(paramId);
if (paramId != ChildProcessCreationParams.DEFAULT_ID && params == null) {
throw new RuntimeException("CreationParams id " + paramId + " not found");
}
Context context = ContextUtils.getApplicationContext();
boolean sandboxed = true;
boolean alwaysInForeground = false;
if (!ContentSwitches.SWITCH_RENDERER_PROCESS.equals(processType)) {
if (params != null && !params.getPackageName().equals(context.getPackageName())) {
// WebViews and WebAPKs have renderer processes running in their applications.
// When launching these renderer processes, {@link ManagedChildProcessConnection}
// requires the package name of the application which holds the renderer process.
// Therefore, the package name in ChildProcessCreationParams could be the package
// name of WebViews, WebAPKs, or Chrome, depending on the host application.
// Except renderer process, all other child processes should use Chrome's package
// name. In WebAPK, ChildProcessCreationParams are initialized with WebAPK's
// package name. Make a copy of the WebAPK's params, but replace the package with
// Chrome's package to use when initializing a non-renderer processes.
// TODO(boliu): Should fold into |paramId|. Investigate why this is needed.
params = new ChildProcessCreationParams(context.getPackageName(),
params.getIsExternalService(), params.getLibraryProcessType(),
params.getBindToCallerCheck());
}
if (ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)) {
sandboxed = false;
alwaysInForeground = true;
} else {
// We only support sandboxed utility processes now.
assert ContentSwitches.SWITCH_UTILITY_PROCESS.equals(processType);
}
}
ChildProcessLauncherHelper process_launcher =
new ChildProcessLauncherHelper(nativePointer, processType);
process_launcher.start(
context, commandLine, filesToBeMapped, params, sandboxed, alwaysInForeground);
return process_launcher;
}
private ChildProcessLauncherHelper(long nativePointer, String processType) {
assert LauncherThread.runningOnLauncherThread();
mNativeChildProcessLauncherHelper = nativePointer;
mIBinderCallback = ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)
? new GpuProcessCallback()
: null;
initLinker();
}
private void start(Context context, String[] commandLine,
final FileDescriptorInfo[] filesToBeMapped, ChildProcessCreationParams params,
boolean sandboxed, boolean alwaysInForeground) {
boolean bindToCallerCheck = params == null ? false : params.getBindToCallerCheck();
Bundle serviceBundle = createServiceBundle(bindToCallerCheck);
onBeforeConnectionAllocated(serviceBundle);
Bundle connectionBundle = createConnectionBundle(commandLine, filesToBeMapped);
ChildProcessLauncher.start(context, serviceBundle,
connectionBundle, new ChildProcessLauncher.LaunchCallback() {
@Override
public void onChildProcessStarted(ChildProcessConnection connection) {
mChildProcessConnection = connection;
// Proactively close the FDs rather than waiting for the GC to do it.
try {
for (FileDescriptorInfo fileInfo : filesToBeMapped) {
fileInfo.fd.close();
}
} catch (IOException ioe) {
Log.w(TAG, "Failed to close FD.", ioe);
}
if (mNativeChildProcessLauncherHelper != 0) {
nativeOnChildProcessStarted(
mNativeChildProcessLauncherHelper, getPid());
}
mNativeChildProcessLauncherHelper = 0;
}
}, getIBinderCallback(), sandboxed, alwaysInForeground, params);
}
private int getPid() {
return mChildProcessConnection == null ? NULL_PROCESS_HANDLE
: mChildProcessConnection.getPid();
}
// Called on client (UI or IO) thread.
@CalledByNative
private boolean isOomProtected() {
// mChildProcessConnection is set on a different thread but does not change once it's been
// set. So it is safe to test whether it's null from a different thread.
if (mChildProcessConnection == null) {
return false;
}
// We consider the process to be child protected if it has a strong or moderate binding and
// the app is in the foreground.
return ChildProcessLauncher.isApplicationInForeground()
&& !mChildProcessConnection.isWaivedBoundOnlyOrWasWhenDied();
}
@CalledByNative
private void setInForeground(int pid, boolean foreground, boolean boostForPendingViews) {
assert LauncherThread.runningOnLauncherThread();
assert mChildProcessConnection != null;
assert getPid() == pid;
ChildProcessLauncher.getBindingManager().setPriority(pid, foreground, boostForPendingViews);
}
@CalledByNative
private static void stop(int pid) {
assert LauncherThread.runningOnLauncherThread();
ChildProcessLauncher.stop(pid);
}
// Called on UI thread.
@CalledByNative
private static int getNumberOfRendererSlots() {
final ChildProcessCreationParams params = ChildProcessCreationParams.getDefault();
final Context context = ContextUtils.getApplicationContext();
final String packageName =
params == null ? context.getPackageName() : params.getPackageName();
try {
return ChildProcessLauncher.getNumberOfSandboxedServices(context, packageName);
} catch (RuntimeException e) {
// Unittest packages do not declare services. Some tests require a realistic number
// to test child process policies, so pick a high-ish number here.
return 65535;
}
}
// Can be called on a number of threads, including launcher, and binder.
private static native void nativeOnChildProcessStarted(
long nativeChildProcessLauncherHelper, int pid);
private static boolean sLinkerInitialized;
private static long sLinkerLoadAddress;
@VisibleForTesting
static void initLinker() {
assert LauncherThread.runningOnLauncherThread();
if (sLinkerInitialized) return;
if (Linker.isUsed()) {
sLinkerLoadAddress = Linker.getInstance().getBaseLoadAddress();
if (sLinkerLoadAddress == 0) {
Log.i(TAG, "Shared RELRO support disabled!");
}
}
sLinkerInitialized = true;
}
private static ChromiumLinkerParams getLinkerParamsForNewConnection() {
assert LauncherThread.runningOnLauncherThread();
assert sLinkerInitialized;
if (sLinkerLoadAddress == 0) return null;
// Always wait for the shared RELROs in service processes.
final boolean waitForSharedRelros = true;
if (Linker.areTestsEnabled()) {
Linker linker = Linker.getInstance();
return new ChromiumLinkerParams(sLinkerLoadAddress, waitForSharedRelros,
linker.getTestRunnerClassNameForTesting(),
linker.getImplementationForTesting());
} else {
return new ChromiumLinkerParams(sLinkerLoadAddress, waitForSharedRelros);
}
}
/**
* Creates the common bundle to be passed to child processes through the service binding intent.
* If the service gets recreated by the framework the intent will be reused, so these parameters
* should be common to all processes of that type.
*
* @param commandLine Command line params to be passed to the service.
* @param linkerParams Linker params to start the service.
*/
// TODO(jcivelli): make private once warmup connection code is move from ChildProcessLauncher to
// this class and remove initLinker call.
static Bundle createServiceBundle(boolean bindToCallerCheck) {
initLinker();
Bundle bundle = new Bundle();
bundle.putBoolean(ChildProcessConstants.EXTRA_BIND_TO_CALLER, bindToCallerCheck);
bundle.putParcelable(
ChildProcessConstants.EXTRA_LINKER_PARAMS, getLinkerParamsForNewConnection());
return bundle;
}
@VisibleForTesting
public static Bundle createConnectionBundle(
String[] commandLine, FileDescriptorInfo[] filesToBeMapped) {
assert sLinkerInitialized;
Bundle bundle = new Bundle();
bundle.putStringArray(ChildProcessConstants.EXTRA_COMMAND_LINE, commandLine);
bundle.putParcelableArray(ChildProcessConstants.EXTRA_FILES, filesToBeMapped);
// content specific parameters.
bundle.putInt(ChildProcessConstants.EXTRA_CPU_COUNT, CpuFeatures.getCount());
bundle.putLong(ChildProcessConstants.EXTRA_CPU_FEATURES, CpuFeatures.getMask());
bundle.putBundle(Linker.EXTRA_LINKER_SHARED_RELROS, Linker.getInstance().getSharedRelros());
return bundle;
}
// Below are methods that will eventually be moved to a content delegate class.
private void onBeforeConnectionAllocated(Bundle commonParameters) {
// TODO(jcivelli): move createServiceBundle in there.
}
private IBinder getIBinderCallback() {
return mIBinderCallback;
}
// Testing only related methods.
@VisibleForTesting
public static ChildProcessLauncherHelper createAndStartForTesting(long nativePointer,
String[] commandLine, FileDescriptorInfo[] filesToBeMapped,
ChildProcessCreationParams creationParams, boolean sandboxed,
boolean alwaysInForeground) {
String processType =
ContentSwitches.getSwitchValue(commandLine, ContentSwitches.SWITCH_PROCESS_TYPE);
ChildProcessLauncherHelper launcherHelper =
new ChildProcessLauncherHelper(nativePointer, processType);
launcherHelper.start(ContextUtils.getApplicationContext(), commandLine, filesToBeMapped,
creationParams, sandboxed, alwaysInForeground);
return launcherHelper;
}
@VisibleForTesting
public ChildProcessConnection getChildProcessConnection() {
return mChildProcessConnection;
}
}