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/wts_session_process_launcher_win.h" | 8 #include "remoting/host/wts_session_process_launcher_win.h" |
9 | 9 |
10 #include <windows.h> | 10 #include <windows.h> |
11 #include <sddl.h> | |
12 #include <limits> | |
11 | 13 |
12 #include "base/logging.h" | 14 #include "base/logging.h" |
15 #include "base/process_util.h" | |
16 #include "base/rand_util.h" | |
17 #include "base/string16.h" | |
18 #include "base/stringprintf.h" | |
19 #include "base/threading/thread.h" | |
13 #include "base/utf_string_conversions.h" | 20 #include "base/utf_string_conversions.h" |
14 #include "base/win/scoped_handle.h" | 21 #include "base/win/scoped_handle.h" |
22 #include "ipc/ipc_channel_proxy.h" | |
23 #include "ipc/ipc_message.h" | |
24 #include "ipc/ipc_message_macros.h" | |
15 | 25 |
26 #include "remoting/host/chromoting_messages.h" | |
27 #include "remoting/host/sas_injector_win.h" | |
16 #include "remoting/host/wts_console_monitor_win.h" | 28 #include "remoting/host/wts_console_monitor_win.h" |
17 | 29 |
18 using base::win::ScopedHandle; | 30 using base::win::ScopedHandle; |
19 using base::TimeDelta; | 31 using base::TimeDelta; |
20 | 32 |
21 namespace { | 33 namespace { |
22 | 34 |
23 // The minimum and maximum delays between attempts to inject host process into | 35 // The minimum and maximum delays between attempts to inject host process into |
24 // a session. | 36 // a session. |
25 const int kMaxLaunchDelaySeconds = 60; | 37 const int kMaxLaunchDelaySeconds = 60; |
26 const int kMinLaunchDelaySeconds = 1; | 38 const int kMinLaunchDelaySeconds = 1; |
27 | 39 |
28 // Name of the default session desktop. | 40 // Name of the default session desktop. |
29 const char kDefaultDesktopName[] = "winsta0\\default"; | 41 const char kDefaultDesktopName[] = "winsta0\\default"; |
30 | 42 |
43 const char kChromePipeNamePrefix[] = "\\\\.\\pipe\\chrome."; | |
Wez
2012/03/08 22:58:55
nit: Comment this.
alexeypa (please no reviews)
2012/03/09 01:13:54
Done.
| |
44 | |
45 // Generatesthe command line of the host process. | |
Wez
2012/03/08 22:58:55
typo: Generates the ...
alexeypa (please no reviews)
2012/03/09 01:13:54
Done.
| |
46 const char kHostProcessCommandLineFormat[] = "\"%ls\" --channel=%ls"; | |
47 | |
48 // The security descriptor of the chromoting IPC channel. It gives full access | |
49 // to LocalSystem and denies access by anyone else. | |
50 const char kChromotingChannelSecurityDescriptor[] = | |
51 "O:SY" "G:SY" "D:(A;;GA;;;SY)"; | |
52 | |
31 // Takes the process token and makes a copy of it. The returned handle will have | 53 // Takes the process token and makes a copy of it. The returned handle will have |
32 // |desired_access| rights. | 54 // |desired_access| rights. |
33 bool CopyProcessToken(DWORD desired_access, | 55 bool CopyProcessToken(DWORD desired_access, |
34 ScopedHandle* token_out) { | 56 ScopedHandle* token_out) { |
35 | 57 |
36 HANDLE handle; | 58 HANDLE handle; |
37 if (!OpenProcessToken(GetCurrentProcess(), | 59 if (!OpenProcessToken(GetCurrentProcess(), |
38 TOKEN_DUPLICATE | desired_access, | 60 TOKEN_DUPLICATE | desired_access, |
39 &handle)) { | 61 &handle)) { |
40 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; | 62 LOG_GETLASTERROR(ERROR) << "Failed to open process token"; |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
107 sizeof(new_session_id))) { | 129 sizeof(new_session_id))) { |
108 LOG_GETLASTERROR(ERROR) << | 130 LOG_GETLASTERROR(ERROR) << |
109 "Failed to change session ID of a token"; | 131 "Failed to change session ID of a token"; |
110 return false; | 132 return false; |
111 } | 133 } |
112 | 134 |
113 token_out->Set(session_token.Take()); | 135 token_out->Set(session_token.Take()); |
114 return true; | 136 return true; |
115 } | 137 } |
116 | 138 |
139 // Generates random channel ID. | |
140 // N.B. Stolen from src/content/common/child_process_host_impl.cc | |
141 string16 GenerateRandomChannelId(void* instance) { | |
142 return base::StringPrintf(ASCIIToUTF16("%d.%p.%d").c_str(), | |
143 base::GetCurrentProcId(), instance, | |
144 base::RandInt(0, std::numeric_limits<int>::max())); | |
145 } | |
146 | |
147 // Creates the server end of the Chromoting IPC channel. | |
Wez
2012/03/08 22:58:55
nit: Similar comment "based on IPC::Channel"?
alexeypa (please no reviews)
2012/03/09 01:13:54
Done.
| |
148 bool CreatePipeForIpcChannel(void* instance, | |
149 string16* channel_name_out, | |
150 ScopedHandle* pipe_out) { | |
151 // Create security descriptor for the channel. | |
152 SECURITY_ATTRIBUTES security_attributes; | |
153 security_attributes.nLength = sizeof(security_attributes); | |
154 security_attributes.bInheritHandle = FALSE; | |
155 | |
156 ULONG security_descriptor_length = 0; | |
157 if (!ConvertStringSecurityDescriptorToSecurityDescriptorA( | |
158 kChromotingChannelSecurityDescriptor, | |
159 SDDL_REVISION_1, | |
160 reinterpret_cast<PSECURITY_DESCRIPTOR*>( | |
161 &security_attributes.lpSecurityDescriptor), | |
162 &security_descriptor_length)) { | |
163 LOG_GETLASTERROR(ERROR) << | |
164 "Failed to create a security descriptor for the Chromoting IPC channel"; | |
165 return false; | |
166 } | |
167 | |
168 // Generate a random channel name. | |
169 string16 channel_name(GenerateRandomChannelId(instance)); | |
170 | |
171 // Convert it to the pipe name. | |
172 string16 pipe_name(ASCIIToUTF16(kChromePipeNamePrefix)); | |
173 pipe_name.append(channel_name); | |
174 | |
175 // Create the server end of the pipe. This code should match the code in | |
176 // IPC::Channel with exception of passing a non-default security descriptor. | |
Wez
2012/03/08 22:58:55
Not for this CL, but would adding support for this
alexeypa (please no reviews)
2012/03/09 01:13:54
Probably.
| |
177 HANDLE pipe = CreateNamedPipeW(pipe_name.c_str(), | |
178 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | | |
179 FILE_FLAG_FIRST_PIPE_INSTANCE, | |
180 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, | |
181 1, | |
182 IPC::Channel::kReadBufferSize, | |
183 IPC::Channel::kReadBufferSize, | |
184 5000, | |
185 &security_attributes); | |
186 if (pipe == INVALID_HANDLE_VALUE) { | |
187 LOG_GETLASTERROR(ERROR) << | |
188 "Failed to create the server end of the Chromoting IPC channel"; | |
189 LocalFree(security_attributes.lpSecurityDescriptor); | |
190 return false; | |
191 } | |
192 | |
193 LocalFree(security_attributes.lpSecurityDescriptor); | |
194 | |
195 *channel_name_out = channel_name; | |
196 pipe_out->Set(pipe); | |
197 return true; | |
198 } | |
199 | |
117 // Launches |binary| in the security context of the supplied |user_token|. | 200 // Launches |binary| in the security context of the supplied |user_token|. |
118 bool LaunchProcessAsUser(const FilePath& binary, | 201 bool LaunchProcessAsUser(const FilePath& binary, |
202 const string16& command_line, | |
119 HANDLE user_token, | 203 HANDLE user_token, |
120 base::Process* process_out) { | 204 base::Process* process_out) { |
121 string16 command_line = binary.value(); | 205 string16 application_name = binary.value(); |
122 string16 desktop = ASCIIToUTF16(kDefaultDesktopName); | 206 string16 desktop = ASCIIToUTF16(kDefaultDesktopName); |
123 | 207 |
124 PROCESS_INFORMATION process_info; | 208 PROCESS_INFORMATION process_info; |
125 STARTUPINFOW startup_info; | 209 STARTUPINFOW startup_info; |
126 | 210 |
127 memset(&startup_info, 0, sizeof(startup_info)); | 211 memset(&startup_info, 0, sizeof(startup_info)); |
128 startup_info.cb = sizeof(startup_info); | 212 startup_info.cb = sizeof(startup_info); |
129 startup_info.lpDesktop = const_cast<LPWSTR>(desktop.c_str()); | 213 startup_info.lpDesktop = const_cast<LPWSTR>(desktop.c_str()); |
130 | 214 |
131 if (!CreateProcessAsUserW(user_token, | 215 if (!CreateProcessAsUserW(user_token, |
132 command_line.c_str(), | 216 application_name.c_str(), |
133 const_cast<LPWSTR>(command_line.c_str()), | 217 const_cast<LPWSTR>(command_line.c_str()), |
134 NULL, | 218 NULL, |
135 NULL, | 219 NULL, |
136 FALSE, | 220 FALSE, |
137 0, | 221 0, |
138 NULL, | 222 NULL, |
139 NULL, | 223 NULL, |
140 &startup_info, | 224 &startup_info, |
141 &process_info)) { | 225 &process_info)) { |
142 LOG_GETLASTERROR(ERROR) << | 226 LOG_GETLASTERROR(ERROR) << |
143 "Failed to launch a process with a user token"; | 227 "Failed to launch a process with a user token"; |
144 return false; | 228 return false; |
145 } | 229 } |
146 | 230 |
147 CloseHandle(process_info.hThread); | 231 CloseHandle(process_info.hThread); |
148 process_out->set_handle(process_info.hProcess); | 232 process_out->set_handle(process_info.hProcess); |
149 return true; | 233 return true; |
150 } | 234 } |
151 | 235 |
152 } // namespace | 236 } // namespace |
153 | 237 |
154 namespace remoting { | 238 namespace remoting { |
155 | 239 |
156 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | 240 WtsSessionProcessLauncher::WtsSessionProcessLauncher( |
157 WtsConsoleMonitor* monitor, | 241 WtsConsoleMonitor* monitor, |
158 const FilePath& host_binary) | 242 const FilePath& host_binary, |
243 base::Thread* io_thread) | |
159 : host_binary_(host_binary), | 244 : host_binary_(host_binary), |
245 io_thread_(io_thread), | |
160 monitor_(monitor), | 246 monitor_(monitor), |
161 state_(StateDetached) { | 247 state_(StateDetached) { |
162 monitor_->AddWtsConsoleObserver(this); | 248 monitor_->AddWtsConsoleObserver(this); |
163 } | 249 } |
164 | 250 |
165 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | 251 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { |
166 DCHECK(state_ == StateDetached); | 252 DCHECK(state_ == StateDetached); |
167 DCHECK(!timer_.IsRunning()); | 253 DCHECK(!timer_.IsRunning()); |
168 DCHECK(process_.handle() == NULL); | 254 DCHECK(process_.handle() == NULL); |
169 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 255 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
256 DCHECK(chromoting_channel_.get() == NULL); | |
170 | 257 |
171 monitor_->RemoveWtsConsoleObserver(this); | 258 monitor_->RemoveWtsConsoleObserver(this); |
172 } | 259 } |
173 | 260 |
174 void WtsSessionProcessLauncher::LaunchProcess() { | 261 void WtsSessionProcessLauncher::LaunchProcess() { |
175 DCHECK(state_ == StateStarting); | 262 DCHECK(state_ == StateStarting); |
176 DCHECK(!timer_.IsRunning()); | 263 DCHECK(!timer_.IsRunning()); |
177 DCHECK(process_.handle() == NULL); | 264 DCHECK(process_.handle() == NULL); |
178 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 265 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
266 DCHECK(chromoting_channel_.get() == NULL); | |
179 | 267 |
180 // Try to launch the process and attach an object watcher to the returned | |
181 // handle so that we get notified when the process terminates. | |
182 launch_time_ = base::Time::Now(); | 268 launch_time_ = base::Time::Now(); |
183 if (LaunchProcessAsUser(host_binary_, session_token_, &process_)) { | 269 |
184 if (process_watcher_.StartWatching(process_.handle(), this)) { | 270 string16 channel_name; |
185 state_ = StateAttached; | 271 ScopedHandle pipe; |
186 return; | 272 if (CreatePipeForIpcChannel(this, &channel_name, &pipe)) { |
187 } else { | 273 // Wrap the pipe into an IPC channel. |
188 LOG(ERROR) << "Failed to arm the process watcher."; | 274 chromoting_channel_.reset(new IPC::ChannelProxy( |
189 process_.Terminate(0); | 275 IPC::ChannelHandle(pipe.Get()), |
Wez
2012/03/08 22:58:55
nit: This indentation looks weird; can you put the
alexeypa (please no reviews)
2012/03/09 01:13:54
Done.
| |
190 process_.Close(); | 276 IPC::Channel::MODE_SERVER, |
277 this, | |
278 io_thread_->message_loop_proxy().get())); | |
279 | |
280 string16 command_line = | |
281 base::StringPrintf(ASCIIToUTF16(kHostProcessCommandLineFormat).c_str(), | |
282 host_binary_.value().c_str(), | |
283 channel_name.c_str()); | |
284 | |
285 // Try to launch the process and attach an object watcher to the returned | |
286 // handle so that we get notified when the process terminates. | |
287 if (LaunchProcessAsUser(host_binary_, command_line, session_token_, | |
288 &process_)) { | |
289 if (process_watcher_.StartWatching(process_.handle(), this)) { | |
290 state_ = StateAttached; | |
291 return; | |
292 } else { | |
293 LOG(ERROR) << "Failed to arm the process watcher."; | |
294 process_.Terminate(0); | |
295 process_.Close(); | |
296 } | |
191 } | 297 } |
298 | |
299 chromoting_channel_.reset(); | |
192 } | 300 } |
193 | 301 |
194 // Something went wrong. Try to launch the host again later. The attempts rate | 302 // Something went wrong. Try to launch the host again later. The attempts rate |
195 // is limited by exponential backoff. | 303 // is limited by exponential backoff. |
196 launch_backoff_ = std::max(launch_backoff_ * 2, | 304 launch_backoff_ = std::max(launch_backoff_ * 2, |
197 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | 305 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); |
198 launch_backoff_ = std::min(launch_backoff_, | 306 launch_backoff_ = std::min(launch_backoff_, |
199 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | 307 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); |
200 timer_.Start(FROM_HERE, launch_backoff_, | 308 timer_.Start(FROM_HERE, launch_backoff_, |
201 this, &WtsSessionProcessLauncher::LaunchProcess); | 309 this, &WtsSessionProcessLauncher::LaunchProcess); |
202 } | 310 } |
203 | 311 |
204 void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { | 312 void WtsSessionProcessLauncher::OnObjectSignaled(HANDLE object) { |
205 DCHECK(state_ == StateAttached); | 313 DCHECK(state_ == StateAttached); |
206 DCHECK(!timer_.IsRunning()); | 314 DCHECK(!timer_.IsRunning()); |
207 DCHECK(process_.handle() != NULL); | 315 DCHECK(process_.handle() != NULL); |
208 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 316 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
317 DCHECK(chromoting_channel_.get() != NULL); | |
209 | 318 |
210 // The host process has been terminated for some reason. The handle can now be | 319 // The host process has been terminated for some reason. The handle can now be |
211 // closed. | 320 // closed. |
212 process_.Close(); | 321 process_.Close(); |
322 chromoting_channel_.reset(); | |
213 | 323 |
214 // Expand the backoff interval if the process has died quickly or reset it if | 324 // Expand the backoff interval if the process has died quickly or reset it if |
215 // it was up longer than the maximum backoff delay. | 325 // it was up longer than the maximum backoff delay. |
216 base::TimeDelta delta = base::Time::Now() - launch_time_; | 326 base::TimeDelta delta = base::Time::Now() - launch_time_; |
217 if (delta < base::TimeDelta() || | 327 if (delta < base::TimeDelta() || |
218 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { | 328 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { |
219 launch_backoff_ = base::TimeDelta(); | 329 launch_backoff_ = base::TimeDelta(); |
220 } else { | 330 } else { |
221 launch_backoff_ = std::max(launch_backoff_ * 2, | 331 launch_backoff_ = std::max(launch_backoff_ * 2, |
222 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | 332 TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); |
223 launch_backoff_ = std::min(launch_backoff_, | 333 launch_backoff_ = std::min(launch_backoff_, |
224 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | 334 TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); |
225 } | 335 } |
226 | 336 |
227 // Try to restart the host. | 337 // Try to restart the host. |
228 state_ = StateStarting; | 338 state_ = StateStarting; |
229 timer_.Start(FROM_HERE, launch_backoff_, | 339 timer_.Start(FROM_HERE, launch_backoff_, |
230 this, &WtsSessionProcessLauncher::LaunchProcess); | 340 this, &WtsSessionProcessLauncher::LaunchProcess); |
231 } | 341 } |
232 | 342 |
343 bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { | |
344 bool handled = true; | |
345 IPC_BEGIN_MESSAGE_MAP(WtsSessionProcessLauncher, message) | |
346 IPC_MESSAGE_HANDLER(ChromotingHostMsg_SendSasToConsole, | |
347 OnSendSasToConsole) | |
348 IPC_MESSAGE_UNHANDLED(handled = false) | |
349 IPC_END_MESSAGE_MAP() | |
350 return handled; | |
351 } | |
352 | |
353 void WtsSessionProcessLauncher::OnSendSasToConsole() { | |
354 if (state_ == StateAttached) { | |
355 if (sas_injector_.get() == NULL) { | |
356 sas_injector_ = SasInjector::Create(); | |
357 } | |
358 | |
359 if (sas_injector_.get() != NULL) { | |
360 sas_injector_->InjectSas(); | |
361 } | |
362 } | |
363 } | |
364 | |
233 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { | 365 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { |
234 DCHECK(state_ == StateDetached); | 366 DCHECK(state_ == StateDetached); |
235 DCHECK(!timer_.IsRunning()); | 367 DCHECK(!timer_.IsRunning()); |
236 DCHECK(process_.handle() == NULL); | 368 DCHECK(process_.handle() == NULL); |
237 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 369 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
370 DCHECK(chromoting_channel_.get() == NULL); | |
238 | 371 |
239 // Temporarily enable the SE_TCB_NAME privilege. The privileged token is | 372 // Temporarily enable the SE_TCB_NAME privilege. The privileged token is |
240 // created as needed and kept for later reuse. | 373 // created as needed and kept for later reuse. |
241 if (privileged_token_.Get() == NULL) { | 374 if (privileged_token_.Get() == NULL) { |
242 if (!CreatePrivilegedToken(&privileged_token_)) { | 375 if (!CreatePrivilegedToken(&privileged_token_)) { |
243 return; | 376 return; |
244 } | 377 } |
245 } | 378 } |
246 | 379 |
247 if (!ImpersonateLoggedOnUser(privileged_token_)) { | 380 if (!ImpersonateLoggedOnUser(privileged_token_)) { |
248 LOG_GETLASTERROR(ERROR) << | 381 LOG_GETLASTERROR(ERROR) << |
249 "Failed to impersonate the privileged token"; | 382 "Failed to impersonate the privileged token"; |
250 return; | 383 return; |
251 } | 384 } |
252 | 385 |
253 // While the SE_TCB_NAME progolege is enabled, create a session token for | 386 // While the SE_TCB_NAME privilege is enabled, create a session token for |
254 // the launched process. | 387 // the launched process. |
255 bool result = CreateSessionToken(session_id, &session_token_); | 388 bool result = CreateSessionToken(session_id, &session_token_); |
256 | 389 |
257 // Revert to the default token. The default token is sufficient to call | 390 // Revert to the default token. The default token is sufficient to call |
258 // CreateProcessAsUser() successfully. | 391 // CreateProcessAsUser() successfully. |
259 CHECK(RevertToSelf()); | 392 CHECK(RevertToSelf()); |
260 | 393 |
261 if (!result) | 394 if (!result) |
262 return; | 395 return; |
263 | 396 |
264 // Now try to launch the host. | 397 // Now try to launch the host. |
265 state_ = StateStarting; | 398 state_ = StateStarting; |
266 LaunchProcess(); | 399 LaunchProcess(); |
267 } | 400 } |
268 | 401 |
269 void WtsSessionProcessLauncher::OnSessionDetached() { | 402 void WtsSessionProcessLauncher::OnSessionDetached() { |
270 DCHECK(state_ == StateDetached || | 403 DCHECK(state_ == StateDetached || |
271 state_ == StateStarting || | 404 state_ == StateStarting || |
272 state_ == StateAttached); | 405 state_ == StateAttached); |
273 | 406 |
274 switch (state_) { | 407 switch (state_) { |
275 case StateDetached: | 408 case StateDetached: |
276 DCHECK(!timer_.IsRunning()); | 409 DCHECK(!timer_.IsRunning()); |
277 DCHECK(process_.handle() == NULL); | 410 DCHECK(process_.handle() == NULL); |
278 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 411 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
412 DCHECK(chromoting_channel_.get() == NULL); | |
279 break; | 413 break; |
280 | 414 |
281 case StateStarting: | 415 case StateStarting: |
282 DCHECK(timer_.IsRunning()); | 416 DCHECK(timer_.IsRunning()); |
283 DCHECK(process_.handle() == NULL); | 417 DCHECK(process_.handle() == NULL); |
284 DCHECK(process_watcher_.GetWatchedObject() == NULL); | 418 DCHECK(process_watcher_.GetWatchedObject() == NULL); |
419 DCHECK(chromoting_channel_.get() == NULL); | |
285 | 420 |
286 timer_.Stop(); | 421 timer_.Stop(); |
287 launch_backoff_ = base::TimeDelta(); | 422 launch_backoff_ = base::TimeDelta(); |
288 state_ = StateDetached; | 423 state_ = StateDetached; |
289 break; | 424 break; |
290 | 425 |
291 case StateAttached: | 426 case StateAttached: |
292 DCHECK(!timer_.IsRunning()); | 427 DCHECK(!timer_.IsRunning()); |
293 DCHECK(process_.handle() != NULL); | 428 DCHECK(process_.handle() != NULL); |
294 DCHECK(process_watcher_.GetWatchedObject() != NULL); | 429 DCHECK(process_watcher_.GetWatchedObject() != NULL); |
430 DCHECK(chromoting_channel_.get() != NULL); | |
295 | 431 |
296 process_watcher_.StopWatching(); | 432 process_watcher_.StopWatching(); |
297 process_.Terminate(0); | 433 process_.Terminate(0); |
298 process_.Close(); | 434 process_.Close(); |
435 chromoting_channel_.reset(); | |
299 state_ = StateDetached; | 436 state_ = StateDetached; |
300 break; | 437 break; |
301 } | 438 } |
302 } | 439 } |
303 | 440 |
304 } // namespace remoting | 441 } // namespace remoting |
OLD | NEW |