Upstream ChildProcessLauncher changes for Android.
Includes updates to SandboxedProcess* to support passing multiple FDs to child
processes.
Review URL: https://chromiumcodereview.appspot.com/10696025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@144801 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/base/android/jni_array.cc b/base/android/jni_array.cc
index 0a55cc5f..53027c72 100644
--- a/base/android/jni_array.cc
+++ b/base/android/jni_array.cc
@@ -114,5 +114,18 @@
AppendJavaByteArrayToByteVector(env, byte_array, out);
}
+void JavaIntArrayToIntVector(JNIEnv* env,
+ jintArray array,
+ std::vector<int>* out) {
+ DCHECK(out);
+ out->clear();
+ jsize len = env->GetArrayLength(array);
+ jint* ints = env->GetIntArrayElements(array, NULL);
+ for (jsize i = 0; i < len; ++i) {
+ out->push_back(static_cast<int>(ints[i]));
+ }
+ env->ReleaseIntArrayElements(array, ints, JNI_ABORT);
+}
+
} // namespace android
} // namespace base
diff --git a/base/android/jni_array.h b/base/android/jni_array.h
index 35e3cbbf..e9f6213 100644
--- a/base/android/jni_array.h
+++ b/base/android/jni_array.h
@@ -49,6 +49,11 @@
jbyteArray byte_array,
std::vector<uint8>* out);
+// Replaces the content of |out| with the Java ints in |int_array|.
+void JavaIntArrayToIntVector(JNIEnv* env,
+ jintArray int_array,
+ std::vector<int>* out);
+
} // namespace android
} // namespace base
diff --git a/content/app/android/sandboxed_process_service.cc b/content/app/android/sandboxed_process_service.cc
index 7c87d71..58a2687 100644
--- a/content/app/android/sandboxed_process_service.cc
+++ b/content/app/android/sandboxed_process_service.cc
@@ -4,6 +4,7 @@
#include "content/app/android/sandboxed_process_service.h"
+#include "base/android/jni_array.h"
#include "base/global_descriptors_posix.h"
#include "base/logging.h"
#include "content/common/android/surface_texture_peer.h"
@@ -14,6 +15,7 @@
using base::android::AttachCurrentThread;
using base::android::CheckException;
+using base::android::JavaIntArrayToIntVector;
namespace {
@@ -35,7 +37,7 @@
int secondary_id) {
JNIEnv* env = base::android::AttachCurrentThread();
content::Java_SandboxedProcessService_establishSurfaceTexturePeer(
- env, service_, pid, type, j_surface_texture, primary_id, secondary_id);
+ env, service_, pid, type, j_surface_texture, primary_id, secondary_id);
CheckException(env);
}
@@ -49,18 +51,22 @@
// Chrome actually uses the renderer code path for all of its sandboxed
// processes such as renderers, plugins, etc.
void InternalInitSandboxedProcess(int ipc_fd,
- int crash_fd,
+ const std::vector<int>& extra_file_ids,
+ const std::vector<int>& extra_file_fds,
JNIEnv* env,
jclass clazz,
jobject context,
jobject service) {
// Set up the IPC file descriptor mapping.
base::GlobalDescriptors::GetInstance()->Set(kPrimaryIPCChannel, ipc_fd);
-#if defined(USE_LINUX_BREAKPAD)
- if (crash_fd > 0) {
- base::GlobalDescriptors::GetInstance()->Set(kCrashDumpSignal, crash_fd);
+ // Register the extra file descriptors.
+ // This usually include the crash dump signals and resource related files.
+ DCHECK(extra_file_fds.size() == extra_file_ids.size());
+ for (size_t i = 0; i < extra_file_ids.size(); ++i) {
+ base::GlobalDescriptors::GetInstance()->Set(extra_file_ids[i],
+ extra_file_fds[i]);
}
-#endif
+
content::SurfaceTexturePeer::InitInstance(
new SurfaceTexturePeerSandboxedImpl(service));
@@ -75,13 +81,16 @@
jobject context,
jobject service,
jint ipc_fd,
- jint crash_fd) {
- InternalInitSandboxedProcess(static_cast<int>(ipc_fd),
- static_cast<int>(crash_fd), env, clazz, context, service);
+ jintArray j_extra_file_ids,
+ jintArray j_extra_file_fds) {
+ std::vector<int> extra_file_ids;
+ std::vector<int> extra_file_fds;
+ JavaIntArrayToIntVector(env, j_extra_file_ids, &extra_file_ids);
+ JavaIntArrayToIntVector(env, j_extra_file_fds, &extra_file_fds);
- // sandboxed process can't be reused. There is no need to wait for the browser
- // to unbind the service. Just exit and done.
- LOG(INFO) << "SandboxedProcessService: Drop out of SandboxedProcessMain.";
+ InternalInitSandboxedProcess(static_cast<int>(ipc_fd),
+ extra_file_ids, extra_file_fds,
+ env, clazz, context, service);
}
void ExitSandboxedProcess(JNIEnv* env, jclass clazz) {
diff --git a/content/browser/android/sandboxed_process_launcher.cc b/content/browser/android/sandboxed_process_launcher.cc
index ca660c9..d1be764 100644
--- a/content/browser/android/sandboxed_process_launcher.cc
+++ b/content/browser/android/sandboxed_process_launcher.cc
@@ -7,11 +7,13 @@
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
#include "jni/sandboxed_process_launcher_jni.h"
using base::android::AttachCurrentThread;
using base::android::ToJavaArrayOfStrings;
using base::android::ScopedJavaLocalRef;
+using base::GlobalDescriptors;
using content::StartSandboxedProcessCallback;
// Called from SandboxedProcessLauncher.java when the SandboxedProcess was
@@ -31,10 +33,10 @@
namespace content {
-ScopedJavaLocalRef<jobject> StartSandboxedProcess(
+void StartSandboxedProcess(
const CommandLine::StringVector& argv,
int ipc_fd,
- int crash_fd,
+ const GlobalDescriptors::Mapping& files_to_register,
const StartSandboxedProcessCallback& callback) {
JNIEnv* env = AttachCurrentThread();
DCHECK(env);
@@ -42,20 +44,26 @@
// Create the Command line String[]
ScopedJavaLocalRef<jobjectArray> j_argv = ToJavaArrayOfStrings(env, argv);
- return Java_SandboxedProcessLauncher_start(env,
+ ScopedJavaLocalRef<jintArray> j_file_to_register_id_files(env,
+ env->NewIntArray(files_to_register.size() * 2));
+ scoped_array<jint> file_to_register_id_files(
+ new jint[files_to_register.size() * 2]);
+ for (size_t i = 0; i < files_to_register.size(); ++i) {
+ const GlobalDescriptors::KeyFDPair& id_file = files_to_register[i];
+ file_to_register_id_files[2 * i] = id_file.first;
+ file_to_register_id_files[(2 * i) + 1] = id_file.second;
+ }
+ env->SetIntArrayRegion(j_file_to_register_id_files.obj(),
+ 0, files_to_register.size() * 2,
+ file_to_register_id_files.get());
+ Java_SandboxedProcessLauncher_start(env,
base::android::GetApplicationContext(),
static_cast<jobjectArray>(j_argv.obj()),
static_cast<jint>(ipc_fd),
- static_cast<jint>(crash_fd),
+ j_file_to_register_id_files.obj(),
reinterpret_cast<jint>(new StartSandboxedProcessCallback(callback)));
}
-void CancelStartSandboxedProcess(
- const base::android::JavaRef<jobject>& connection) {
- Java_SandboxedProcessLauncher_cancelStart(AttachCurrentThread(),
- connection.obj());
-}
-
void StopSandboxedProcess(base::ProcessHandle handle) {
JNIEnv* env = AttachCurrentThread();
DCHECK(env);
diff --git a/content/browser/android/sandboxed_process_launcher.h b/content/browser/android/sandboxed_process_launcher.h
index 3184780..13b36da 100644
--- a/content/browser/android/sandboxed_process_launcher.h
+++ b/content/browser/android/sandboxed_process_launcher.h
@@ -7,9 +7,10 @@
#include <jni.h>
-#include "base/android/scoped_java_ref.h"
#include "base/callback.h"
#include "base/command_line.h"
+#include "base/global_descriptors_posix.h"
+#include "base/platform_file.h"
#include "base/process.h"
namespace content {
@@ -17,22 +18,14 @@
typedef base::Callback<void(base::ProcessHandle)> StartSandboxedProcessCallback;
// Starts a process as a sandboxed process spawned by the Android
// ActivityManager.
-// The connection object returned may be used with a subsequent call to
-// CancelStartSandboxedProcess().
// The created process handle is returned to the |callback| on success, 0 is
// retuned if the process could not be created.
-base::android::ScopedJavaLocalRef<jobject> StartSandboxedProcess(
+void StartSandboxedProcess(
const CommandLine::StringVector& argv,
int ipc_fd,
- int crash_fd,
+ const base::GlobalDescriptors::Mapping& files_to_register,
const StartSandboxedProcessCallback& callback);
-// Cancel the starting of a sanboxed process.
-//
-// |connection| is the one returned by StartSandboxedProcess.
-void CancelStartSandboxedProcess(
- const base::android::JavaRef<jobject>& connection);
-
// Stops a sandboxed process based on the handle returned form
// StartSandboxedProcess.
void StopSandboxedProcess(base::ProcessHandle handle);
@@ -44,3 +37,4 @@
} // namespace content
#endif // CONTENT_BROWSER_ANDROID_SANDBOXED_PROCESS_LAUNCHER_H_
+
diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc
index 094ab1b..fb5ed467 100644
--- a/content/browser/child_process_launcher.cc
+++ b/content/browser/child_process_launcher.cc
@@ -25,6 +25,9 @@
#include "content/common/sandbox_policy.h"
#elif defined(OS_MACOSX)
#include "content/browser/mach_broker_mac.h"
+#elif defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "content/browser/android/sandboxed_process_launcher.h"
#elif defined(OS_POSIX)
#include "base/memory/singleton.h"
#include "content/browser/renderer_host/render_sandbox_host_linux.h"
@@ -64,6 +67,8 @@
void Launch(
#if defined(OS_WIN)
const FilePath& exposed_dir,
+#elif defined(OS_ANDROID)
+ int ipcfd,
#elif defined(OS_POSIX)
bool use_zygote,
const base::EnvironmentVector& environ,
@@ -75,6 +80,12 @@
CHECK(BrowserThread::GetCurrentThreadIdentifier(&client_thread_id_));
+#if defined(OS_ANDROID)
+ // We need to close the client end of the IPC channel to reliably detect
+ // child termination. We will close this fd after we create the child
+ // process which is asynchronous on Android.
+ ipcfd_ = ipcfd;
+#endif
BrowserThread::PostTask(
BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
base::Bind(
@@ -83,6 +94,8 @@
client_thread_id_,
#if defined(OS_WIN)
exposed_dir,
+#elif defined(OS_ANDROID)
+ ipcfd,
#elif defined(OS_POSIX)
use_zygote,
environ,
@@ -91,6 +104,27 @@
cmd_line));
}
+#if defined(OS_ANDROID)
+ static void OnSandboxedProcessStarted(
+ // |this_object| is NOT thread safe. Only use it to post a task back.
+ scoped_refptr<Context> this_object,
+ BrowserThread::ID client_thread_id,
+ base::ProcessHandle handle) {
+ if (BrowserThread::CurrentlyOn(client_thread_id)) {
+ // This is always invoked on the UI thread which is commonly the
+ // |client_thread_id| so we can shortcut one PostTask.
+ this_object->Notify(handle);
+ } else {
+ BrowserThread::PostTask(
+ client_thread_id, FROM_HERE,
+ base::Bind(
+ &ChildProcessLauncher::Context::Notify,
+ this_object,
+ handle));
+ }
+ }
+#endif
+
void ResetClient() {
// No need for locking as this function gets called on the same thread that
// client_ would be used.
@@ -116,6 +150,8 @@
BrowserThread::ID client_thread_id,
#if defined(OS_WIN)
const FilePath& exposed_dir,
+#elif defined(OS_ANDROID)
+ int ipcfd,
#elif defined(OS_POSIX)
bool use_zygote,
const base::EnvironmentVector& env,
@@ -124,10 +160,25 @@
CommandLine* cmd_line) {
scoped_ptr<CommandLine> cmd_line_deleter(cmd_line);
- base::ProcessHandle handle = base::kNullProcessHandle;
#if defined(OS_WIN)
- handle = sandbox::StartProcessWithAccess(cmd_line, exposed_dir);
+ base::ProcessHandle handle = sandbox::StartProcessWithAccess(
+ cmd_line, exposed_dir);
+#elif defined(OS_ANDROID)
+ std::string process_type =
+ cmd_line->GetSwitchValueASCII(switches::kProcessType);
+ base::GlobalDescriptors::Mapping files_to_register;
+ files_to_register.push_back(std::pair<base::GlobalDescriptors::Key, int>(
+ kPrimaryIPCChannel, ipcfd));
+ content::GetContentClient()->browser()->
+ GetAdditionalMappedFilesForChildProcess(*cmd_line, &files_to_register);
+
+ content::StartSandboxedProcess(cmd_line->argv(),
+ ipcfd, files_to_register,
+ base::Bind(&ChildProcessLauncher::Context::OnSandboxedProcessStarted,
+ this_object, client_thread_id));
+
#elif defined(OS_POSIX)
+ base::ProcessHandle handle = base::kNullProcessHandle;
// We need to close the client end of the IPC channel
// to reliably detect child termination.
file_util::ScopedFD ipcfd_closer(&ipcfd);
@@ -137,7 +188,7 @@
base::GlobalDescriptors::Mapping files_to_register;
files_to_register.push_back(std::pair<base::GlobalDescriptors::Key, int>(
kPrimaryIPCChannel, ipcfd));
-#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#if !defined(OS_MACOSX)
content::GetContentClient()->browser()->
GetAdditionalMappedFilesForChildProcess(*cmd_line, &files_to_register);
if (use_zygote) {
@@ -146,7 +197,7 @@
process_type);
} else
// Fall through to the normal posix case below when we're not zygoting.
-#endif // defined(OS_MACOSX) && !defined(OS_ANDROID)
+#endif // !defined(OS_MACOSX)
{
// Convert FD mapping to FileHandleMappingVector
base::FileHandleMappingVector fds_to_map;
@@ -158,7 +209,7 @@
id_file.first + base::GlobalDescriptors::kBaseDescriptor));
}
-#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#if !defined(OS_MACOSX)
if (process_type == switches::kRendererProcess) {
const int sandbox_fd =
RenderSandboxHostLinux::GetInstance()->GetRendererSocket();
@@ -166,7 +217,7 @@
sandbox_fd,
kSandboxIPCChannel + base::GlobalDescriptors::kBaseDescriptor));
}
-#endif // defined(OS_MACOSX) && !defined(OS_ANDROID)
+#endif // defined(OS_MACOSX)
// Actually launch the app.
base::LaunchOptions options;
@@ -204,16 +255,17 @@
handle = base::kNullProcessHandle;
}
#endif // else defined(OS_POSIX)
-
- BrowserThread::PostTask(
- client_thread_id, FROM_HERE,
- base::Bind(
- &Context::Notify,
- this_object.get(),
-#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
- use_zygote,
+#if !defined(OS_ANDROID)
+ BrowserThread::PostTask(
+ client_thread_id, FROM_HERE,
+ base::Bind(
+ &Context::Notify,
+ this_object.get(),
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+ use_zygote,
#endif
- handle));
+ handle));
+#endif // !defined(OS_ANDROID)
}
void Notify(
@@ -221,6 +273,10 @@
bool zygote,
#endif
base::ProcessHandle handle) {
+#if defined(OS_ANDROID)
+ // Finally close the ipcfd
+ file_util::ScopedFD ipcfd_closer(&ipcfd_);
+#endif
starting_ = false;
process_.set_handle(handle);
if (!handle)
@@ -267,13 +323,17 @@
bool zygote,
#endif
base::ProcessHandle handle) {
+#if defined(OS_ANDROID)
+ LOG(INFO) << "ChromeProcess: Stopping process with handle " << handle;
+ content::StopSandboxedProcess(handle);
+#else
base::Process process(handle);
// Client has gone away, so just kill the process. Using exit code 0
// means that UMA won't treat this as a crash.
process.Terminate(content::RESULT_CODE_NORMAL_EXIT);
// On POSIX, we must additionally reap the child.
#if defined(OS_POSIX)
-#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#if !defined(OS_MACOSX)
if (zygote) {
// If the renderer was created via a zygote, we have to proxy the reaping
// through the zygote process.
@@ -285,6 +345,7 @@
}
#endif // OS_POSIX
process.Close();
+#endif // defined(OS_ANDROID)
}
Client* client_;
@@ -296,8 +357,10 @@
// Controls whether the child process should be terminated on browser
// shutdown. Default behavior is to terminate the child.
bool terminate_child_on_shutdown_;
-
-#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#if defined(OS_ANDROID)
+ // The fd to close after creating the process.
+ int ipcfd_;
+#elif defined(OS_POSIX) && !defined(OS_MACOSX)
bool zygote_;
#endif
};
@@ -317,6 +380,8 @@
context_->Launch(
#if defined(OS_WIN)
exposed_dir,
+#elif defined(OS_ANDROID)
+ ipcfd,
#elif defined(OS_POSIX)
use_zygote,
environ,
diff --git a/content/public/android/java/src/org/chromium/content/app/LibraryLoader.java b/content/public/android/java/src/org/chromium/content/app/LibraryLoader.java
index 963cdd2..33a4937 100644
--- a/content/public/android/java/src/org/chromium/content/app/LibraryLoader.java
+++ b/content/public/android/java/src/org/chromium/content/app/LibraryLoader.java
@@ -21,9 +21,7 @@
public class LibraryLoader {
private static final String TAG = "LibraryLoader";
- /* TODO(jrg): resolve up and downstream discrepancy; there is no
- * upstream libchromeview.so */
- private static String sLibrary = "chromeview";
+ private static String sLibrary = null;
private static boolean sLoaded = false;
@@ -62,6 +60,13 @@
}
/**
+ * @return The name of the native library set to be loaded.
+ */
+ public static String getLibraryToLoad() {
+ return sLibrary;
+ }
+
+ /**
* This method blocks until the library is fully loaded and initialized;
* must be called on the thread that the native will call its "main" thread.
*/
@@ -168,6 +173,10 @@
* @return Whether the native library was successfully loaded.
*/
static boolean loadNow() {
+ if (sLibrary == null) {
+ assert false : "No library specified to load. Call setLibraryToLoad before first.";
+ return false;
+ }
assert !sInitialized;
try {
Log.i(TAG, "loading: " + sLibrary);
diff --git a/content/public/android/java/src/org/chromium/content/app/SandboxedProcessService.java b/content/public/android/java/src/org/chromium/content/app/SandboxedProcessService.java
index 2a3c2bd..4ce68ed 100644
--- a/content/public/android/java/src/org/chromium/content/app/SandboxedProcessService.java
+++ b/content/public/android/java/src/org/chromium/content/app/SandboxedProcessService.java
@@ -16,6 +16,8 @@
import android.util.Log;
import android.view.Surface;
+import java.util.ArrayList;
+
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.content.app.ContentMain;
@@ -43,9 +45,12 @@
// This is the native "Main" thread for the renderer / utility process.
private Thread mSandboxMainThread;
// Parameters received via IPC, only accessed while holding the mSandboxMainThread monitor.
+ private String mNativeLibraryName; // Must be passed in via the bind command.
private String[] mCommandLineParams;
private ParcelFileDescriptor mIPCFd;
- private ParcelFileDescriptor mCrashFd;
+ // Pairs IDs and file descriptors that should be registered natively.
+ private ArrayList<Integer> mExtraFileIds;
+ private ArrayList<ParcelFileDescriptor> mExtraFileFds;
private static Context sContext = null;
private boolean mLibraryInitialized = false;
@@ -66,9 +71,20 @@
// We must have received the command line by now
assert mCommandLineParams != null;
mIPCFd = args.getParcelable(SandboxedProcessConnection.EXTRA_IPC_FD);
- // mCrashFd may be null if native crash reporting is disabled.
- if (args.containsKey(SandboxedProcessConnection.EXTRA_CRASH_FD)) {
- mCrashFd = args.getParcelable(SandboxedProcessConnection.EXTRA_CRASH_FD);
+ mExtraFileIds = new ArrayList<Integer>();
+ mExtraFileFds = new ArrayList<ParcelFileDescriptor>();
+ for (int i = 0;; i++) {
+ String fdName = SandboxedProcessConnection.EXTRA_FILES_PREFIX + i
+ + SandboxedProcessConnection.EXTRA_FILES_FD_SUFFIX;
+ ParcelFileDescriptor parcel = args.getParcelable(fdName);
+ if (parcel == null) {
+ // End of the file list.
+ break;
+ }
+ mExtraFileFds.add(parcel);
+ String idName = SandboxedProcessConnection.EXTRA_FILES_PREFIX + i
+ + SandboxedProcessConnection.EXTRA_FILES_ID_SUFFIX;
+ mExtraFileIds.add(args.getInt(idName));
}
mSandboxMainThread.notifyAll();
}
@@ -95,6 +111,12 @@
@Override
public void run() {
try {
+ synchronized (mSandboxMainThread) {
+ while (mNativeLibraryName == null) {
+ mSandboxMainThread.wait();
+ }
+ }
+ LibraryLoader.setLibraryToLoad(mNativeLibraryName);
if (!LibraryLoader.loadNow()) return;
synchronized (mSandboxMainThread) {
while (mCommandLineParams == null) {
@@ -109,10 +131,17 @@
mSandboxMainThread.wait();
}
}
- int crashFd = (mCrashFd == null) ? -1 : mCrashFd.detachFd();
+ assert mExtraFileIds.size() == mExtraFileFds.size();
+ int[] extraFileIds = new int[mExtraFileIds.size()];
+ int[] extraFileFds = new int[mExtraFileFds.size()];
+ for (int i = 0; i < mExtraFileIds.size(); ++i) {
+ extraFileIds[i] = mExtraFileIds.get(i);
+ extraFileFds[i] = mExtraFileFds.get(i).detachFd();
+ }
ContentMain.initApplicationContext(sContext.getApplicationContext());
nativeInitSandboxedProcess(sContext.getApplicationContext(),
- SandboxedProcessService.this, mIPCFd.detachFd(), crashFd);
+ SandboxedProcessService.this, mIPCFd.detachFd(),
+ extraFileIds, extraFileFds);
ContentMain.start();
nativeExitSandboxedProcess();
} catch (InterruptedException e) {
@@ -158,6 +187,8 @@
stopSelf();
synchronized (mSandboxMainThread) {
+ mNativeLibraryName = intent.getStringExtra(
+ SandboxedProcessConnection.EXTRA_NATIVE_LIBRARY_NAME);
mCommandLineParams = intent.getStringArrayExtra(
SandboxedProcessConnection.EXTRA_COMMAND_LINE);
mSandboxMainThread.notifyAll();
@@ -179,8 +210,8 @@
*/
@SuppressWarnings("unused")
@CalledByNative
- private void establishSurfaceTexturePeer(int pid, int type, Object surfaceObject, int primaryID,
- int secondaryID) {
+ private void establishSurfaceTexturePeer(
+ int pid, int type, Object surfaceObject, int primaryID, int secondaryID) {
if (mCallback == null) {
Log.e(TAG, "No callback interface has been provided.");
return;
@@ -216,10 +247,13 @@
* @param applicationContext The Application Context of the current process.
* @param service The current SandboxedProcessService object.
* @param ipcFd File descriptor to use for ipc.
- * @param crashFd File descriptor for signaling crashes.
+ * @param extraFileIds (Optional) A list of pair of file IDs that should be registered for
+ * access by the renderer.
+ * @param extraFileFds (Optional) A list of pair of file descriptors that should be registered
+ * for access by the renderer.
*/
private static native void nativeInitSandboxedProcess(Context applicationContext,
- SandboxedProcessService service, int ipcFd, int crashFd);
+ SandboxedProcessService service, int ipcFd, int[] extraFileIds, int[] extraFileFds);
/**
* Force the sandboxed process to exit.
diff --git a/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessConnection.java b/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessConnection.java
index df6af2b..e6376b8 100644
--- a/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessConnection.java
+++ b/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessConnection.java
@@ -16,31 +16,32 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.chromium.base.CalledByNative;
-import org.chromium.content.app.SandboxedProcessService;
+import org.chromium.base.ThreadUtils;
import org.chromium.content.common.CommandLine;
import org.chromium.content.common.ISandboxedProcessCallback;
import org.chromium.content.common.ISandboxedProcessService;
import org.chromium.content.common.TraceEvent;
-public class SandboxedProcessConnection {
+public class SandboxedProcessConnection implements ServiceConnection {
interface DeathCallback {
void onSandboxedProcessDied(int pid);
}
// Names of items placed in the bind intent or connection bundle.
public static final String EXTRA_COMMAND_LINE =
- "com.google.android.apps.chrome.extra.sandbox_command_line";
+ "com.google.android.apps.chrome.extra.sandbox_command_line";
+ public static final String EXTRA_NATIVE_LIBRARY_NAME =
+ "com.google.android.apps.chrome.extra.sandbox_native_library_name";
// Note the FD may only be passed in the connection bundle.
public static final String EXTRA_IPC_FD = "com.google.android.apps.chrome.extra.sandbox_ipcFd";
- public static final String EXTRA_CRASH_FD =
- "com.google.android.apps.chrome.extra.sandbox_crashFd";
- public static final String EXTRA_CHROME_PAK_FD =
- "com.google.android.apps.chrome.extra.sandbox_chromePakFd";
- public static final String EXTRA_LOCALE_PAK_FD =
- "com.google.android.apps.chrome.extra.sandbox_localePakFd";
+ public static final String EXTRA_FILES_PREFIX =
+ "com.google.android.apps.chrome.extra.sandbox_extraFile_";
+ public static final String EXTRA_FILES_ID_SUFFIX = "_id";
+ public static final String EXTRA_FILES_FD_SUFFIX = "_fd";
private final Context mContext;
private final int mServiceNumber;
@@ -61,99 +62,27 @@
private static class ConnectionParams {
final String[] mCommandLine;
final int mIpcFd;
- final int mCrashFd;
+ final int[] mExtraFileIdsAndFds;
final ISandboxedProcessCallback mCallback;
final Runnable mOnConnectionCallback;
ConnectionParams(
String[] commandLine,
int ipcFd,
- int crashFd,
+ int[] idsAndFds,
ISandboxedProcessCallback callback,
Runnable onConnectionCallback) {
mCommandLine = commandLine;
mIpcFd = ipcFd;
- mCrashFd = crashFd;
+ mExtraFileIdsAndFds = idsAndFds;
mCallback = callback;
mOnConnectionCallback = onConnectionCallback;
}
- };
-
- // Implement the ServiceConnection as an inner class, so it can stem the service
- // callbacks when cancelled or unbound.
- class AsyncBoundServiceConnection extends AsyncTask<Intent, Void, Boolean>
- implements ServiceConnection {
- private boolean mIsDestroyed = false;
- private AtomicBoolean mIsBound = new AtomicBoolean(false);
-
- // AsyncTask
- @Override
- protected Boolean doInBackground(Intent... intents) {
- boolean isBound = mContext.bindService(intents[0], this, Context.BIND_AUTO_CREATE);
- mIsBound.set(isBound);
- return isBound;
- }
-
- @Override
- protected void onPostExecute(Boolean boundOK) {
- synchronized (SandboxedProcessConnection.this) {
- if (!boundOK && !mIsDestroyed) {
- SandboxedProcessConnection.this.onBindFailed();
- }
- // else: bind will complete asynchronously with a callback to onServiceConnected().
- }
- }
-
- @Override
- protected void onCancelled(Boolean boundOK) {
- // According to {@link AsyncTask#onCancelled(Object)}, the Object can be null.
- if (boundOK != null && boundOK) {
- unBindIfAble();
- }
- }
-
- /**
- * Unbinds this connection if it hasn't already been unbound. There's a guard to check that
- * we haven't already been unbound because the Browser process cancelling a connection can
- * race with something else (Android?) cancelling the connection.
- */
- private void unBindIfAble() {
- if (mIsBound.getAndSet(false)) {
- mContext.unbindService(this);
- }
- }
-
- // ServiceConnection
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (SandboxedProcessConnection.this) {
- if (!mIsDestroyed) {
- SandboxedProcessConnection.this.onServiceConnected(name, service);
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- synchronized (SandboxedProcessConnection.this) {
- if (!mIsDestroyed) {
- SandboxedProcessConnection.this.onServiceDisconnected(name);
- }
- }
- }
-
- public void destroy() {
- assert Thread.holdsLock(SandboxedProcessConnection.this);
- if (!cancel(false)) {
- unBindIfAble();
- }
- mIsDestroyed = true;
- }
}
// This is only valid while the connection is being established.
private ConnectionParams mConnectionParams;
- private AsyncBoundServiceConnection mServiceConnection;
+ private boolean mIsBound;
SandboxedProcessConnection(Context context, int number,
SandboxedProcessConnection.DeathCallback deathCallback) {
@@ -172,7 +101,7 @@
private Intent createServiceBindIntent() {
Intent intent = new Intent();
- String n = SandboxedProcessService.class.getName();
+ String n = org.chromium.content.app.SandboxedProcessService.class.getName();
intent.setClassName(mContext, n + mServiceNumber);
intent.setPackage(mContext.getPackageName());
return intent;
@@ -183,37 +112,25 @@
* to setup the connection parameters. (These methods are separated to allow the client
* to pass whatever parameters they have available here, and complete the remainder
* later while reducing the connection setup latency).
- *
+ * @param nativeLibraryName The name of the shared native library to be loaded for the
+ * sandboxed process.
* @param commandLine (Optional) Command line for the sandboxed process. If omitted, then
- * the command line parameters must instead be passed to setupConnection().
+ * the command line parameters must instead be passed to setupConnection().
*/
- synchronized void bind(String[] commandLine) {
+ synchronized void bind(String nativeLibraryName, String[] commandLine) {
TraceEvent.begin();
+ assert !ThreadUtils.runningOnUiThread();
+
final Intent intent = createServiceBindIntent();
+ intent.putExtra(EXTRA_NATIVE_LIBRARY_NAME, nativeLibraryName);
if (commandLine != null) {
intent.putExtra(EXTRA_COMMAND_LINE, commandLine);
}
- // TODO(joth): By the docs, AsyncTasks should only be created on the UI thread, but
- // bind() currently 'may' be called on any thread. In practice it's only ever called
- // from UI, but it's not guaranteed. See http://b/5694925.
- Looper mainLooper = Looper.getMainLooper();
- if (Looper.myLooper() == mainLooper) {
- mServiceConnection = new AsyncBoundServiceConnection();
- // On completion this will call back to onServiceConnected().
- mServiceConnection.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, intent);
- } else {
- // TODO(jcivelli): http://b/5694925 we only have to post to the UI thread because we use
- // an AsyncTask and it requires it. Replace that AsyncTask by running our own thread and
- // change this so we run directly from the current thread.
- new Handler(mainLooper).postAtFrontOfQueue(new Runnable() {
- public void run() {
- mServiceConnection = new AsyncBoundServiceConnection();
- // On completion this will call back to onServiceConnected().
- mServiceConnection.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
- intent);
- }
- });
+
+ mIsBound = mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ if (!mIsBound) {
+ onBindFailed();
}
TraceEvent.end();
}
@@ -223,20 +140,21 @@
* This establishes the parameters that were not already supplied in bind.
* @param commandLine (Optional) will be ignored if the command line was already sent in bind()
* @param ipcFd The file descriptor that will be used by the sandbox process for IPC.
- * @param crashFd (Optional) file descriptor that will be used for crash dumps.
+ * @param fileToRegisterIdFds (Optional) a list of pair of IDs and FDs that should be
+ * registered
* @param callback Used for status updates regarding this process connection.
* @param onConnectionCallback will be run when the connection is setup and ready to use.
*/
synchronized void setupConnection(
String[] commandLine,
int ipcFd,
- int crashFd,
+ int[] fileToRegisterIdFds,
ISandboxedProcessCallback callback,
Runnable onConnectionCallback) {
TraceEvent.begin();
assert mConnectionParams == null;
- mConnectionParams = new ConnectionParams(commandLine, ipcFd, crashFd, callback,
- onConnectionCallback);
+ mConnectionParams = new ConnectionParams(commandLine, ipcFd, fileToRegisterIdFds, callback,
+ onConnectionCallback);
if (mServiceConnectComplete) {
doConnectionSetup();
}
@@ -247,9 +165,9 @@
* Unbind the ISandboxedProcessService. It is safe to call this multiple times.
*/
synchronized void unbind() {
- if (mServiceConnection != null) {
- mServiceConnection.destroy();
- mServiceConnection = null;
+ if (mIsBound) {
+ mContext.unbindService(this);
+ mIsBound = false;
}
if (mService != null) {
if (mHighPriorityConnection != null) {
@@ -263,8 +181,8 @@
}
// Called on the main thread to notify that the service is connected.
- private void onServiceConnected(ComponentName className, IBinder service) {
- assert Thread.holdsLock(this);
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
TraceEvent.begin();
mServiceConnectComplete = true;
mService = ISandboxedProcessService.Stub.asInterface(service);
@@ -276,7 +194,6 @@
// Called on the main thread to notify that the bindService() call failed (returned false).
private void onBindFailed() {
- assert Thread.holdsLock(this);
mServiceConnectComplete = true;
if (mConnectionParams != null) {
doConnectionSetup();
@@ -296,30 +213,43 @@
if (onConnectionCallback == null) {
unbind();
} else if (mService != null) {
+ ParcelFileDescriptor ipcFdParcel;
try {
- ParcelFileDescriptor ipcFdParcel =
- ParcelFileDescriptor.fromFd(mConnectionParams.mIpcFd);
- Bundle bundle = new Bundle();
- bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine);
- bundle.putParcelable(EXTRA_IPC_FD, ipcFdParcel);
+ ipcFdParcel = ParcelFileDescriptor.fromFd(mConnectionParams.mIpcFd);
+ } catch(IOException e) {
+ Log.e(TAG, "Invalid IPC FD, aborting connection.", e);
+ return;
+ }
+ Bundle bundle = new Bundle();
+ bundle.putStringArray(EXTRA_COMMAND_LINE, mConnectionParams.mCommandLine);
+ bundle.putParcelable(EXTRA_IPC_FD, ipcFdParcel);
+ int[] idsAndFds = mConnectionParams.mExtraFileIdsAndFds;
+ assert idsAndFds.length % 2 == 0;
+ int pairLength = idsAndFds.length / 2;
+ for (int i = 0; i < pairLength; i++) {
+ String idName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_ID_SUFFIX;
+ String fdName = EXTRA_FILES_PREFIX + i + EXTRA_FILES_FD_SUFFIX;
+ ParcelFileDescriptor parcelFile;
try {
- ParcelFileDescriptor crashFdParcel =
- ParcelFileDescriptor.fromFd(mConnectionParams.mCrashFd);
- bundle.putParcelable(EXTRA_CRASH_FD, crashFdParcel);
- // We will let the GC close the crash ParcelFileDescriptor.
- } catch (java.io.IOException e) {
- Log.w(TAG, "Invalid crash Fd. Native crash reporting will be disabled.");
+ parcelFile = ParcelFileDescriptor.fromFd(idsAndFds[(2 * i) + 1]);
+ bundle.putParcelable(fdName, parcelFile);
+ bundle.putInt(idName, idsAndFds[2 * i]);
+ } catch (IOException e) {
+ Log.e(TAG, "Invalid extra file FD: id=" + idsAndFds[ 2 * i] + " fd="
+ + idsAndFds[(2 * i) + 1]);
}
-
+ }
+ try {
mPID = mService.setupConnection(bundle, mConnectionParams.mCallback);
- ipcFdParcel.close(); // We proactivley close now rather than wait for GC &
- // finalizer.
- } catch(java.io.IOException e) {
- Log.w(TAG, "Invalid ipc FD.");
- } catch(android.os.RemoteException e) {
- Log.w(TAG, "Exception when trying to call service method: "
- + e);
+ } catch (android.os.RemoteException re) {
+ Log.e(TAG, "Failed to setup connection.", re);
+ }
+ try {
+ // We proactivley close now rather than wait for GC & finalizer.
+ ipcFdParcel.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Failed to close IPC FD.", ioe);
}
}
mConnectionParams = null;
@@ -330,8 +260,8 @@
}
// Called on the main thread to notify that the sandboxed service did not disconnect gracefully.
- private void onServiceDisconnected(ComponentName className) {
- assert Thread.holdsLock(this);
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
int pid = mPID; // Stash pid & connection callback since unbind() will clear them.
Runnable onConnectionCallback =
mConnectionParams != null ? mConnectionParams.mOnConnectionCallback : null;
diff --git a/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessLauncher.java b/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessLauncher.java
index 83264c260..3627523 100644
--- a/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessLauncher.java
+++ b/content/public/android/java/src/org/chromium/content/browser/SandboxedProcessLauncher.java
@@ -13,6 +13,8 @@
import java.util.concurrent.ConcurrentHashMap;
import org.chromium.base.CalledByNative;
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.app.LibraryLoader;
import org.chromium.content.common.CommandLine;
import org.chromium.content.common.ISandboxedProcessCallback;
import org.chromium.content.common.ISandboxedProcessService;
@@ -74,7 +76,10 @@
String[] commandLine) {
SandboxedProcessConnection connection = allocateConnection(context);
if (connection != null) {
- connection.bind(commandLine);
+ String libraryName = LibraryLoader.getLibraryToLoad();
+ assert libraryName != null : "Attempting to launch a sandbox process without first "
+ + "calling LibraryLoader.setLibraryToLoad";
+ connection.bind(libraryName, commandLine);
}
return connection;
}
@@ -136,10 +141,11 @@
/**
* Should be called early in startup so the work needed to spawn the sandboxed process can
- * be done in parallel to other startup work.
+ * be done in parallel to other startup work. Must not be called on the UI thread.
* @param context the application context used for the connection.
*/
public static synchronized void warmUp(Context context) {
+ assert !ThreadUtils.runningOnUiThread();
if (mSpareConnection == null) {
mSpareConnection = allocateBoundConnection(context, null);
}
@@ -155,14 +161,13 @@
* @param commandLine The sandboxed process command line argv.
* @param ipcFd File descriptor used to set up IPC.
* @param clientContext Arbitrary parameter used by the client to distinguish this connection.
- * @return Connection object which maybe used with subsequent call to {@link #cancelStart}
*/
@CalledByNative
- static SandboxedProcessConnection start(
+ static void start(
Context context,
final String[] commandLine,
int ipcFd,
- int crashFd,
+ int[] fileToRegisterIdFds,
final int clientContext) {
assert clientContext != 0;
SandboxedProcessConnection allocatedConnection;
@@ -173,7 +178,7 @@
if (allocatedConnection == null) {
allocatedConnection = allocateBoundConnection(context, commandLine);
if (allocatedConnection == null) {
- return null;
+ return;
}
}
final SandboxedProcessConnection connection = allocatedConnection;
@@ -192,21 +197,8 @@
nativeOnSandboxedProcessStarted(clientContext, pid);
}
};
- connection.setupConnection(commandLine, ipcFd, crashFd, createCallback(), onConnect);
- return connection;
- }
-
- /**
- * Cancels a pending connection to a sandboxed process. This may be called from any thread.
- *
- * @param connection the object that was returned from the corresponding call to {@link #start}.
- */
- @CalledByNative
- static void cancelStart(SandboxedProcessConnection connection) {
- assert connection != null;
- assert !mServiceMap.containsValue(connection);
- connection.unbind();
- freeConnection(connection);
+ connection.setupConnection(commandLine, ipcFd, fileToRegisterIdFds, createCallback(),
+ onConnect);
}
/**