| 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();
|
| + }
|
| }
|
| }
|
|
|
|
|