Chromium Code Reviews| Index: content/browser/child_process_launcher_elevated.cc |
| diff --git a/content/browser/child_process_launcher_elevated.cc b/content/browser/child_process_launcher_elevated.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..cd9a4944ccc67096e743757a283b272019eb2687 |
| --- /dev/null |
| +++ b/content/browser/child_process_launcher_elevated.cc |
| @@ -0,0 +1,254 @@ |
| +// 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_elevated.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" |
| + |
| +#include "base/files/file_path.h" |
| + |
| +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 ChildProcessLauncherElevated::Context |
| + : public base::RefCountedThreadSafe<ChildProcessLauncherElevated::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) { |
| + terminate_child_on_shutdown_ = true; |
|
mef
2013/12/18 21:02:10
nit: tabbing
Drew Haven
2014/01/09 01:15:14
Done.
|
| + } |
| + |
| + void Launch( |
| + CommandLine* cmd_line, |
| + int child_process_id, |
| + Client* client) { |
| + client_ = client; |
| + |
| + CHECK(BrowserThread::GetCurrentThreadIdentifier(&client_thread_id_)); |
| + |
| + BrowserThread::PostTask( |
| + BrowserThread::PROCESS_LAUNCHER, FROM_HERE, |
| + base::Bind( |
| + &Context::LaunchInternal, |
| + make_scoped_refptr(this), |
| + client_thread_id_, |
| + child_process_id, |
| + 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<ChildProcessLauncherElevated::Context>; |
| + friend class ChildProcessLauncherElevated; |
| + |
| + ~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(&ChildProcessLauncherElevated::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, |
| + CommandLine* cmd_line) { |
| + scoped_ptr<CommandLine> cmd_line_deleter(cmd_line); |
| + base::TimeTicks begin_launch_time = base::TimeTicks::Now(); |
| + |
| + base::ProcessHandle handle; |
| + base::LaunchOptions options; |
| + options.start_hidden = true; |
| + base::LaunchElevatedProcess(*cmd_line, options, &handle); |
| + |
| + if (handle) |
| + RecordHistograms(begin_launch_time); |
| + |
| + BrowserThread::PostTask( |
| + client_thread_id, FROM_HERE, |
| + base::Bind( |
| + &Context::Notify, |
| + this_object.get(), |
| + handle)); |
| + } |
| + |
| + void Notify( |
| + base::ProcessHandle handle) { |
| + starting_ = false; |
| + process_.set_handle(handle); |
| + if (!handle) |
| + LOG(ERROR) << "Failed to launch child process"; |
| + |
| + if (client_) { |
| + client_->OnProcessLaunched(); |
|
mef
2013/12/18 21:02:10
But if !handle, then process is NOT launched?
Drew Haven
2014/01/09 01:15:14
I'm not really sure what's going on here. It's ju
|
| + } 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, |
| + 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( |
| + 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(RESULT_CODE_NORMAL_EXIT); |
|
mef
2013/12/18 21:02:10
This will probably fail for elevated process, woul
Drew Haven
2014/01/09 01:15:14
I tested it on my Windows 7 box and the parent pro
|
| + // On POSIX, we must additionally reap the child. |
|
mef
2013/12/18 21:02:10
Not sure what to do about POSIX stuff, given that
Drew Haven
2014/01/09 01:15:14
For now, at least, I made this class entirely wind
|
| + process.Close(); |
| + } |
| + |
| + 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_; |
| +}; |
| + |
| + |
| +ChildProcessLauncherElevated::ChildProcessLauncherElevated( |
| + CommandLine* cmd_line, |
| + int child_process_id, |
| + Client* client) { |
| + context_ = new Context(); |
| + context_->Launch( |
| + cmd_line, |
| + child_process_id, |
| + client); |
| +} |
| + |
| +ChildProcessLauncherElevated::~ChildProcessLauncherElevated() { |
| + context_->ResetClient(); |
| +} |
| + |
| +bool ChildProcessLauncherElevated::IsStarting() { |
| + return context_->starting_; |
| +} |
| + |
| +base::ProcessHandle ChildProcessLauncherElevated::GetHandle() { |
| + DCHECK(!context_->starting_); |
| + return context_->process_.handle(); |
| +} |
| + |
| +base::TerminationStatus ChildProcessLauncherElevated::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_; |
| + } |
| + 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 ChildProcessLauncherElevated::SetProcessBackgrounded(bool background) { |
| + BrowserThread::PostTask( |
| + BrowserThread::PROCESS_LAUNCHER, FROM_HERE, |
| + base::Bind( |
| + &ChildProcessLauncherElevated::Context::SetProcessBackgrounded, |
| + GetHandle(), background)); |
| +} |
| + |
| +void ChildProcessLauncherElevated::SetTerminateChildOnShutdown( |
| + bool terminate_on_shutdown) { |
| + if (context_.get()) |
| + context_->set_terminate_child_on_shutdown(terminate_on_shutdown); |
| +} |
| + |
| +} // namespace content |