// Copyright (c) 2011 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 // For std::pair. #include "base/command_line.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" #include "base/threading/thread.h" #include "content/browser/browser_thread.h" #include "content/browser/content_browser_client.h" #include "content/common/chrome_descriptors.h" #include "content/common/content_switches.h" #include "content/common/process_watcher.h" #include "content/common/result_codes.h" #if defined(OS_WIN) #include "base/file_path.h" #include "content/browser/handle_enumerator_win.h" #include "content/common/sandbox_policy.h" #elif defined(OS_MACOSX) #include "chrome/browser/mach_broker_mac.h" #elif defined(OS_POSIX) #include "base/memory/singleton.h" #include "content/browser/zygote_host_linux.h" #include "content/browser/renderer_host/render_sandbox_host_linux.h" #endif #if defined(OS_POSIX) #include "base/global_descriptors_posix.h" #endif // 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 { public: Context() : client_(NULL), client_thread_id_(BrowserThread::UI), starting_(true), terminate_child_on_shutdown_(true) #if defined(OS_POSIX) && !defined(OS_MACOSX) , zygote_(false) #endif { } void Launch( #if defined(OS_WIN) const FilePath& exposed_dir, #elif defined(OS_POSIX) bool use_zygote, const base::environment_vector& environ, int ipcfd, #endif CommandLine* cmd_line, Client* client) { client_ = client; CHECK(BrowserThread::GetCurrentThreadIdentifier(&client_thread_id_)); BrowserThread::PostTask( BrowserThread::PROCESS_LAUNCHER, FROM_HERE, NewRunnableMethod( this, &Context::LaunchInternal, #if defined(OS_WIN) exposed_dir, #elif defined(OS_POSIX) use_zygote, environ, ipcfd, #endif cmd_line)); } 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; friend class ChildProcessLauncher; ~Context() { Terminate(); } void LaunchInternal( #if defined(OS_WIN) const FilePath& exposed_dir, #elif defined(OS_POSIX) bool use_zygote, const base::environment_vector& env, int ipcfd, #endif CommandLine* cmd_line) { scoped_ptr cmd_line_deleter(cmd_line); base::ProcessHandle handle = base::kNullProcessHandle; #if defined(OS_WIN) handle = sandbox::StartProcessWithAccess(cmd_line, exposed_dir); #elif defined(OS_POSIX) #if defined(OS_POSIX) && !defined(OS_MACOSX) // On Linux, we need to add some extra file descriptors for crash handling. std::string process_type = cmd_line->GetSwitchValueASCII(switches::kProcessType); int crash_signal_fd = content::GetContentClient()->browser()->GetCrashSignalFD(process_type); if (use_zygote) { base::GlobalDescriptors::Mapping mapping; mapping.push_back(std::pair(kPrimaryIPCChannel, ipcfd)); if (crash_signal_fd >= 0) { mapping.push_back(std::pair(kCrashDumpSignal, crash_signal_fd)); } handle = ZygoteHost::GetInstance()->ForkRenderer(cmd_line->argv(), mapping); } else // Fall through to the normal posix case below when we're not zygoting. #endif { base::file_handle_mapping_vector fds_to_map; fds_to_map.push_back(std::make_pair( ipcfd, kPrimaryIPCChannel + base::GlobalDescriptors::kBaseDescriptor)); #if defined(OS_POSIX) && !defined(OS_MACOSX) if (crash_signal_fd >= 0) { fds_to_map.push_back(std::make_pair( crash_signal_fd, kCrashDumpSignal + base::GlobalDescriptors::kBaseDescriptor)); } if (process_type == switches::kRendererProcess) { const int sandbox_fd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket(); fds_to_map.push_back(std::make_pair( sandbox_fd, kSandboxIPCChannel + base::GlobalDescriptors::kBaseDescriptor)); } #endif // defined(OS_POSIX) && !defined(OS_MACOSX) bool launched = false; #if defined(OS_MACOSX) // It is possible for the child process to die immediately after // launching. To prevent leaking MachBroker map entries in this case, // lock around all of LaunchApp(). If the child dies, the death // notification will be processed by the MachBroker after the call to // AddPlaceholderForPid(), enabling proper cleanup. { // begin scope for AutoLock MachBroker* broker = MachBroker::GetInstance(); base::AutoLock lock(broker->GetLock()); // This call to |PrepareForFork()| will start the MachBroker listener // thread, if it is not already running. Therefore the browser process // will be listening for Mach IPC before LaunchApp() is called. broker->PrepareForFork(); #endif // Actually launch the app. launched = base::LaunchApp(cmd_line->argv(), env, fds_to_map, /* wait= */false, &handle); #if defined(OS_MACOSX) if (launched) broker->AddPlaceholderForPid(handle); } // end scope for AutoLock #endif if (!launched) handle = base::kNullProcessHandle; } #endif // else defined(OS_POSIX) BrowserThread::PostTask( client_thread_id_, FROM_HERE, NewRunnableMethod( this, &ChildProcessLauncher::Context::Notify, #if defined(OS_POSIX) && !defined(OS_MACOSX) use_zygote, #endif handle)); } void Notify( #if defined(OS_POSIX) && !defined(OS_MACOSX) bool zygote, #endif base::ProcessHandle handle) { starting_ = false; process_.set_handle(handle); #if defined(OS_POSIX) && !defined(OS_MACOSX) zygote_ = zygote; #endif if (client_) { client_->OnProcessLaunched(); } else { Terminate(); } } void Terminate() { if (!process_.handle()) return; if (!terminate_child_on_shutdown_) return; #if defined(OS_WIN) const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); if (browser_command_line.HasSwitch(switches::kAuditHandles) || browser_command_line.HasSwitch(switches::kAuditAllHandles)) { scoped_refptr handle_enum( new content::HandleEnumerator(process_.handle(), browser_command_line.HasSwitch(switches::kAuditAllHandles))); handle_enum->RunHandleEnumeration(); process_.set_handle(base::kNullProcessHandle); return; } #endif // 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, NewRunnableFunction( &ChildProcessLauncher::Context::TerminateInternal, #if defined(OS_POSIX) && !defined(OS_MACOSX) zygote_, #endif process_.handle())); process_.set_handle(base::kNullProcessHandle); } void SetProcessBackgrounded(bool background) { DCHECK(!starting_); process_.SetProcessBackgrounded(background); } // TODO(apatrick): Remove this ASAP. http://crbog.com/81449 shows that this is // called before later calling null. Disable optimization to try and get more // information about what happened here. #if defined(OS_WIN) #pragma optimize("", off) #endif static void TerminateInternal( #if defined(OS_POSIX) && !defined(OS_MACOSX) bool zygote, #endif base::ProcessHandle handle) { 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(ResultCodes::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. ZygoteHost::GetInstance()->EnsureProcessTerminated(handle); } else #endif // !OS_MACOSX { ProcessWatcher::EnsureProcessTerminated(handle); } #endif // OS_POSIX process.Close(); } #if defined(OS_WIN) #pragma optimize("", on) #endif Client* client_; BrowserThread::ID client_thread_id_; base::Process process_; 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_POSIX) && !defined(OS_MACOSX) bool zygote_; #endif }; ChildProcessLauncher::ChildProcessLauncher( #if defined(OS_WIN) const FilePath& exposed_dir, #elif defined(OS_POSIX) bool use_zygote, const base::environment_vector& environ, int ipcfd, #endif CommandLine* cmd_line, Client* client) { context_ = new Context(); context_->Launch( #if defined(OS_WIN) exposed_dir, #elif defined(OS_POSIX) use_zygote, environ, ipcfd, #endif cmd_line, 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( int* exit_code) { base::TerminationStatus status; base::ProcessHandle handle = context_->process_.handle(); #if defined(OS_POSIX) && !defined(OS_MACOSX) if (context_->zygote_) { status = ZygoteHost::GetInstance()->GetTerminationStatus(handle, exit_code); } else #endif { status = base::GetTerminationStatus(handle, 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 (status != base::TERMINATION_STATUS_STILL_RUNNING) context_->process_.Close(); return status; } void ChildProcessLauncher::SetProcessBackgrounded(bool background) { BrowserThread::PostTask( BrowserThread::PROCESS_LAUNCHER, FROM_HERE, NewRunnableMethod( context_.get(), &ChildProcessLauncher::Context::SetProcessBackgrounded, background)); } void ChildProcessLauncher::SetTerminateChildOnShutdown( bool terminate_on_shutdown) { if (context_) context_->set_terminate_child_on_shutdown(terminate_on_shutdown); }