| // 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; |
| } |
| } |