Index: remoting/host/win/wts_session_process_launcher.cc |
diff --git a/remoting/host/win/wts_session_process_launcher.cc b/remoting/host/win/wts_session_process_launcher.cc |
index 42cb702a9d3dfff8b5354045670f579cb312c602..bec5db12ef9dba4bfa5004fd362e09e486dcf747 100644 |
--- a/remoting/host/win/wts_session_process_launcher.cc |
+++ b/remoting/host/win/wts_session_process_launcher.cc |
@@ -47,9 +47,14 @@ const int kMinLaunchDelaySeconds = 1; |
const FilePath::CharType kMe2meHostBinaryName[] = |
FILE_PATH_LITERAL("remoting_me2me_host.exe"); |
+const FilePath::CharType kMe2meServiceBinaryName[] = |
+ FILE_PATH_LITERAL("remoting_service.exe"); |
+ |
// The IPC channel name is passed to the host in the command line. |
const char kChromotingIpcSwitchName[] = "chromoting-ipc"; |
+const char kElevateSwitchName[] = "elevate"; |
+ |
// The command line parameters that should be copied from the service's command |
// line to the host process. |
const char* kCopiedSwitchNames[] = { |
@@ -72,87 +77,57 @@ WtsSessionProcessLauncher::WtsSessionProcessLauncher( |
attached_(false), |
main_message_loop_(main_message_loop), |
ipc_message_loop_(ipc_message_loop), |
- monitor_(monitor) { |
+ monitor_(monitor), |
+ job_state_(kJobUninitialized) { |
monitor_->AddWtsConsoleObserver(this); |
-} |
-WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { |
- monitor_->RemoveWtsConsoleObserver(this); |
- |
- DCHECK(!attached_); |
- DCHECK(!timer_.IsRunning()); |
-} |
- |
-void WtsSessionProcessLauncher::LaunchProcess() { |
- DCHECK(main_message_loop_->BelongsToCurrentThread()); |
- DCHECK(attached_); |
- DCHECK(launcher_.get() == NULL); |
- DCHECK(!timer_.IsRunning()); |
- DCHECK(!worker_process_.IsValid()); |
+ process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); |
+ CHECK(process_exit_event_.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); |
+ // To receive job object notifications it is registered with the completion |
+ // port represented by |ipc_message_loop|. The registration has to be done on |
+ // the I/O thread because MessageLoopForIO::RegisterJobObject() can only be |
+ // called via MessageLoopForIO::current(). |
+ ipc_message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &WtsSessionProcessLauncher::InitializeJob, |
+ base::Unretained(this))); |
} |
-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; |
- } |
+WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { |
+ // Make sure that the object is completely stopped. The same check exists |
+ // in Stoppable::~Stoppable() but this one allows us to examine the state of |
+ // the object before destruction. |
+ CHECK_EQ(stoppable_state(), Stoppable::kStopped); |
- // Stop trying to restart the worker process if its process exited due to |
- // misconfiguration. |
- if (kMinPermanentErrorExitCode <= exit_code && |
- exit_code <= kMaxPermanentErrorExitCode) { |
- Stop(); |
- return; |
- } |
+ monitor_->RemoveWtsConsoleObserver(this); |
- // 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)); |
- } |
+ CHECK(!attached_); |
+ CHECK(!timer_.IsRunning()); |
+} |
- // Try to launch the worker process. |
- timer_.Start(FROM_HERE, launch_backoff_, |
- this, &WtsSessionProcessLauncher::LaunchProcess); |
- } |
+void WtsSessionProcessLauncher::OnIOCompleted( |
+ base::MessagePumpForIO::IOContext* context, |
+ DWORD bytes_transferred, |
+ DWORD error) { |
+ DCHECK(ipc_message_loop_->BelongsToCurrentThread()); |
+ |
+ // |bytes_transferred| is used in job object notifications to supply |
+ // the message ID; |context| carries process ID. |
+ main_message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &WtsSessionProcessLauncher::OnJobNotification, |
+ base::Unretained(this), bytes_transferred, |
+ reinterpret_cast<DWORD>(context))); |
} |
bool WtsSessionProcessLauncher::DoLaunchProcess( |
const std::string& channel_name, |
ScopedHandle* process_exit_event_out) { |
DCHECK(main_message_loop_->BelongsToCurrentThread()); |
- DCHECK(!worker_process_.IsValid()); |
+ |
+ // The job object is not ready. Retry starting the host process later. |
+ if (!job_.IsValid()) { |
+ return false; |
+ } |
// Construct the host binary name. |
FilePath dir_path; |
@@ -161,28 +136,48 @@ bool WtsSessionProcessLauncher::DoLaunchProcess( |
return false; |
} |
FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); |
+ FilePath service_binary = dir_path.Append(kMe2meServiceBinaryName); |
// 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); |
+ CommandLine command_line(service_binary); |
+ command_line.AppendSwitchPath(kElevateSwitchName, host_binary); |
command_line.AppendSwitchNative(kChromotingIpcSwitchName, |
UTF8ToWide(channel_name)); |
command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), |
kCopiedSwitchNames, |
_countof(kCopiedSwitchNames)); |
+ CHECK(ResetEvent(process_exit_event_)); |
+ |
// 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, |
+ base::win::ScopedHandle worker_process; |
+ base::win::ScopedHandle worker_thread; |
+ if (!LaunchProcessWithToken(service_binary, |
command_line.GetCommandLineString(), |
session_token_, |
- &worker_process_)) { |
+ CREATE_SUSPENDED, |
+ &worker_process, |
+ &worker_thread)) { |
+ return false; |
+ } |
+ |
+ if (!AssignProcessToJobObject(job_, worker_process)) { |
+ LOG_GETLASTERROR(ERROR) << "Failed to assign the worker to the job object"; |
+ TerminateProcess(worker_process, CONTROL_C_EXIT); |
+ return false; |
+ } |
+ |
+ if (!ResumeThread(worker_thread)) { |
+ LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread"; |
+ DoKillProcess(CONTROL_C_EXIT); |
return false; |
} |
ScopedHandle process_exit_event; |
if (!DuplicateHandle(GetCurrentProcess(), |
- worker_process_, |
+ process_exit_event_, |
GetCurrentProcess(), |
process_exit_event.Receive(), |
SYNCHRONIZE, |
@@ -200,8 +195,8 @@ bool WtsSessionProcessLauncher::DoLaunchProcess( |
void WtsSessionProcessLauncher::DoKillProcess(DWORD exit_code) { |
DCHECK(main_message_loop_->BelongsToCurrentThread()); |
- if (worker_process_.IsValid()) { |
- TerminateProcess(worker_process_, exit_code); |
+ if (job_.IsValid()) { |
+ TerminateJobObject(job_, exit_code); |
} |
} |
@@ -221,20 +216,6 @@ bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { |
return handled; |
} |
-void WtsSessionProcessLauncher::OnSendSasToConsole() { |
- DCHECK(main_message_loop_->BelongsToCurrentThread()); |
- |
- if (attached_) { |
- if (sas_injector_.get() == NULL) { |
- sas_injector_ = SasInjector::Create(); |
- } |
- |
- if (sas_injector_.get() != NULL) { |
- sas_injector_->InjectSas(); |
- } |
- } |
-} |
- |
void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { |
DCHECK(main_message_loop_->BelongsToCurrentThread()); |
@@ -276,8 +257,201 @@ void WtsSessionProcessLauncher::DoStop() { |
OnSessionDetached(); |
} |
- if (launcher_.get() == NULL) { |
- CompleteStopping(); |
+ job_.Close(); |
+ |
+ // Drain the completion queue to make sure all job object notification have |
+ // been received. |
+ if (job_state_ == kJobRunning) { |
+ job_state_ = kJobStopping; |
+ ipc_message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &WtsSessionProcessLauncher::DrainJobNotifications, |
+ base::Unretained(this))); |
+ } |
+ |
+ // Don't complete shutdown if |launcher_| is not completely stopped. |
+ if (launcher_.get() != NULL) { |
+ return; |
+ } |
+ |
+ // Don't complete shutdown if the completion queue hasn't been drained. |
+ if (job_state_ != kJobUninitialized && job_state_ != kJobStopped) { |
+ return; |
+ } |
+ |
+ CompleteStopping(); |
+} |
+ |
+void WtsSessionProcessLauncher::DrainJobNotifications() { |
+ DCHECK(ipc_message_loop_->BelongsToCurrentThread()); |
+ |
+ // DrainJobNotifications() is posted after the job object is destroyed, so |
+ // by this time all notifications from the job object have been processed |
+ // already. Let the main thread know that the queue has been drained. |
+ main_message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &WtsSessionProcessLauncher::DrainJobNotificationsCompleted, |
+ base::Unretained(this))); |
+} |
+ |
+void WtsSessionProcessLauncher::DrainJobNotificationsCompleted() { |
+ DCHECK(main_message_loop_->BelongsToCurrentThread()); |
+ DCHECK_EQ(job_state_, kJobStopping); |
+ |
+ job_state_ = kJobStopped; |
+ Stop(); |
+} |
+ |
+void WtsSessionProcessLauncher::InitializeJob() { |
+ DCHECK(ipc_message_loop_->BelongsToCurrentThread()); |
+ |
+ ScopedHandle job; |
+ job.Set(CreateJobObject(NULL, NULL)); |
+ if (!job.IsValid()) { |
+ LOG_GETLASTERROR(ERROR) << "Failed to create a job object"; |
+ return; |
+ } |
+ |
+ // Limit the number of active processes in the job to two (the process |
+ // performing elevation and the host) and make sure that all processes will be |
+ // killed once the job object is destroyed. |
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; |
+ memset(&info, 0, sizeof(info)); |
+ info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS | |
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; |
+ info.BasicLimitInformation.ActiveProcessLimit = 2; |
+ if (!SetInformationJobObject(job, |
+ JobObjectExtendedLimitInformation, |
+ &info, |
+ sizeof(info))) { |
+ LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object"; |
+ return; |
+ } |
+ |
+ // Register the job object with the completion port in the I/O thread to |
+ // receive job notifications. |
+ if (!MessageLoopForIO::current()->RegisterJobObject(job, this)) { |
+ LOG_GETLASTERROR(ERROR) |
+ << "Failed to associate the job object with a completion port"; |
+ return; |
+ } |
+ |
+ // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped |
+ // pointer. |
+ scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle()); |
+ *job_wrapper = job.Pass(); |
+ |
+ // Let the main thread know that initialization is complete. |
+ main_message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &WtsSessionProcessLauncher::InitializeJobCompleted, |
+ base::Unretained(this), base::Passed(&job_wrapper))); |
+} |
+ |
+void WtsSessionProcessLauncher::InitializeJobCompleted( |
+ scoped_ptr<ScopedHandle> job) { |
+ DCHECK(main_message_loop_->BelongsToCurrentThread()); |
+ DCHECK(!job_.IsValid()); |
+ DCHECK_EQ(job_state_, kJobUninitialized); |
+ |
+ if (stoppable_state() == Stoppable::kRunning) { |
+ job_ = job->Pass(); |
+ job_state_ = kJobRunning; |
+ } |
+} |
+ |
+void WtsSessionProcessLauncher::OnJobNotification(DWORD message, DWORD pid) { |
+ DCHECK(main_message_loop_->BelongsToCurrentThread()); |
+ |
+ switch (message) { |
+ case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: |
+ CHECK(SetEvent(process_exit_event_)); |
+ break; |
+ |
+ case JOB_OBJECT_MSG_NEW_PROCESS: |
+ // We report the exit code of the worker process to be |CONTROL_C_EXIT| |
+ // if we cannot get the actual exit code. So here we can safely ignore |
+ // the error returned by OpenProcess(). |
+ worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)); |
+ break; |
+ } |
+} |
+ |
+void WtsSessionProcessLauncher::LaunchProcess() { |
+ DCHECK(main_message_loop_->BelongsToCurrentThread()); |
+ DCHECK(attached_); |
+ DCHECK(launcher_.get() == NULL); |
+ DCHECK(!timer_.IsRunning()); |
+ |
+ 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 = CONTROL_C_EXIT; |
+ if (worker_process_.IsValid()) { |
+ if (!::GetExitCodeProcess(worker_process_, &exit_code)) { |
+ LOG_GETLASTERROR(INFO) |
+ << "Failed to query the exit code of the worker process"; |
+ exit_code = CONTROL_C_EXIT; |
+ } |
+ |
+ worker_process_.Close(); |
+ } |
+ |
+ launcher_.reset(NULL); |
+ |
+ // Do not relaunch the worker process if the caller has asked us to stop. |
+ if (stoppable_state() != Stoppable::kRunning) { |
+ Stop(); |
+ 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); |
+ } |
+} |
+ |
+void WtsSessionProcessLauncher::OnSendSasToConsole() { |
+ DCHECK(main_message_loop_->BelongsToCurrentThread()); |
+ |
+ if (attached_) { |
+ if (sas_injector_.get() == NULL) { |
+ sas_injector_ = SasInjector::Create(); |
+ } |
+ |
+ if (sas_injector_.get() != NULL) { |
+ sas_injector_->InjectSas(); |
+ } |
} |
} |