// Copyright (c) 2012 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. #include "content/browser/child_process_launcher.h" #include <utility> // For std::pair. #include "base/bind.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/process/process.h" #include "base/synchronization/lock.h" #include "base/threading/thread.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" #include "content/public/common/content_descriptors.h" #include "content/public/common/content_switches.h" #include "content/public/common/result_codes.h" #if defined(OS_WIN) #include "base/files/file_path.h" #include "content/common/sandbox_win.h" #include "content/public/common/sandbox_init.h" #include "content/public/common/sandboxed_process_launcher_delegate.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/child_process_launcher_android.h" #elif defined(OS_POSIX) #include "base/memory/shared_memory.h" #include "base/memory/singleton.h" #include "content/browser/renderer_host/render_sandbox_host_linux.h" #include "content/browser/zygote_host/zygote_host_impl_linux.h" #include "content/common/child_process_sandbox_support_impl_linux.h" #endif #if defined(OS_POSIX) #include "base/metrics/stats_table.h" #include "base/posix/global_descriptors.h" #endif namespace content { // Having the functionality of ChildProcessLauncher be in an internal // ref counted object allows us to automatically terminate the process when the // parent class destructs, while still holding on to state that we need. class ChildProcessLauncher::Context : public base::RefCountedThreadSafe<ChildProcessLauncher::Context> { public: Context() : client_(NULL), client_thread_id_(BrowserThread::UI), termination_status_(base::TERMINATION_STATUS_NORMAL_TERMINATION), exit_code_(RESULT_CODE_NORMAL_EXIT), starting_(true) #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) , zygote_(false) #endif { #if defined(OS_POSIX) terminate_child_on_shutdown_ = !CommandLine::ForCurrentProcess()-> HasSwitch(switches::kChildCleanExit); #else terminate_child_on_shutdown_ = true; #endif } void Launch( #if defined(OS_WIN) SandboxedProcessLauncherDelegate* delegate, #elif defined(OS_ANDROID) int ipcfd, #elif defined(OS_POSIX) bool use_zygote, const base::EnvironmentMap& environ, int ipcfd, #endif CommandLine* cmd_line, int child_process_id, Client* client) { client_ = client; 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( &Context::LaunchInternal, make_scoped_refptr(this), client_thread_id_, child_process_id, #if defined(OS_WIN) delegate, #elif defined(OS_ANDROID) ipcfd, #elif defined(OS_POSIX) use_zygote, environ, ipcfd, #endif cmd_line)); } #if defined(OS_ANDROID) static void OnChildProcessStarted( // |this_object| is NOT thread safe. Only use it to post a task back. scoped_refptr<Context> this_object, BrowserThread::ID client_thread_id, const base::TimeTicks begin_launch_time, base::ProcessHandle handle) { RecordHistograms(begin_launch_time); 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. CHECK(BrowserThread::CurrentlyOn(client_thread_id_)); client_ = NULL; } void set_terminate_child_on_shutdown(bool terminate_on_shutdown) { terminate_child_on_shutdown_ = terminate_on_shutdown; } private: friend class base::RefCountedThreadSafe<ChildProcessLauncher::Context>; friend class ChildProcessLauncher; ~Context() { Terminate(); } static void RecordHistograms(const base::TimeTicks begin_launch_time) { base::TimeDelta launch_time = base::TimeTicks::Now() - begin_launch_time; if (BrowserThread::CurrentlyOn(BrowserThread::PROCESS_LAUNCHER)) { RecordLaunchHistograms(launch_time); } else { BrowserThread::PostTask( BrowserThread::PROCESS_LAUNCHER, FROM_HERE, base::Bind(&ChildProcessLauncher::Context::RecordLaunchHistograms, launch_time)); } } static void RecordLaunchHistograms(const base::TimeDelta launch_time) { // Log the launch time, separating out the first one (which will likely be // slower due to the rest of the browser initializing at the same time). static bool done_first_launch = false; if (done_first_launch) { UMA_HISTOGRAM_TIMES("MPArch.ChildProcessLaunchSubsequent", launch_time); } else { UMA_HISTOGRAM_TIMES("MPArch.ChildProcessLaunchFirst", launch_time); done_first_launch = true; } } static void LaunchInternal( // |this_object| is NOT thread safe. Only use it to post a task back. scoped_refptr<Context> this_object, BrowserThread::ID client_thread_id, int child_process_id, #if defined(OS_WIN) SandboxedProcessLauncherDelegate* delegate, #elif defined(OS_ANDROID) int ipcfd, #elif defined(OS_POSIX) bool use_zygote, const base::EnvironmentMap& env, int ipcfd, #endif CommandLine* cmd_line) { scoped_ptr<CommandLine> cmd_line_deleter(cmd_line); base::TimeTicks begin_launch_time = base::TimeTicks::Now(); #if defined(OS_WIN) scoped_ptr<SandboxedProcessLauncherDelegate> delegate_deleter(delegate); base::ProcessHandle handle = StartSandboxedProcess(delegate, cmd_line); #elif defined(OS_POSIX) std::string process_type = cmd_line->GetSwitchValueASCII(switches::kProcessType); std::vector<FileDescriptorInfo> files_to_register; files_to_register.push_back( FileDescriptorInfo(kPrimaryIPCChannel, base::FileDescriptor(ipcfd, false))); base::StatsTable* stats_table = base::StatsTable::current(); if (stats_table && base::SharedMemory::IsHandleValid( stats_table->GetSharedMemoryHandle())) { files_to_register.push_back( FileDescriptorInfo(kStatsTableSharedMemFd, stats_table->GetSharedMemoryHandle())); } #endif #if defined(OS_ANDROID) // Android WebView runs in single process, ensure that we never get here // when running in single process mode. CHECK(!cmd_line->HasSwitch(switches::kSingleProcess)); GetContentClient()->browser()-> GetAdditionalMappedFilesForChildProcess(*cmd_line, child_process_id, &files_to_register); StartChildProcess(cmd_line->argv(), files_to_register, base::Bind(&ChildProcessLauncher::Context::OnChildProcessStarted, this_object, client_thread_id, begin_launch_time)); #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); #if !defined(OS_MACOSX) GetContentClient()->browser()-> GetAdditionalMappedFilesForChildProcess(*cmd_line, child_process_id, &files_to_register); if (use_zygote) { handle = ZygoteHostImpl::GetInstance()->ForkRequest(cmd_line->argv(), files_to_register, process_type); } else // Fall through to the normal posix case below when we're not zygoting. #endif // !defined(OS_MACOSX) { // Convert FD mapping to FileHandleMappingVector base::FileHandleMappingVector fds_to_map; for (size_t i = 0; i < files_to_register.size(); ++i) { fds_to_map.push_back(std::make_pair( files_to_register[i].fd.fd, files_to_register[i].id + base::GlobalDescriptors::kBaseDescriptor)); } #if !defined(OS_MACOSX) if (process_type == switches::kRendererProcess) { const int sandbox_fd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket(); fds_to_map.push_back(std::make_pair( sandbox_fd, GetSandboxFD())); } #endif // defined(OS_MACOSX) // Actually launch the app. base::LaunchOptions options; options.environ = env; options.fds_to_remap = &fds_to_map; #if defined(OS_MACOSX) // Hold the MachBroker lock for the duration of LaunchProcess. The child // will send its task port to the parent almost immediately after startup. // The Mach message will be delivered to the parent, but updating the // record of the launch will wait until after the placeholder PID is // inserted below. This ensures that while the child process may send its // port to the parent prior to the parent leaving LaunchProcess, the // order in which the record in MachBroker is updated is correct. MachBroker* broker = MachBroker::GetInstance(); broker->GetLock().Acquire(); // Make sure the MachBroker is running, and inform it to expect a // check-in from the new process. broker->EnsureRunning(); #endif // defined(OS_MACOSX) bool launched = base::LaunchProcess(*cmd_line, options, &handle); #if defined(OS_MACOSX) if (launched) broker->AddPlaceholderForPid(handle); // After updating the broker, release the lock and let the child's // messasge be processed on the broker's thread. broker->GetLock().Release(); #endif // defined(OS_MACOSX) if (!launched) handle = base::kNullProcessHandle; } #endif // else defined(OS_POSIX) #if !defined(OS_ANDROID) if (handle) RecordHistograms(begin_launch_time); 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)); #endif // !defined(OS_ANDROID) } void Notify( #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) 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) LOG(ERROR) << "Failed to launch child process"; #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) zygote_ = zygote; #endif if (client_) { client_->OnProcessLaunched(); } else { Terminate(); } } void Terminate() { if (!process_.handle()) return; if (!terminate_child_on_shutdown_) return; // On Posix, EnsureProcessTerminated can lead to 2 seconds of sleep! So // don't this on the UI/IO threads. BrowserThread::PostTask( BrowserThread::PROCESS_LAUNCHER, FROM_HERE, base::Bind( &Context::TerminateInternal, #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) zygote_, #endif process_.handle())); process_.set_handle(base::kNullProcessHandle); } static void SetProcessBackgrounded(base::ProcessHandle handle, bool background) { base::Process process(handle); process.SetProcessBackgrounded(background); } static void TerminateInternal( #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) bool zygote, #endif base::ProcessHandle handle) { #if defined(OS_ANDROID) VLOG(0) << "ChromeProcess: Stopping process with handle " << handle; StopChildProcess(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(RESULT_CODE_NORMAL_EXIT); // On POSIX, we must additionally reap the child. #if defined(OS_POSIX) #if !defined(OS_MACOSX) if (zygote) { // If the renderer was created via a zygote, we have to proxy the reaping // through the zygote process. ZygoteHostImpl::GetInstance()->EnsureProcessTerminated(handle); } else #endif // !OS_MACOSX { base::EnsureProcessTerminated(handle); } #endif // OS_POSIX process.Close(); #endif // defined(OS_ANDROID) } Client* client_; BrowserThread::ID client_thread_id_; base::Process process_; base::TerminationStatus termination_status_; int exit_code_; bool starting_; // 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_ANDROID) // The fd to close after creating the process. int ipcfd_; #elif defined(OS_POSIX) && !defined(OS_MACOSX) bool zygote_; #endif }; ChildProcessLauncher::ChildProcessLauncher( #if defined(OS_WIN) SandboxedProcessLauncherDelegate* delegate, #elif defined(OS_POSIX) bool use_zygote, const base::EnvironmentMap& environ, int ipcfd, #endif CommandLine* cmd_line, int child_process_id, Client* client) { context_ = new Context(); context_->Launch( #if defined(OS_WIN) delegate, #elif defined(OS_ANDROID) ipcfd, #elif defined(OS_POSIX) use_zygote, environ, ipcfd, #endif cmd_line, child_process_id, client); } ChildProcessLauncher::~ChildProcessLauncher() { context_->ResetClient(); } bool ChildProcessLauncher::IsStarting() { return context_->starting_; } base::ProcessHandle ChildProcessLauncher::GetHandle() { DCHECK(!context_->starting_); return context_->process_.handle(); } base::TerminationStatus ChildProcessLauncher::GetChildTerminationStatus( bool known_dead, int* exit_code) { base::ProcessHandle handle = context_->process_.handle(); if (handle == base::kNullProcessHandle) { // Process is already gone, so return the cached termination status. if (exit_code) *exit_code = context_->exit_code_; return context_->termination_status_; } #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) if (context_->zygote_) { context_->termination_status_ = ZygoteHostImpl::GetInstance()-> GetTerminationStatus(handle, known_dead, &context_->exit_code_); } else if (known_dead) { context_->termination_status_ = base::GetKnownDeadTerminationStatus(handle, &context_->exit_code_); } else { #elif defined(OS_MACOSX) if (known_dead) { context_->termination_status_ = base::GetKnownDeadTerminationStatus(handle, &context_->exit_code_); } else { #elif defined(OS_ANDROID) if (IsChildProcessOomProtected(handle)) { context_->termination_status_ = base::TERMINATION_STATUS_OOM_PROTECTED; } else { #else { #endif context_->termination_status_ = base::GetTerminationStatus(handle, &context_->exit_code_); } if (exit_code) *exit_code = context_->exit_code_; // POSIX: If the process crashed, then the kernel closed the socket // for it and so the child has already died by the time we get // here. Since GetTerminationStatus called waitpid with WNOHANG, // it'll reap the process. However, if GetTerminationStatus didn't // reap the child (because it was still running), we'll need to // Terminate via ProcessWatcher. So we can't close the handle here. if (context_->termination_status_ != base::TERMINATION_STATUS_STILL_RUNNING) context_->process_.Close(); return context_->termination_status_; } void ChildProcessLauncher::SetProcessBackgrounded(bool background) { BrowserThread::PostTask( BrowserThread::PROCESS_LAUNCHER, FROM_HERE, base::Bind( &ChildProcessLauncher::Context::SetProcessBackgrounded, GetHandle(), background)); } void ChildProcessLauncher::SetTerminateChildOnShutdown( bool terminate_on_shutdown) { if (context_.get()) context_->set_terminate_child_on_shutdown(terminate_on_shutdown); } } // namespace content