Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 // | 4 // |
| 5 // This file implements the Windows service controlling Me2Me host processes | 5 // This file implements the Windows service controlling Me2Me host processes |
| 6 // running within user sessions. | 6 // running within user sessions. |
| 7 | 7 |
| 8 #include "remoting/host/win/wts_session_process_launcher.h" | 8 #include "remoting/host/win/wts_session_process_launcher.h" |
| 9 | 9 |
| 10 #include <windows.h> | 10 #include <windows.h> |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 40 namespace { | 40 namespace { |
| 41 | 41 |
| 42 // The minimum and maximum delays between attempts to inject host process into | 42 // The minimum and maximum delays between attempts to inject host process into |
| 43 // a session. | 43 // a session. |
| 44 const int kMaxLaunchDelaySeconds = 60; | 44 const int kMaxLaunchDelaySeconds = 60; |
| 45 const int kMinLaunchDelaySeconds = 1; | 45 const int kMinLaunchDelaySeconds = 1; |
| 46 | 46 |
| 47 const FilePath::CharType kMe2meHostBinaryName[] = | 47 const FilePath::CharType kMe2meHostBinaryName[] = |
| 48 FILE_PATH_LITERAL("remoting_me2me_host.exe"); | 48 FILE_PATH_LITERAL("remoting_me2me_host.exe"); |
| 49 | 49 |
| 50 const FilePath::CharType kMe2meServiceBinaryName[] = | |
| 51 FILE_PATH_LITERAL("remoting_service.exe"); | |
| 52 | |
| 50 // The IPC channel name is passed to the host in the command line. | 53 // The IPC channel name is passed to the host in the command line. |
| 51 const char kChromotingIpcSwitchName[] = "chromoting-ipc"; | 54 const char kChromotingIpcSwitchName[] = "chromoting-ipc"; |
| 52 | 55 |
| 56 const char kElevateSwitchName[] = "elevate"; | |
| 57 | |
| 53 // The command line parameters that should be copied from the service's command | 58 // The command line parameters that should be copied from the service's command |
| 54 // line to the host process. | 59 // line to the host process. |
| 55 const char* kCopiedSwitchNames[] = { | 60 const char* kCopiedSwitchNames[] = { |
| 56 "auth-config", "host-config", switches::kV, switches::kVModule }; | 61 "auth-config", "host-config", switches::kV, switches::kVModule }; |
| 57 | 62 |
| 58 // The security descriptor of the Chromoting IPC channel. It gives full access | 63 // The security descriptor of the Chromoting IPC channel. It gives full access |
| 59 // to LocalSystem and denies access by anyone else. | 64 // to LocalSystem and denies access by anyone else. |
| 60 const char kChromotingChannelSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; | 65 const char kChromotingChannelSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; |
| 61 | 66 |
| 62 } // namespace | 67 } // namespace |
| 63 | 68 |
| 64 namespace remoting { | 69 namespace remoting { |
| 65 | 70 |
| 66 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | 71 WtsSessionProcessLauncher::WtsSessionProcessLauncher( |
| 67 const base::Closure& stopped_callback, | 72 const base::Closure& stopped_callback, |
| 68 WtsConsoleMonitor* monitor, | 73 WtsConsoleMonitor* monitor, |
| 69 scoped_refptr<base::SingleThreadTaskRunner> main_message_loop, | 74 scoped_refptr<base::SingleThreadTaskRunner> main_message_loop, |
| 70 scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop) | 75 scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop) |
| 71 : Stoppable(main_message_loop, stopped_callback), | 76 : Stoppable(main_message_loop, stopped_callback), |
| 72 attached_(false), | 77 attached_(false), |
| 73 main_message_loop_(main_message_loop), | 78 main_message_loop_(main_message_loop), |
| 74 ipc_message_loop_(ipc_message_loop), | 79 ipc_message_loop_(ipc_message_loop), |
| 75 monitor_(monitor) { | 80 monitor_(monitor), |
| 81 job_state_(kJobUninitialized) { | |
| 76 monitor_->AddWtsConsoleObserver(this); | 82 monitor_->AddWtsConsoleObserver(this); |
| 83 | |
| 84 process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); | |
| 85 CHECK(process_exit_event_.IsValid()); | |
| 86 | |
| 87 // Job object initalization has to be performed on the I/O thread. | |
|
Wez
2012/08/14 01:08:59
nit: Why? :) e.g. because an I/O thread is needed
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 88 ipc_message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 89 &WtsSessionProcessLauncher::InitializeJob, | |
| 90 base::Unretained(this))); | |
| 77 } | 91 } |
| 78 | 92 |
| 79 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | 93 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { |
| 80 monitor_->RemoveWtsConsoleObserver(this); | 94 monitor_->RemoveWtsConsoleObserver(this); |
| 81 | 95 |
| 82 DCHECK(!attached_); | 96 DCHECK(!attached_); |
| 83 DCHECK(!timer_.IsRunning()); | 97 DCHECK(!timer_.IsRunning()); |
|
Wez
2012/08/14 01:08:59
nit: The destructor DCHECKs should probably be CHE
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 84 } | 98 } |
| 85 | 99 |
| 86 void WtsSessionProcessLauncher::LaunchProcess() { | 100 void WtsSessionProcessLauncher::LaunchProcess() { |
| 87 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 101 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 88 DCHECK(attached_); | 102 DCHECK(attached_); |
| 89 DCHECK(launcher_.get() == NULL); | 103 DCHECK(launcher_.get() == NULL); |
| 90 DCHECK(!timer_.IsRunning()); | 104 DCHECK(!timer_.IsRunning()); |
| 91 DCHECK(!worker_process_.IsValid()); | 105 DCHECK(!worker_process_.IsValid()); |
| 92 | 106 |
| 93 launch_time_ = base::Time::Now(); | 107 launch_time_ = base::Time::Now(); |
| 94 launcher_.reset(new WorkerProcessLauncher( | 108 launcher_.reset(new WorkerProcessLauncher( |
| 95 this, | 109 this, |
| 96 base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped, | 110 base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped, |
| 97 base::Unretained(this)), | 111 base::Unretained(this)), |
| 98 main_message_loop_, | 112 main_message_loop_, |
| 99 ipc_message_loop_)); | 113 ipc_message_loop_)); |
| 100 launcher_->Start(kChromotingChannelSecurityDescriptor); | 114 launcher_->Start(kChromotingChannelSecurityDescriptor); |
| 101 } | 115 } |
| 102 | 116 |
| 103 void WtsSessionProcessLauncher::OnLauncherStopped() { | 117 void WtsSessionProcessLauncher::OnLauncherStopped() { |
| 104 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 118 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 105 | 119 |
| 106 DWORD exit_code; | 120 DWORD exit_code = CONTROL_C_EXIT; |
| 107 if (!::GetExitCodeProcess(worker_process_, &exit_code)) { | 121 if (worker_process_.IsValid()) { |
| 108 LOG_GETLASTERROR(INFO) | 122 if (!::GetExitCodeProcess(worker_process_, &exit_code)) { |
| 109 << "Failed to query the exit code of the worker process"; | 123 LOG_GETLASTERROR(INFO) |
| 110 exit_code = CONTROL_C_EXIT; | 124 << "Failed to query the exit code of the worker process"; |
| 125 exit_code = CONTROL_C_EXIT; | |
| 126 } | |
| 127 | |
| 128 worker_process_.Close(); | |
| 111 } | 129 } |
| 112 | 130 |
| 113 launcher_.reset(NULL); | 131 launcher_.reset(NULL); |
| 114 worker_process_.Close(); | |
| 115 | 132 |
| 116 // Do not relaunch the worker process if the caller has asked us to stop. | 133 // Do not relaunch the worker process if the caller has asked us to stop. |
| 117 if (stoppable_state() != Stoppable::kRunning) { | 134 if (stoppable_state() != Stoppable::kRunning) { |
| 118 CompleteStopping(); | 135 Stop(); |
|
Wez
2012/08/14 01:08:59
I'm confused; isn't Stop() what the caller called
alexeypa (please no reviews)
2012/08/14 05:43:22
Yes, but it does not mean that Stop() is still on
| |
| 119 return; | 136 return; |
| 120 } | 137 } |
| 121 | 138 |
| 122 // Stop trying to restart the worker process if its process exited due to | 139 // Stop trying to restart the worker process if its process exited due to |
| 123 // misconfiguration. | 140 // misconfiguration. |
| 124 if (kMinPermanentErrorExitCode <= exit_code && | 141 if (kMinPermanentErrorExitCode <= exit_code && |
| 125 exit_code <= kMaxPermanentErrorExitCode) { | 142 exit_code <= kMaxPermanentErrorExitCode) { |
| 126 Stop(); | 143 Stop(); |
| 127 return; | 144 return; |
| 128 } | 145 } |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 141 launch_backoff_ = std::min( | 158 launch_backoff_ = std::min( |
| 142 launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | 159 launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); |
| 143 } | 160 } |
| 144 | 161 |
| 145 // Try to launch the worker process. | 162 // Try to launch the worker process. |
| 146 timer_.Start(FROM_HERE, launch_backoff_, | 163 timer_.Start(FROM_HERE, launch_backoff_, |
| 147 this, &WtsSessionProcessLauncher::LaunchProcess); | 164 this, &WtsSessionProcessLauncher::LaunchProcess); |
| 148 } | 165 } |
| 149 } | 166 } |
| 150 | 167 |
| 168 void WtsSessionProcessLauncher::OnIOCompleted( | |
| 169 base::MessagePumpForIO::IOContext* context, | |
| 170 DWORD bytes_transfered, | |
|
Wez
2012/08/14 01:08:59
typo: bytes_transferred
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 171 DWORD error) { | |
| 172 DCHECK(ipc_message_loop_->BelongsToCurrentThread()); | |
| 173 | |
| 174 // |bytes_transfered| is repurposed by the job object notifications to be | |
| 175 // the message ID. | |
|
Wez
2012/08/14 01:08:59
nit: Suggest "... used in job notifications to sup
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 176 BOOL result; | |
| 177 switch (bytes_transfered) { | |
| 178 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: | |
| 179 result = SetEvent(process_exit_event_); | |
| 180 CHECK(result); | |
| 181 break; | |
| 182 } | |
| 183 } | |
| 184 | |
| 151 bool WtsSessionProcessLauncher::DoLaunchProcess( | 185 bool WtsSessionProcessLauncher::DoLaunchProcess( |
| 152 const std::string& channel_name, | 186 const std::string& channel_name, |
| 153 ScopedHandle* process_exit_event_out) { | 187 ScopedHandle* process_exit_event_out) { |
| 154 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 188 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 155 DCHECK(!worker_process_.IsValid()); | 189 DCHECK(!worker_process_.IsValid()); |
| 156 | 190 |
| 191 // Wait until the sandbox is ready. | |
|
Wez
2012/08/14 01:08:59
nit: You mean that we can't launch until the sandb
alexeypa (please no reviews)
2012/08/14 05:43:22
Yes.
| |
| 192 if (!job_.IsValid()) { | |
| 193 return false; | |
| 194 } | |
| 195 | |
| 157 // Construct the host binary name. | 196 // Construct the host binary name. |
| 158 FilePath dir_path; | 197 FilePath dir_path; |
| 159 if (!PathService::Get(base::DIR_EXE, &dir_path)) { | 198 if (!PathService::Get(base::DIR_EXE, &dir_path)) { |
| 160 LOG(ERROR) << "Failed to get the executable file name."; | 199 LOG(ERROR) << "Failed to get the executable file name."; |
| 161 return false; | 200 return false; |
| 162 } | 201 } |
| 163 FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); | 202 FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); |
| 203 FilePath service_binary = dir_path.Append(kMe2meServiceBinaryName); | |
| 164 | 204 |
| 165 // Create the host process command line passing the name of the IPC channel | 205 // Create the host process command line passing the name of the IPC channel |
| 166 // to use and copying known switches from the service's command line. | 206 // to use and copying known switches from the service's command line. |
| 167 CommandLine command_line(host_binary); | 207 CommandLine command_line(service_binary); |
| 208 command_line.AppendSwitchPath(kElevateSwitchName, host_binary); | |
| 168 command_line.AppendSwitchNative(kChromotingIpcSwitchName, | 209 command_line.AppendSwitchNative(kChromotingIpcSwitchName, |
| 169 UTF8ToWide(channel_name)); | 210 UTF8ToWide(channel_name)); |
| 170 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), | 211 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), |
| 171 kCopiedSwitchNames, | 212 kCopiedSwitchNames, |
| 172 _countof(kCopiedSwitchNames)); | 213 _countof(kCopiedSwitchNames)); |
| 173 | 214 |
| 215 BOOL result = ResetEvent(process_exit_event_); | |
| 216 CHECK(result); | |
|
Wez
2012/08/14 01:08:59
nit: Do you need to create a local variable for th
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 217 | |
| 174 // Try to launch the process and attach an object watcher to the returned | 218 // Try to launch the process and attach an object watcher to the returned |
| 175 // handle so that we get notified when the process terminates. | 219 // handle so that we get notified when the process terminates. |
| 176 if (!LaunchProcessWithToken(host_binary, | 220 base::win::ScopedHandle worker_process; |
| 221 base::win::ScopedHandle worker_thread; | |
| 222 if (!LaunchProcessWithToken(service_binary, | |
| 177 command_line.GetCommandLineString(), | 223 command_line.GetCommandLineString(), |
| 178 session_token_, | 224 session_token_, |
| 179 &worker_process_)) { | 225 CREATE_SUSPENDED, |
| 226 &worker_process, | |
| 227 &worker_thread)) { | |
| 228 return false; | |
| 229 } | |
| 230 | |
| 231 if (!AssignProcessToJobObject(job_, worker_process)) { | |
| 232 LOG_GETLASTERROR(ERROR) << "Failed to assign the worker to the job object"; | |
| 233 TerminateProcess(worker_process, CONTROL_C_EXIT); | |
|
Wez
2012/08/14 01:08:59
nit: ::TerminateProcess()?
alexeypa (please no reviews)
2012/08/14 05:43:22
'::' is not necessary, less common in Chromium cod
| |
| 234 return false; | |
| 235 } | |
| 236 | |
| 237 if (!ResumeThread(worker_thread)) { | |
| 238 LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread"; | |
| 239 DoKillProcess(CONTROL_C_EXIT); | |
| 180 return false; | 240 return false; |
| 181 } | 241 } |
| 182 | 242 |
| 183 ScopedHandle process_exit_event; | 243 ScopedHandle process_exit_event; |
| 184 if (!DuplicateHandle(GetCurrentProcess(), | 244 if (!DuplicateHandle(GetCurrentProcess(), |
| 185 worker_process_, | 245 process_exit_event_, |
| 186 GetCurrentProcess(), | 246 GetCurrentProcess(), |
| 187 process_exit_event.Receive(), | 247 process_exit_event.Receive(), |
| 188 SYNCHRONIZE, | 248 SYNCHRONIZE, |
| 189 FALSE, | 249 FALSE, |
| 190 0)) { | 250 0)) { |
| 191 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; | 251 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; |
| 192 DoKillProcess(CONTROL_C_EXIT); | 252 DoKillProcess(CONTROL_C_EXIT); |
| 193 return false; | 253 return false; |
| 194 } | 254 } |
| 195 | 255 |
| 196 *process_exit_event_out = process_exit_event.Pass(); | 256 *process_exit_event_out = process_exit_event.Pass(); |
| 197 return true; | 257 return true; |
| 198 } | 258 } |
| 199 | 259 |
| 200 void WtsSessionProcessLauncher::DoKillProcess(DWORD exit_code) { | 260 void WtsSessionProcessLauncher::DoKillProcess(DWORD exit_code) { |
| 201 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 261 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 202 | 262 |
| 203 if (worker_process_.IsValid()) { | 263 if (job_.IsValid()) { |
| 204 TerminateProcess(worker_process_, exit_code); | 264 TerminateJobObject(job_, exit_code); |
| 205 } | 265 } |
| 206 } | 266 } |
| 207 | 267 |
| 208 void WtsSessionProcessLauncher::OnChannelConnected(DWORD peer_pid) { | 268 void WtsSessionProcessLauncher::OnChannelConnected(DWORD peer_pid) { |
| 209 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 269 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 210 | 270 |
| 211 DWORD expected_pid = GetProcessId(worker_process_); | 271 // Nothing to validate. Only processes running as SYSTEM can connect to |
| 212 if (peer_pid != expected_pid) { | 272 // the pipe, so if it was a malicious process the game would be over already. |
| 213 LOG(ERROR) | 273 |
| 214 << "Unexpected client connected: expected=" << expected_pid | 274 // Get a handle to the peer process so we could query its exit code later. |
| 215 << ", actual=" << peer_pid; | 275 // Ignore the error. If the processes cannot be open the error code is generic |
|
Wez
2012/08/14 01:08:59
typo: processes
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 216 Stop(); | 276 // |CONTROL_C_EXIT|. |
| 217 } | 277 worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, peer_pid)); |
| 218 } | 278 } |
| 219 | 279 |
| 220 bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { | 280 bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { |
| 221 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 281 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 222 | 282 |
| 223 bool handled = true; | 283 bool handled = true; |
| 224 IPC_BEGIN_MESSAGE_MAP(WtsSessionProcessLauncher, message) | 284 IPC_BEGIN_MESSAGE_MAP(WtsSessionProcessLauncher, message) |
| 225 IPC_MESSAGE_HANDLER(ChromotingHostMsg_SendSasToConsole, | 285 IPC_MESSAGE_HANDLER(ChromotingHostMsg_SendSasToConsole, |
| 226 OnSendSasToConsole) | 286 OnSendSasToConsole) |
| 227 IPC_MESSAGE_UNHANDLED(handled = false) | 287 IPC_MESSAGE_UNHANDLED(handled = false) |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 277 } | 337 } |
| 278 } | 338 } |
| 279 | 339 |
| 280 void WtsSessionProcessLauncher::DoStop() { | 340 void WtsSessionProcessLauncher::DoStop() { |
| 281 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 341 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 282 | 342 |
| 283 if (attached_) { | 343 if (attached_) { |
| 284 OnSessionDetached(); | 344 OnSessionDetached(); |
| 285 } | 345 } |
| 286 | 346 |
| 287 if (launcher_.get() == NULL) { | 347 job_.Close(); |
| 288 CompleteStopping(); | 348 |
| 349 // Drain the completion queue to make sure all job object notification have | |
| 350 // been received. | |
| 351 if (job_state_ > kJobUninitialized) { | |
|
Wez
2012/08/14 01:08:59
Ick. Do you really need to use more-than here?
alexeypa (please no reviews)
2012/08/14 05:43:22
No, that is wrong. It should list specific states.
| |
| 352 DCHECK_EQ(job_state_, kJobRunning); | |
| 353 job_state_ = kJobStopping; | |
| 354 ipc_message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 355 &WtsSessionProcessLauncher::DrainJobNotifications, | |
| 356 base::Unretained(this))); | |
| 357 } | |
| 358 | |
| 359 // Wait for |launcher_| to be completely stopped. | |
|
Wez
2012/08/14 01:08:59
nit: You're not waiting for anything here; you mea
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 360 if (launcher_.get() != NULL) { | |
| 361 return; | |
| 362 } | |
| 363 | |
| 364 // Wait for the completion queue to be drained. | |
|
Wez
2012/08/14 01:08:59
nit: Similarly, you mean don't complete shutdown u
alexeypa (please no reviews)
2012/08/14 05:43:22
Done.
| |
| 365 if (job_state_ != kJobUninitialized && job_state_ != kJobStopped) { | |
| 366 return; | |
| 367 } | |
| 368 | |
| 369 CompleteStopping(); | |
| 370 } | |
| 371 | |
| 372 void WtsSessionProcessLauncher::DrainJobNotifications() { | |
|
Wez
2012/08/14 01:08:59
nit: This method just posts a notification back to
alexeypa (please no reviews)
2012/08/14 05:43:22
|DrainJobNotifications| describes what happens, bu
| |
| 373 DCHECK(ipc_message_loop_->BelongsToCurrentThread()); | |
| 374 | |
| 375 // DrainJobNotifications() is posted after the job object is destroyed, so | |
| 376 // by this time all notifications from the job object have been processed | |
| 377 // already. Let the main thread know that the queue has been drained. | |
|
Wez
2012/08/14 01:08:59
This assumes that the notifications are queued to
alexeypa (please no reviews)
2012/08/14 05:43:22
Yes, no notifications can be posted after the job
| |
| 378 main_message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 379 &WtsSessionProcessLauncher::DrainJobNotificationsCompleted, | |
| 380 base::Unretained(this))); | |
| 381 } | |
| 382 | |
| 383 void WtsSessionProcessLauncher::DrainJobNotificationsCompleted() { | |
| 384 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 385 DCHECK_EQ(job_state_, kJobStopping); | |
| 386 | |
| 387 job_state_ = kJobStopped; | |
| 388 Stop(); | |
| 389 } | |
| 390 | |
| 391 void WtsSessionProcessLauncher::InitializeJob() { | |
| 392 DCHECK(ipc_message_loop_->BelongsToCurrentThread()); | |
| 393 | |
| 394 ScopedHandle job; | |
| 395 job.Set(CreateJobObject(NULL, NULL)); | |
| 396 if (!job.IsValid()) { | |
| 397 LOG_GETLASTERROR(ERROR) << "Failed to create a job object"; | |
| 398 return; | |
| 399 } | |
| 400 | |
| 401 // Limit number of active processes in the job to two (the process performing | |
| 402 // elevation and the host) and make sure that all processes will be killed | |
| 403 // once the job object is destroyed. | |
| 404 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; | |
| 405 memset(&info, 0, sizeof(info)); | |
| 406 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS | | |
| 407 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; | |
| 408 info.BasicLimitInformation.ActiveProcessLimit = 2; | |
| 409 if (!SetInformationJobObject(job, | |
| 410 JobObjectExtendedLimitInformation, | |
| 411 &info, | |
| 412 sizeof(info))) { | |
| 413 LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object"; | |
| 414 return; | |
| 415 } | |
| 416 | |
| 417 // Register the job object with the completion port in the I/O thread to | |
| 418 // receive job notifications. | |
| 419 if (!MessageLoopForIO::current()->RegisterJobObject(job, this)) { | |
| 420 LOG_GETLASTERROR(ERROR) | |
| 421 << "Failed to associate the job object with a completion port"; | |
| 422 return; | |
| 423 } | |
| 424 | |
| 425 // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped | |
| 426 // pointer. | |
| 427 scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle()); | |
| 428 *job_wrapper = job.Pass(); | |
| 429 | |
| 430 // Let the main thread know that initialization is complete. | |
| 431 main_message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 432 &WtsSessionProcessLauncher::InitializeJobCompleted, | |
| 433 base::Unretained(this), base::Passed(&job_wrapper))); | |
| 434 } | |
| 435 | |
| 436 void WtsSessionProcessLauncher::InitializeJobCompleted( | |
| 437 scoped_ptr<ScopedHandle> job) { | |
| 438 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 439 DCHECK(!job_.IsValid()); | |
| 440 DCHECK_EQ(job_state_, kJobUninitialized); | |
| 441 | |
| 442 if (stoppable_state() == Stoppable::kRunning) { | |
| 443 job_ = job->Pass(); | |
| 444 job_state_ = kJobRunning; | |
| 289 } | 445 } |
| 290 } | 446 } |
| 291 | 447 |
| 292 } // namespace remoting | 448 } // namespace remoting |
| OLD | NEW |