diff options
author | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-10 18:42:13 +0000 |
---|---|---|
committer | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-10 18:42:13 +0000 |
commit | 80713584fd37a86dc69ecfaf866082477b891474 (patch) | |
tree | ba5774547c6387def418b46bb624869000e071de /remoting | |
parent | 42aef9e43a98c99b93b5c1f545a812ccab133cc9 (diff) | |
download | chromium_src-80713584fd37a86dc69ecfaf866082477b891474.zip chromium_src-80713584fd37a86dc69ecfaf866082477b891474.tar.gz chromium_src-80713584fd37a86dc69ecfaf866082477b891474.tar.bz2 |
[Chromoting] Moving common logic responsible for launching child processes to WorkerProcessLauncher class. Launches processes are expected to connect back via a Chromium IPC channel (and their identify can be verified at this point). The class also monitors lifetime of the launched process invoking the normal shutdown sequence in case of premature death of the worker process.
BUG=134694
Review URL: https://chromiumcodereview.appspot.com/10828181
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@151096 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/base/stoppable.cc | 4 | ||||
-rw-r--r-- | remoting/host/win/launch_process_with_token.cc | 4 | ||||
-rw-r--r-- | remoting/host/win/launch_process_with_token.h | 3 | ||||
-rw-r--r-- | remoting/host/win/worker_process_launcher.cc | 211 | ||||
-rw-r--r-- | remoting/host/win/worker_process_launcher.h | 137 | ||||
-rw-r--r-- | remoting/host/win/wts_session_process_launcher.cc | 356 | ||||
-rw-r--r-- | remoting/host/win/wts_session_process_launcher.h | 48 | ||||
-rw-r--r-- | remoting/remoting.gyp | 2 |
8 files changed, 509 insertions, 256 deletions
diff --git a/remoting/base/stoppable.cc b/remoting/base/stoppable.cc index 88b3c95..046ac42 100644 --- a/remoting/base/stoppable.cc +++ b/remoting/base/stoppable.cc @@ -26,8 +26,10 @@ void Stoppable::Stop() { if (state_ == kRunning) { state_ = kStopping; - DoStop(); } + + // DoStop() can be called multiple times. + DoStop(); } void Stoppable::CompleteStopping() { diff --git a/remoting/host/win/launch_process_with_token.cc b/remoting/host/win/launch_process_with_token.cc index 5de0bdf..ae0e1e6 100644 --- a/remoting/host/win/launch_process_with_token.cc +++ b/remoting/host/win/launch_process_with_token.cc @@ -345,7 +345,7 @@ bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) { bool LaunchProcessWithToken(const FilePath& binary, const CommandLine::StringType& command_line, HANDLE user_token, - base::Process* process_out) { + ScopedHandle* process_out) { FilePath::StringType application_name = binary.value(); base::win::ScopedProcessInformation process_info; @@ -404,7 +404,7 @@ bool LaunchProcessWithToken(const FilePath& binary, } CHECK(process_info.IsValid()); - process_out->set_handle(process_info.TakeProcessHandle()); + process_out->Set(process_info.TakeProcessHandle()); return true; } diff --git a/remoting/host/win/launch_process_with_token.h b/remoting/host/win/launch_process_with_token.h index 0ce4a79..196fdeb 100644 --- a/remoting/host/win/launch_process_with_token.h +++ b/remoting/host/win/launch_process_with_token.h @@ -10,7 +10,6 @@ #include "base/command_line.h" #include "base/file_path.h" -#include "base/process_util.h" #include "base/win/scoped_handle.h" namespace remoting { @@ -24,7 +23,7 @@ bool CreateSessionToken(uint32 session_id, base::win::ScopedHandle* token_out); bool LaunchProcessWithToken(const FilePath& binary, const CommandLine::StringType& command_line, HANDLE user_token, - base::Process* process_out); + base::win::ScopedHandle* process_out); } // namespace remoting diff --git a/remoting/host/win/worker_process_launcher.cc b/remoting/host/win/worker_process_launcher.cc new file mode 100644 index 0000000..cf781b2 --- /dev/null +++ b/remoting/host/win/worker_process_launcher.cc @@ -0,0 +1,211 @@ +// 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 "remoting/host/win/worker_process_launcher.h" + +#include <windows.h> +#include <sddl.h> +#include <limits> + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/process_util.h" +#include "base/rand_util.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_handle.h" +#include "ipc/ipc_channel_proxy.h" +#include "ipc/ipc_message.h" + +using base::win::ScopedHandle; + +namespace { + +// Match the pipe name prefix used by Chrome IPC channels so that the client +// could use Chrome IPC APIs instead of connecting manually. +const char kChromePipeNamePrefix[] = "\\\\.\\pipe\\chrome."; + +} // namespace + +namespace remoting { + +WorkerProcessLauncher::Delegate::~Delegate() { +} + +WorkerProcessLauncher::WorkerProcessLauncher( + Delegate* delegate, + const base::Closure& stopped_callback, + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner) + : Stoppable(main_task_runner, stopped_callback), + delegate_(delegate), + main_task_runner_(main_task_runner), + ipc_task_runner_(ipc_task_runner) { +} + +WorkerProcessLauncher::~WorkerProcessLauncher() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); +} + +void WorkerProcessLauncher::Start(const std::string& pipe_sddl) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(ipc_channel_.get() == NULL); + DCHECK(!pipe_.IsValid()); + DCHECK(!process_exit_event_.IsValid()); + DCHECK(process_watcher_.GetWatchedObject() == NULL); + + std::string channel_name = GenerateRandomChannelId(); + if (CreatePipeForIpcChannel(channel_name, pipe_sddl, &pipe_)) { + // Wrap the pipe into an IPC channel. + ipc_channel_.reset(new IPC::ChannelProxy( + IPC::ChannelHandle(pipe_), + IPC::Channel::MODE_SERVER, + this, + ipc_task_runner_)); + + // Launch the process and attach an object watcher to the returned process + // handle so that we get notified if the process terminates. + if (delegate_->DoLaunchProcess(channel_name, &process_exit_event_)) { + if (process_watcher_.StartWatching(process_exit_event_, this)) { + return; + } + + delegate_->DoKillProcess(CONTROL_C_EXIT); + } + } + + Stop(); +} + +void WorkerProcessLauncher::Send(IPC::Message* message) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + ipc_channel_->Send(message); +} + +void WorkerProcessLauncher::OnObjectSignaled(HANDLE object) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(process_watcher_.GetWatchedObject() == NULL); + + Stop(); +} + +bool WorkerProcessLauncher::OnMessageReceived(const IPC::Message& message) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(ipc_channel_.get() != NULL); + DCHECK(pipe_.IsValid()); + DCHECK(process_exit_event_.IsValid()); + + return delegate_->OnMessageReceived(message); +} + +void WorkerProcessLauncher::OnChannelConnected(int32 peer_pid) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(ipc_channel_.get() != NULL); + DCHECK(pipe_.IsValid()); + DCHECK(process_exit_event_.IsValid()); + + // Get the actual peer's PID (i.e. reported by the OS) instead of the PID + // reported by the peer itself (|peer_pid|). + DWORD actual_peer_pid; + if (!GetNamedPipeClientProcessId(pipe_, &actual_peer_pid)) { + LOG_GETLASTERROR(ERROR) << "Failed to query the peer's PID"; + Stop(); + return; + } + + delegate_->OnChannelConnected(actual_peer_pid); +} + +void WorkerProcessLauncher::OnChannelError() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(ipc_channel_.get() != NULL); + DCHECK(pipe_.IsValid()); + DCHECK(process_exit_event_.IsValid()); + + Stop(); +} + +void WorkerProcessLauncher::DoStop() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + ipc_channel_.reset(); + pipe_.Close(); + + // Kill the process if it has been started already. + if (process_watcher_.GetWatchedObject() != NULL) { + delegate_->DoKillProcess(CONTROL_C_EXIT); + return; + } + + DCHECK(ipc_channel_.get() == NULL); + DCHECK(!pipe_.IsValid()); + DCHECK(process_watcher_.GetWatchedObject() == NULL); + + process_exit_event_.Close(); + CompleteStopping(); +} + +// Creates the server end of the Chromoting IPC channel. +bool WorkerProcessLauncher::CreatePipeForIpcChannel( + const std::string& channel_name, + const std::string& pipe_sddl, + ScopedHandle* pipe_out) { + // Create security descriptor for the channel. + SECURITY_ATTRIBUTES security_attributes; + security_attributes.nLength = sizeof(security_attributes); + security_attributes.bInheritHandle = FALSE; + + ULONG security_descriptor_length = 0; + if (!ConvertStringSecurityDescriptorToSecurityDescriptor( + UTF8ToUTF16(pipe_sddl).c_str(), + SDDL_REVISION_1, + reinterpret_cast<PSECURITY_DESCRIPTOR*>( + &security_attributes.lpSecurityDescriptor), + &security_descriptor_length)) { + LOG_GETLASTERROR(ERROR) << + "Failed to create a security descriptor for the Chromoting IPC channel"; + return false; + } + + // Convert the channel name to the pipe name. + std::string pipe_name(kChromePipeNamePrefix); + pipe_name.append(channel_name); + + // Create the server end of the pipe. This code should match the code in + // IPC::Channel with exception of passing a non-default security descriptor. + base::win::ScopedHandle pipe; + pipe.Set(CreateNamedPipe( + UTF8ToUTF16(pipe_name).c_str(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, + 1, + IPC::Channel::kReadBufferSize, + IPC::Channel::kReadBufferSize, + 5000, + &security_attributes)); + if (!pipe.IsValid()) { + LOG_GETLASTERROR(ERROR) << + "Failed to create the server end of the Chromoting IPC channel"; + LocalFree(security_attributes.lpSecurityDescriptor); + return false; + } + + LocalFree(security_attributes.lpSecurityDescriptor); + + *pipe_out = pipe.Pass(); + return true; +} + +// N.B. Copied from src/content/common/child_process_host_impl.cc +std::string WorkerProcessLauncher::GenerateRandomChannelId() { + return base::StringPrintf("%d.%p.%d", + base::GetCurrentProcId(), this, + base::RandInt(0, std::numeric_limits<int>::max())); +} + +} // namespace remoting diff --git a/remoting/host/win/worker_process_launcher.h b/remoting/host/win/worker_process_launcher.h new file mode 100644 index 0000000..7dc71eb --- /dev/null +++ b/remoting/host/win/worker_process_launcher.h @@ -0,0 +1,137 @@ +// 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. + +#ifndef REMOTING_HOST_WIN_WORKER_PROCESS_LAUNCHER_H_ +#define REMOTING_HOST_WIN_WORKER_PROCESS_LAUNCHER_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/process.h" +#include "base/win/scoped_handle.h" +#include "base/win/object_watcher.h" +#include "ipc/ipc_channel.h" +#include "remoting/base/stoppable.h" + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace IPC { +class ChannelProxy; +class Message; +} // namespace IPC + +namespace remoting { + +// Launches a worker process that is controlled via an IPC channel. All +// interaction with the spawned process is through the IPC::Listener and Send() +// method. In case of error the channel is closed and the worker process is +// terminated. +// +// WorkerProcessLauncher object is good for one process launch attempt only. +class WorkerProcessLauncher + : public Stoppable, + public base::win::ObjectWatcher::Delegate, + public IPC::Listener { + public: + class Delegate { + public: + virtual ~Delegate(); + + // Starts the worker process and passes |channel_name| to it. + // |process_exit_event_out| receives a handle that becomes signalled once + // the launched process has been terminated. + virtual bool DoLaunchProcess( + const std::string& channel_name, + base::win::ScopedHandle* process_exit_event_out) = 0; + + // Terminates the worker process with the given exit code. + virtual void DoKillProcess(DWORD exit_code) = 0; + + // Notifies that a client has been connected to the channel. |peer_pid| + // is the peer process's ID that the delegate can use to verify identity of + // the client. The verification code has to make sure that the client + // process's PID will not be assigned to another process (for instance by + // keeping an opened handle of the client process). + virtual void OnChannelConnected(DWORD peer_pid) = 0; + + // Processes messages sent by the client. + virtual bool OnMessageReceived(const IPC::Message& message) = 0; + }; + + // Creates the launcher. + // |delegate| will be able to receive messages sent over the channel once + // the worker has been started and until it is stopped by Stop() or an error + // occurs. + // + // |stopped_callback| and |main_task_runner| are passed to the underlying + // |Stoppable| implementation. The caller should call all the methods on this + // class on the |main_task_runner| thread. |ipc_task_runner| is used to + // perform background IPC I/O. + WorkerProcessLauncher( + Delegate* delegate, + const base::Closure& stopped_callback, + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, + scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner); + virtual ~WorkerProcessLauncher(); + + // Starts the worker process. + void Start(const std::string& pipe_sddl); + + // Sends an IPC message to the worker process. This method can be called only + // after successful Start() and until Stop() is called or an error occurred. + void Send(IPC::Message* message); + + // base::win::ObjectWatcher::Delegate implementation. + virtual void OnObjectSignaled(HANDLE object) OVERRIDE; + + // IPC::Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; + virtual void OnChannelError() OVERRIDE; + + protected: + // Stoppable implementation. + virtual void DoStop() OVERRIDE; + + private: + // Creates the server end of the Chromoting IPC channel. + bool CreatePipeForIpcChannel(const std::string& channel_name, + const std::string& pipe_sddl, + base::win::ScopedHandle* pipe_out); + + // Generates random channel ID. + std::string GenerateRandomChannelId(); + + Delegate* delegate_; + + // The main service message loop. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + + // Message loop used by the IPC channel. + scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_; + + // Used to determine when the launched process terminates. + base::win::ObjectWatcher process_watcher_; + + // A waiting handle that becomes signalled once the launched process has + // been terminated. + base::win::ScopedHandle process_exit_event_; + + // The IPC channel connecting to the launched process. + scoped_ptr<IPC::ChannelProxy> ipc_channel_; + + // The server end of the pipe. + base::win::ScopedHandle pipe_; + + DISALLOW_COPY_AND_ASSIGN(WorkerProcessLauncher); +}; + +} // namespace remoting + +#endif // REMOTING_HOST_WIN_WORKER_PROCESS_LAUNCHER_H_ diff --git a/remoting/host/win/wts_session_process_launcher.cc b/remoting/host/win/wts_session_process_launcher.cc index 8b57da7..e57d37c 100644 --- a/remoting/host/win/wts_session_process_launcher.cc +++ b/remoting/host/win/wts_session_process_launcher.cc @@ -47,9 +47,6 @@ const int kMinLaunchDelaySeconds = 1; const FilePath::CharType kMe2meHostBinaryName[] = FILE_PATH_LITERAL("remoting_me2me_host.exe"); -// Match the pipe name prefix used by Chrome IPC channels. -const char kChromePipeNamePrefix[] = "\\\\.\\pipe\\chrome."; - // The IPC channel name is passed to the host in the command line. const char kChromotingIpcSwitchName[] = "chromoting-ipc"; @@ -60,70 +57,7 @@ const char* kCopiedSwitchNames[] = { // The security descriptor of the Chromoting IPC channel. It gives full access // to LocalSystem and denies access by anyone else. -const wchar_t kChromotingChannelSecurityDescriptor[] = - L"O:SYG:SYD:(A;;GA;;;SY)"; - -// Generates random channel ID. -// N.B. Stolen from src/content/common/child_process_host_impl.cc -std::string GenerateRandomChannelId(void* instance) { - return base::StringPrintf("%d.%p.%d", - base::GetCurrentProcId(), instance, - base::RandInt(0, std::numeric_limits<int>::max())); -} - -// Creates the server end of the Chromoting IPC channel. -// N.B. This code is based on IPC::Channel's implementation. -bool CreatePipeForIpcChannel(void* instance, - std::string* channel_name_out, - ScopedHandle* pipe_out) { - // Create security descriptor for the channel. - SECURITY_ATTRIBUTES security_attributes; - security_attributes.nLength = sizeof(security_attributes); - security_attributes.bInheritHandle = FALSE; - - ULONG security_descriptor_length = 0; - if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( - kChromotingChannelSecurityDescriptor, - SDDL_REVISION_1, - reinterpret_cast<PSECURITY_DESCRIPTOR*>( - &security_attributes.lpSecurityDescriptor), - &security_descriptor_length)) { - LOG_GETLASTERROR(ERROR) << - "Failed to create a security descriptor for the Chromoting IPC channel"; - return false; - } - - // Generate a random channel name. - std::string channel_name(GenerateRandomChannelId(instance)); - - // Convert it to the pipe name. - std::string pipe_name(kChromePipeNamePrefix); - pipe_name.append(channel_name); - - // Create the server end of the pipe. This code should match the code in - // IPC::Channel with exception of passing a non-default security descriptor. - HANDLE pipe = CreateNamedPipeW(UTF8ToUTF16(pipe_name).c_str(), - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | - FILE_FLAG_FIRST_PIPE_INSTANCE, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, - 1, - IPC::Channel::kReadBufferSize, - IPC::Channel::kReadBufferSize, - 5000, - &security_attributes); - if (pipe == INVALID_HANDLE_VALUE) { - LOG_GETLASTERROR(ERROR) << - "Failed to create the server end of the Chromoting IPC channel"; - LocalFree(security_attributes.lpSecurityDescriptor); - return false; - } - - LocalFree(security_attributes.lpSecurityDescriptor); - - *channel_name_out = channel_name; - pipe_out->Set(pipe); - return true; -} +const char kChromotingChannelSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; } // namespace @@ -135,151 +69,157 @@ WtsSessionProcessLauncher::WtsSessionProcessLauncher( scoped_refptr<base::SingleThreadTaskRunner> main_message_loop, scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop) : Stoppable(main_message_loop, stopped_callback), + attached_(false), main_message_loop_(main_message_loop), ipc_message_loop_(ipc_message_loop), - monitor_(monitor), - state_(StateDetached) { + monitor_(monitor) { monitor_->AddWtsConsoleObserver(this); } WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { monitor_->RemoveWtsConsoleObserver(this); - if (state_ != StateDetached) { - OnSessionDetached(); - } - DCHECK(state_ == StateDetached); + DCHECK(!attached_); DCHECK(!timer_.IsRunning()); - DCHECK(process_.handle() == NULL); - DCHECK(process_watcher_.GetWatchedObject() == NULL); - DCHECK(chromoting_channel_.get() == NULL); } void WtsSessionProcessLauncher::LaunchProcess() { DCHECK(main_message_loop_->BelongsToCurrentThread()); - DCHECK(state_ == StateStarting); + DCHECK(attached_); + DCHECK(launcher_.get() == NULL); DCHECK(!timer_.IsRunning()); - DCHECK(process_.handle() == NULL); - DCHECK(process_watcher_.GetWatchedObject() == NULL); - DCHECK(chromoting_channel_.get() == NULL); + DCHECK(!worker_process_.IsValid()); launch_time_ = base::Time::Now(); + launcher_.reset(new WorkerProcessLauncher( + this, + base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped, + base::Unretained(this)), + main_message_loop_, + ipc_message_loop_)); + launcher_->Start(kChromotingChannelSecurityDescriptor); +} + +void WtsSessionProcessLauncher::OnLauncherStopped() { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + + DWORD exit_code; + if (!::GetExitCodeProcess(worker_process_, &exit_code)) { + LOG_GETLASTERROR(INFO) + << "Failed to query the exit code of the worker process"; + exit_code = CONTROL_C_EXIT; + } + + launcher_.reset(NULL); + worker_process_.Close(); + + // Do not relaunch the worker process if the caller has asked us to stop. + if (stoppable_state() != Stoppable::kRunning) { + CompleteStopping(); + return; + } + + // Stop trying to restart the worker process if its process exited due to + // misconfiguration. + if (kMinPermanentErrorExitCode <= exit_code && + exit_code <= kMaxPermanentErrorExitCode) { + Stop(); + return; + } + + // Try to restart the worker process if we are still attached to a session. + if (attached_) { + // Expand the backoff interval if the process has died quickly or reset it + // if it was up longer than the maximum backoff delay. + base::TimeDelta delta = base::Time::Now() - launch_time_; + if (delta < base::TimeDelta() || + delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { + launch_backoff_ = base::TimeDelta(); + } else { + launch_backoff_ = std::max( + launch_backoff_ * 2, TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); + launch_backoff_ = std::min( + launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); + } + + // Try to launch the worker process. + timer_.Start(FROM_HERE, launch_backoff_, + this, &WtsSessionProcessLauncher::LaunchProcess); + } +} + +bool WtsSessionProcessLauncher::DoLaunchProcess( + const std::string& channel_name, + ScopedHandle* process_exit_event_out) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + DCHECK(!worker_process_.IsValid()); // Construct the host binary name. FilePath dir_path; if (!PathService::Get(base::DIR_EXE, &dir_path)) { LOG(ERROR) << "Failed to get the executable file name."; - Stop(); - return; + return false; } FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); - std::string channel_name; - ScopedHandle pipe; - if (CreatePipeForIpcChannel(this, &channel_name, &pipe)) { - // Wrap the pipe into an IPC channel. - chromoting_channel_.reset(new IPC::ChannelProxy( - IPC::ChannelHandle(pipe.Get()), - IPC::Channel::MODE_SERVER, - this, - ipc_message_loop_)); - - // Create the host process command line passing the name of the IPC channel - // to use and copying known switches from the service's command line. - CommandLine command_line(host_binary); - command_line.AppendSwitchASCII(kChromotingIpcSwitchName, channel_name); - command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), - kCopiedSwitchNames, - _countof(kCopiedSwitchNames)); - - // Try to launch the process and attach an object watcher to the returned - // handle so that we get notified when the process terminates. - if (LaunchProcessWithToken(host_binary, - command_line.GetCommandLineString(), - session_token_, - &process_)) { - if (process_watcher_.StartWatching(process_.handle(), this)) { - state_ = StateAttached; - return; - } else { - LOG(ERROR) << "Failed to arm the process watcher."; - process_.Terminate(0); - process_.Close(); - } - } + // Create the host process command line passing the name of the IPC channel + // to use and copying known switches from the service's command line. + CommandLine command_line(host_binary); + command_line.AppendSwitchNative(kChromotingIpcSwitchName, + UTF8ToWide(channel_name)); + command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), + kCopiedSwitchNames, + _countof(kCopiedSwitchNames)); + + // Try to launch the process and attach an object watcher to the returned + // handle so that we get notified when the process terminates. + if (!LaunchProcessWithToken(host_binary, + command_line.GetCommandLineString(), + session_token_, + &worker_process_)) { + return false; + } - chromoting_channel_.reset(); + ScopedHandle process_exit_event; + if (!DuplicateHandle(GetCurrentProcess(), + worker_process_, + GetCurrentProcess(), + process_exit_event.Receive(), + SYNCHRONIZE, + FALSE, + 0)) { + LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; + DoKillProcess(CONTROL_C_EXIT); + return false; } - // Something went wrong. Try to launch the host again later. The attempts rate - // is limited by exponential backoff. - launch_backoff_ = std::max(launch_backoff_ * 2, - TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); - launch_backoff_ = std::min(launch_backoff_, - TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); - timer_.Start(FROM_HERE, launch_backoff_, - this, &WtsSessionProcessLauncher::LaunchProcess); + *process_exit_event_out = process_exit_event.Pass(); + return true; } -void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { - if (!main_message_loop_->BelongsToCurrentThread()) { - main_message_loop_->PostTask( - FROM_HERE, base::Bind(&WtsSessionProcessLauncher::OnObjectSignaled, - base::Unretained(this), object)); - return; - } +void WtsSessionProcessLauncher::DoKillProcess(DWORD exit_code) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); - // It is possible that OnObjectSignaled() task will be queued by another - // thread right before |process_watcher_| was stopped. It such a case it is - // safe to ignore this notification. - if (state_ != StateAttached) { - return; + if (worker_process_.IsValid()) { + TerminateProcess(worker_process_, exit_code); } +} - DCHECK(!timer_.IsRunning()); - DCHECK(process_.handle() != NULL); - DCHECK(process_watcher_.GetWatchedObject() == NULL); - DCHECK(chromoting_channel_.get() != NULL); +void WtsSessionProcessLauncher::OnChannelConnected(DWORD peer_pid) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); - // Stop trying to restart the host if its process exited due to - // misconfiguration. - int exit_code; - bool stop_trying = - base::WaitForExitCodeWithTimeout( - process_.handle(), &exit_code, base::TimeDelta()) && - kMinPermanentErrorExitCode <= exit_code && - exit_code <= kMaxPermanentErrorExitCode; - - // The host process has been terminated for some reason. The handle can now be - // closed. - process_.Close(); - chromoting_channel_.reset(); - state_ = StateStarting; - - if (stop_trying) { + DWORD expected_pid = GetProcessId(worker_process_); + if (peer_pid != expected_pid) { + LOG(ERROR) + << "Unexpected client connected: expected=" << expected_pid + << ", actual=" << peer_pid; Stop(); - return; - } - - // Expand the backoff interval if the process has died quickly or reset it if - // it was up longer than the maximum backoff delay. - base::TimeDelta delta = base::Time::Now() - launch_time_; - if (delta < base::TimeDelta() || - delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { - launch_backoff_ = base::TimeDelta(); - } else { - launch_backoff_ = std::max(launch_backoff_ * 2, - TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); - launch_backoff_ = std::min(launch_backoff_, - TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); } - - // Try to restart the host. - timer_.Start(FROM_HERE, launch_backoff_, - this, &WtsSessionProcessLauncher::LaunchProcess); } bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + bool handled = true; IPC_BEGIN_MESSAGE_MAP(WtsSessionProcessLauncher, message) IPC_MESSAGE_HANDLER(ChromotingHostMsg_SendSasToConsole, @@ -290,14 +230,9 @@ bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { } void WtsSessionProcessLauncher::OnSendSasToConsole() { - if (!main_message_loop_->BelongsToCurrentThread()) { - main_message_loop_->PostTask( - FROM_HERE, base::Bind(&WtsSessionProcessLauncher::OnSendSasToConsole, - base::Unretained(this))); - return; - } + DCHECK(main_message_loop_->BelongsToCurrentThread()); - if (state_ == StateAttached) { + if (attached_) { if (sas_injector_.get() == NULL) { sas_injector_ = SasInjector::Create(); } @@ -315,68 +250,43 @@ void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { return; } - DCHECK(state_ == StateDetached); + DCHECK(!attached_); DCHECK(!timer_.IsRunning()); - DCHECK(process_.handle() == NULL); - DCHECK(process_watcher_.GetWatchedObject() == NULL); - DCHECK(chromoting_channel_.get() == NULL); + + attached_ = true; // Create a session token for the launched process. if (!CreateSessionToken(session_id, &session_token_)) return; // Now try to launch the host. - state_ = StateStarting; LaunchProcess(); } void WtsSessionProcessLauncher::OnSessionDetached() { DCHECK(main_message_loop_->BelongsToCurrentThread()); - DCHECK(state_ == StateDetached || - state_ == StateStarting || - state_ == StateAttached); - - switch (state_) { - case StateDetached: - DCHECK(!timer_.IsRunning()); - DCHECK(process_.handle() == NULL); - DCHECK(process_watcher_.GetWatchedObject() == NULL); - DCHECK(chromoting_channel_.get() == NULL); - break; - - case StateStarting: - DCHECK(process_.handle() == NULL); - DCHECK(process_watcher_.GetWatchedObject() == NULL); - DCHECK(chromoting_channel_.get() == NULL); - - timer_.Stop(); - launch_backoff_ = base::TimeDelta(); - state_ = StateDetached; - break; - - case StateAttached: - DCHECK(!timer_.IsRunning()); - DCHECK(process_.handle() != NULL); - DCHECK(process_watcher_.GetWatchedObject() != NULL); - DCHECK(chromoting_channel_.get() != NULL); - - process_watcher_.StopWatching(); - process_.Terminate(0); - process_.Close(); - chromoting_channel_.reset(); - state_ = StateDetached; - break; - } + DCHECK(attached_); + attached_ = false; + launch_backoff_ = base::TimeDelta(); session_token_.Close(); + timer_.Stop(); + + if (launcher_.get() != NULL) { + launcher_->Stop(); + } } void WtsSessionProcessLauncher::DoStop() { - if (state_ != StateDetached) { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + + if (attached_) { OnSessionDetached(); } - CompleteStopping(); + if (launcher_.get() == NULL) { + CompleteStopping(); + } } } // namespace remoting diff --git a/remoting/host/win/wts_session_process_launcher.h b/remoting/host/win/wts_session_process_launcher.h index 577c074..3bde4c1 100644 --- a/remoting/host/win/wts_session_process_launcher.h +++ b/remoting/host/win/wts_session_process_launcher.h @@ -15,9 +15,9 @@ #include "base/time.h" #include "base/timer.h" #include "base/win/scoped_handle.h" -#include "base/win/object_watcher.h" #include "ipc/ipc_channel.h" #include "remoting/base/stoppable.h" +#include "remoting/host/win/worker_process_launcher.h" #include "remoting/host/win/wts_console_observer.h" namespace base { @@ -36,13 +36,13 @@ class WtsConsoleMonitor; class WtsSessionProcessLauncher : public Stoppable, - public base::win::ObjectWatcher::Delegate, - public IPC::Listener, + public WorkerProcessLauncher::Delegate, public WtsConsoleObserver { public: - // Constructs a WtsSessionProcessLauncher object. All interaction with - // |monitor| should happen on |main_message_loop|. |ipc_message_loop| has - // to be an I/O message loop. + // Constructs a WtsSessionProcessLauncher object. |stopped_callback| and + // |main_message_loop| are passed to the undelying |Stoppable| implementation. + // All interaction with |monitor| should happen on |main_message_loop|. + // |ipc_message_loop| must be an I/O message loop. WtsSessionProcessLauncher( const base::Closure& stopped_callback, WtsConsoleMonitor* monitor, @@ -51,10 +51,12 @@ class WtsSessionProcessLauncher virtual ~WtsSessionProcessLauncher(); - // base::win::ObjectWatcher::Delegate implementation. - virtual void OnObjectSignaled(HANDLE object) OVERRIDE; - - // IPC::Listener implementation. + // WorkerProcessLauncher::Delegate implementation. + virtual bool DoLaunchProcess( + const std::string& channel_name, + base::win::ScopedHandle* process_exit_event_out) OVERRIDE; + virtual void DoKillProcess(DWORD exit_code) OVERRIDE; + virtual void OnChannelConnected(DWORD peer_pid) OVERRIDE; virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; // WtsConsoleObserver implementation. @@ -71,10 +73,16 @@ class WtsSessionProcessLauncher // reason. void LaunchProcess(); + // Called when the launcher reports the process to be stopped. + void OnLauncherStopped(); + // Sends the Secure Attention Sequence to the session represented by // |session_token_|. void OnSendSasToConsole(); + // |true| if this object is currently attached to the console session. + bool attached_; + // Time of the last launch attempt. base::Time launch_time_; @@ -93,29 +101,13 @@ class WtsSessionProcessLauncher // This pointer is used to unsubscribe from session attach and detach events. WtsConsoleMonitor* monitor_; - // The handle of the process injected into the console session. - base::Process process_; + scoped_ptr<WorkerProcessLauncher> launcher_; - // Used to determine when the launched process terminates. - base::win::ObjectWatcher process_watcher_; + base::win::ScopedHandle worker_process_; // The token to be used to launch a process in a different session. base::win::ScopedHandle session_token_; - // Defines the states the process launcher can be in. - enum State { - StateDetached, - StateStarting, - StateAttached, - }; - - // Current state of the process launcher. - State state_; - - // The Chromoting IPC channel connecting the service to the per-session - // process. - scoped_ptr<IPC::ChannelProxy> chromoting_channel_; - scoped_ptr<SasInjector> sas_injector_; DISALLOW_COPY_AND_ASSIGN(WtsSessionProcessLauncher); diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 80b284a..ffcb70c 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -620,6 +620,8 @@ 'host/win/host_service_resource.h', 'host/win/launch_process_with_token.cc', 'host/win/launch_process_with_token.h', + 'host/win/worker_process_launcher.cc', + 'host/win/worker_process_launcher.h', 'host/win/wts_console_monitor.h', 'host/win/wts_console_observer.h', 'host/win/wts_session_process_launcher.cc', |