blob: 8ba6941485bc10d9a891e4317b708ff273489713 [file] [log] [blame]
// Copyright (c) 2010 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 "chrome/browser/child_process_launcher.h"
#include <utility> // For std::pair.
#include "base/command_line.h"
#include "base/lock.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/thread.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/common/chrome_descriptors.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/process_watcher.h"
#include "chrome/common/result_codes.h"
#if defined(OS_WIN)
#include "base/file_path.h"
#include "chrome/common/sandbox_policy.h"
#elif defined(OS_LINUX)
#include "base/singleton.h"
#include "chrome/browser/crash_handler_host_linux.h"
#include "chrome/browser/zygote_host_linux.h"
#include "chrome/browser/renderer_host/render_sandbox_host_linux.h"
#endif
#if defined(OS_MACOSX)
#include "chrome/browser/mach_broker_mac.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<ChildProcessLauncher::Context> {
public:
Context()
: client_(NULL),
client_thread_id_(BrowserThread::UI),
starting_(true)
#if defined(OS_LINUX)
, 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;
}
private:
friend class base::RefCountedThreadSafe<ChildProcessLauncher::Context>;
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<CommandLine> 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_LINUX)
if (use_zygote) {
base::GlobalDescriptors::Mapping mapping;
mapping.push_back(std::pair<uint32_t, int>(kPrimaryIPCChannel, ipcfd));
const int crash_signal_fd =
RendererCrashHandlerHostLinux::GetInstance()->GetDeathSignalSocket();
if (crash_signal_fd >= 0) {
mapping.push_back(std::pair<uint32_t, int>(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_LINUX)
// On Linux, we need to add some extra file descriptors for crash handling
// and the sandbox.
bool is_renderer =
cmd_line->GetSwitchValueASCII(switches::kProcessType) ==
switches::kRendererProcess;
bool is_plugin =
cmd_line->GetSwitchValueASCII(switches::kProcessType) ==
switches::kPluginProcess;
if (is_renderer || is_plugin) {
int crash_signal_fd;
if (is_renderer) {
crash_signal_fd = RendererCrashHandlerHostLinux::GetInstance()->
GetDeathSignalSocket();
} else {
crash_signal_fd = PluginCrashHandlerHostLinux::GetInstance()->
GetDeathSignalSocket();
}
if (crash_signal_fd >= 0) {
fds_to_map.push_back(std::make_pair(
crash_signal_fd,
kCrashDumpSignal + base::GlobalDescriptors::kBaseDescriptor));
}
}
if (is_renderer) {
const int sandbox_fd =
RenderSandboxHostLinux::GetInstance()->GetRendererSocket();
fds_to_map.push_back(std::make_pair(
sandbox_fd,
kSandboxIPCChannel + base::GlobalDescriptors::kBaseDescriptor));
}
#endif // defined(OS_LINUX)
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();
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_LINUX)
use_zygote,
#endif
handle));
}
void Notify(
#if defined(OS_LINUX)
bool zygote,
#endif
base::ProcessHandle handle) {
starting_ = false;
process_.set_handle(handle);
#if defined(OS_LINUX)
zygote_ = zygote;
#endif
if (client_) {
client_->OnProcessLaunched();
} else {
Terminate();
}
}
void Terminate() {
if (!process_.handle())
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,
NewRunnableFunction(
&ChildProcessLauncher::Context::TerminateInternal,
#if defined(OS_LINUX)
zygote_,
#endif
process_.handle()));
process_.set_handle(base::kNullProcessHandle);
}
static void TerminateInternal(
#if defined(OS_LINUX)
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_LINUX)
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_LINUX
{
ProcessWatcher::EnsureProcessTerminated(handle);
}
#endif // OS_POSIX
process.Close();
}
Client* client_;
BrowserThread::ID client_thread_id_;
base::Process process_;
bool starting_;
#if defined(OS_LINUX)
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_LINUX)
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) {
DCHECK(!context_->starting_);
context_->process_.SetProcessBackgrounded(background);
}