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> |
| 11 #include <sddl.h> | 11 #include <sddl.h> |
| 12 #include <limits> | 12 #include <limits> |
| 13 | 13 |
| 14 #include "base/base_switches.h" | 14 #include "base/base_switches.h" |
| 15 #include "base/bind.h" | 15 #include "base/bind.h" |
| 16 #include "base/bind_helpers.h" | 16 #include "base/bind_helpers.h" |
| 17 #include "base/command_line.h" | 17 #include "base/command_line.h" |
| 18 #include "base/file_path.h" | 18 #include "base/file_path.h" |
| 19 #include "base/file_util.h" | 19 #include "base/file_util.h" |
| 20 #include "base/logging.h" | 20 #include "base/logging.h" |
| 21 #include "base/single_thread_task_runner.h" | 21 #include "base/single_thread_task_runner.h" |
| 22 #include "base/path_service.h" | 22 #include "base/path_service.h" |
| 23 #include "base/rand_util.h" | 23 #include "base/rand_util.h" |
| 24 #include "base/stringprintf.h" | 24 #include "base/stringprintf.h" |
| 25 #include "base/utf_string_conversions.h" | 25 #include "base/utf_string_conversions.h" |
| 26 #include "base/win/scoped_handle.h" | 26 #include "base/win/scoped_handle.h" |
| 27 #include "base/win/windows_version.h" | |
| 27 #include "ipc/ipc_channel_proxy.h" | 28 #include "ipc/ipc_channel_proxy.h" |
| 28 #include "ipc/ipc_message.h" | 29 #include "ipc/ipc_message.h" |
| 29 #include "ipc/ipc_message_macros.h" | 30 #include "ipc/ipc_message_macros.h" |
| 30 #include "remoting/host/constants.h" | 31 #include "remoting/host/constants.h" |
| 31 #include "remoting/host/chromoting_messages.h" | 32 #include "remoting/host/chromoting_messages.h" |
| 32 #include "remoting/host/win/launch_process_with_token.h" | 33 #include "remoting/host/win/launch_process_with_token.h" |
| 33 #include "remoting/host/win/wts_console_monitor.h" | 34 #include "remoting/host/win/wts_console_monitor.h" |
| 34 | 35 |
| 35 using base::win::ScopedHandle; | 36 using base::win::ScopedHandle; |
| 36 using base::TimeDelta; | 37 using base::TimeDelta; |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 59 "host-config", switches::kV, switches::kVModule }; | 60 "host-config", switches::kV, switches::kVModule }; |
| 60 | 61 |
| 61 // The security descriptor of the daemon IPC endpoint. It gives full access | 62 // The security descriptor of the daemon IPC endpoint. It gives full access |
| 62 // to LocalSystem and denies access by anyone else. | 63 // to LocalSystem and denies access by anyone else. |
| 63 const char kDaemonIpcSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; | 64 const char kDaemonIpcSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; |
| 64 | 65 |
| 65 } // namespace | 66 } // namespace |
| 66 | 67 |
| 67 namespace remoting { | 68 namespace remoting { |
| 68 | 69 |
| 69 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | 70 class WtsProcessLauncherImpl |
| 70 const base::Closure& stopped_callback, | 71 : public base::RefCountedThreadSafe<WtsProcessLauncherImpl>, |
| 71 WtsConsoleMonitor* monitor, | 72 public WorkerProcessLauncher::Delegate { |
| 72 scoped_refptr<base::SingleThreadTaskRunner> main_message_loop, | 73 public: |
| 73 scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop) | 74 // Returns the exit code of the worker process. |
| 74 : Stoppable(main_message_loop, stopped_callback), | 75 virtual DWORD GetExitCode() = 0; |
| 75 attached_(false), | 76 |
| 76 main_message_loop_(main_message_loop), | 77 // Sets the token to be used to launch the worker process. |
| 77 ipc_message_loop_(ipc_message_loop), | 78 virtual bool SetSessionToken(HANDLE session_token) = 0; |
| 78 monitor_(monitor), | 79 |
| 79 job_state_(kJobUninitialized) { | 80 // Stops the object asynchronously. |
| 80 monitor_->AddWtsConsoleObserver(this); | 81 virtual void Stop() = 0; |
| 81 | 82 |
| 83 protected: | |
| 84 friend class base::RefCountedThreadSafe<WtsProcessLauncherImpl>; | |
| 85 virtual ~WtsProcessLauncherImpl(); | |
| 86 }; | |
| 87 | |
| 88 namespace { | |
| 89 | |
| 90 // Implements |WorkerProcessLauncher::Delegate| that starts the host process | |
|
Wez
2012/09/14 23:36:18
nit: host process -> specified process?
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 91 // in a different session via CreateProcessAsUser and uses the returned process | |
| 92 // handle to control it. | |
|
Wez
2012/09/14 23:36:18
nit: ... to monitor and close it. ?
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 93 class SingleProcessLauncher : public WtsProcessLauncherImpl { | |
| 94 public: | |
| 95 SingleProcessLauncher( | |
| 96 const FilePath& binary, | |
|
Wez
2012/09/14 23:36:18
nit: binary_path
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 97 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner); | |
| 98 | |
| 99 // WorkerProcessLauncher::Delegate implementation. | |
| 100 virtual bool DoLaunchProcess( | |
| 101 const std::string& channel_name, | |
| 102 base::win::ScopedHandle* process_exit_event_out) OVERRIDE; | |
| 103 virtual void DoKillProcess(DWORD exit_code) OVERRIDE; | |
| 104 | |
| 105 // WtsProcessLauncherImpl implementation. | |
| 106 virtual DWORD GetExitCode() OVERRIDE; | |
| 107 virtual bool SetSessionToken(HANDLE session_token) OVERRIDE; | |
| 108 virtual void Stop() OVERRIDE; | |
| 109 | |
| 110 private: | |
| 111 FilePath binary_; | |
| 112 | |
| 113 // The main service message loop. | |
| 114 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; | |
| 115 | |
| 116 // A waiting handle that becomes signalled once all process associated with | |
|
Wez
2012/09/14 23:36:18
nit: Handle that becomes ... once the process has
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 117 // the job have been terminated. | |
| 118 base::win::ScopedHandle process_exit_event_; | |
| 119 | |
| 120 // The token to be used to launch a process in a different session. | |
| 121 base::win::ScopedHandle session_token_; | |
| 122 | |
| 123 base::win::ScopedHandle worker_process_; | |
| 124 | |
| 125 DISALLOW_COPY_AND_ASSIGN(SingleProcessLauncher); | |
| 126 }; | |
| 127 | |
| 128 // Implements |WorkerProcessLauncher::Delegate| that starts the host process | |
| 129 // in a different session and uses job object to control it. This object | |
|
Wez
2012/09/14 23:36:18
nit: You mention it uses [a] job object, and then
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 130 // utilizes a helper process to bypass limitations of ShellExecute() which | |
| 131 // cannot spawn processes across the session boundary. ShellExecute() in its | |
| 132 // turn is needed to start a binary specifying uiAccess='true' in its manifest. | |
| 133 class InJobLauncher : public WtsProcessLauncherImpl, | |
|
Wez
2012/09/14 23:36:18
nit: ShellExecuteLauncher?
Then you can describe
alexeypa (please no reviews)
2012/09/18 16:58:06
Thinking about what this class is actually doing,
| |
| 134 public base::MessagePumpForIO::IOHandler { | |
| 135 public: | |
| 136 InJobLauncher( | |
| 137 const FilePath& binary, | |
| 138 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, | |
| 139 scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner); | |
| 140 | |
| 141 // base::MessagePumpForIO::IOHandler implementation. | |
| 142 virtual void OnIOCompleted(base::MessagePumpForIO::IOContext* context, | |
| 143 DWORD bytes_transferred, | |
| 144 DWORD error) OVERRIDE; | |
| 145 | |
| 146 // WorkerProcessLauncher::Delegate implementation. | |
| 147 virtual bool DoLaunchProcess( | |
| 148 const std::string& channel_name, | |
| 149 base::win::ScopedHandle* process_exit_event_out) OVERRIDE; | |
| 150 virtual void DoKillProcess(DWORD exit_code) OVERRIDE; | |
| 151 | |
| 152 // WtsProcessLauncherImpl implementation. | |
| 153 virtual DWORD GetExitCode() OVERRIDE; | |
| 154 virtual bool SetSessionToken(HANDLE session_token) OVERRIDE; | |
| 155 virtual void Stop() OVERRIDE; | |
| 156 | |
| 157 private: | |
| 158 // Drains the completion port queue to make sure that all job object | |
| 159 // notifications has been received. | |
|
Wez
2012/09/14 23:36:18
typo: has -> have
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 160 void DrainJobNotifications(); | |
| 161 | |
| 162 // Notifies that the completion port queue has been drained. | |
| 163 void DrainJobNotificationsCompleted(); | |
|
Wez
2012/09/14 23:36:18
Does this method trigger a notification, or is it
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 164 | |
| 165 // Creates and initializes the job object that will sandbox the launched child | |
| 166 // processes. | |
| 167 void InitializeJob(); | |
| 168 | |
| 169 // Notifies that the job object initialization is complete. | |
|
Wez
2012/09/14 23:36:18
nit: See above re "Notified when..."
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 170 void InitializeJobCompleted(scoped_ptr<base::win::ScopedHandle> job); | |
| 171 | |
| 172 void OnJobNotification(DWORD message, DWORD pid); | |
|
Wez
2012/09/14 23:36:18
nit: What's this for?
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 173 | |
| 174 FilePath binary_; | |
| 175 | |
| 176 // The main service message loop. | |
|
Wez
2012/09/14 23:36:18
Can you re-describe this in terms of what this cla
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 177 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; | |
| 178 | |
| 179 // Message loop used by the IPC channel. | |
| 180 scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_; | |
| 181 | |
| 182 // The job object used to control the lifetime of child processes. | |
| 183 base::win::ScopedHandle job_; | |
| 184 | |
| 185 // A waiting handle that becomes signalled once all process associated with | |
| 186 // the job have been terminated. | |
| 187 base::win::ScopedHandle process_exit_event_; | |
| 188 | |
| 189 // The token to be used to launch a process in a different session. | |
| 190 base::win::ScopedHandle session_token_; | |
| 191 | |
| 192 base::win::ScopedHandle worker_process_; | |
|
Wez
2012/09/14 23:36:18
nit: Handle to the worker process, if launched?
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 193 | |
| 194 DISALLOW_COPY_AND_ASSIGN(InJobLauncher); | |
| 195 }; | |
| 196 | |
| 197 SingleProcessLauncher::SingleProcessLauncher( | |
| 198 const FilePath& binary, | |
| 199 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner) | |
| 200 : binary_(binary), | |
| 201 main_task_runner_(main_task_runner) { | |
| 202 } | |
| 203 | |
| 204 bool SingleProcessLauncher::DoLaunchProcess( | |
| 205 const std::string& channel_name, | |
| 206 base::win::ScopedHandle* process_exit_event_out) { | |
| 207 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 208 | |
| 209 // Create the host process command line passing the name of the IPC channel | |
|
Wez
2012/09/14 23:36:18
nit: host process command line -> command line?
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 210 // to use and copying known switches from the service's command line. | |
|
Wez
2012/09/14 23:36:18
nit: service's -> caller's?
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 211 CommandLine command_line(binary_); | |
| 212 command_line.AppendSwitchNative(kDaemonIpcSwitchName, | |
| 213 UTF8ToWide(channel_name)); | |
| 214 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), | |
| 215 kCopiedSwitchNames, | |
| 216 _countof(kCopiedSwitchNames)); | |
| 217 | |
| 218 // Try to launch the process and attach an object watcher to the returned | |
| 219 // handle so that we get notified when the process terminates. | |
| 220 base::win::ScopedHandle worker_thread; | |
| 221 worker_process_.Close(); | |
| 222 if (!LaunchProcessWithToken(binary_, | |
| 223 command_line.GetCommandLineString(), | |
| 224 session_token_, | |
| 225 0, | |
| 226 &worker_process_, | |
| 227 &worker_thread)) { | |
| 228 return false; | |
| 229 } | |
| 230 | |
| 231 ScopedHandle process_exit_event; | |
| 232 if (!DuplicateHandle(GetCurrentProcess(), | |
| 233 worker_process_, | |
| 234 GetCurrentProcess(), | |
| 235 process_exit_event.Receive(), | |
| 236 SYNCHRONIZE, | |
| 237 FALSE, | |
| 238 0)) { | |
| 239 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; | |
| 240 DoKillProcess(CONTROL_C_EXIT); | |
| 241 return false; | |
| 242 } | |
| 243 | |
| 244 *process_exit_event_out = process_exit_event.Pass(); | |
| 245 return true; | |
| 246 } | |
| 247 | |
| 248 void SingleProcessLauncher::DoKillProcess(DWORD exit_code) { | |
| 249 DCHECK(main_task_runner_->BelongsToCurrentThread()); | |
| 250 | |
| 251 if (worker_process_.IsValid()) { | |
| 252 TerminateProcess(worker_process_, exit_code); | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 DWORD SingleProcessLauncher::GetExitCode() { | |
| 257 DWORD exit_code = CONTROL_C_EXIT; | |
| 258 if (worker_process_.IsValid()) { | |
| 259 if (!::GetExitCodeProcess(worker_process_, &exit_code)) { | |
| 260 LOG_GETLASTERROR(INFO) | |
| 261 << "Failed to query the exit code of the worker process"; | |
| 262 exit_code = CONTROL_C_EXIT; | |
| 263 } | |
| 264 | |
| 265 worker_process_.Close(); | |
| 266 } | |
| 267 | |
| 268 return exit_code; | |
| 269 } | |
| 270 | |
| 271 bool SingleProcessLauncher::SetSessionToken(HANDLE session_token) { | |
|
Wez
2012/09/14 23:36:18
nit: Rename this ResetUserToken() and have it take
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 272 session_token_.Close(); | |
| 273 if (!DuplicateHandle(GetCurrentProcess(), | |
| 274 session_token, | |
| 275 GetCurrentProcess(), | |
| 276 session_token_.Receive(), | |
| 277 0, | |
| 278 FALSE, | |
| 279 DUPLICATE_SAME_ACCESS)) { | |
| 280 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; | |
| 281 return false; | |
| 282 } | |
| 283 | |
| 284 return true; | |
| 285 } | |
| 286 | |
| 287 void SingleProcessLauncher::Stop() { | |
| 288 } | |
| 289 | |
| 290 InJobLauncher::InJobLauncher( | |
| 291 const FilePath& binary, | |
| 292 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, | |
| 293 scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner) | |
| 294 : binary_(binary), | |
| 295 main_task_runner_(main_task_runner), | |
| 296 ipc_task_runner_(ipc_task_runner) { | |
| 82 process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); | 297 process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL)); |
| 83 CHECK(process_exit_event_.IsValid()); | 298 CHECK(process_exit_event_.IsValid()); |
| 84 | 299 |
| 85 // To receive job object notifications it is registered with the completion | 300 // To receive job object notifications it is registered with the completion |
|
Wez
2012/09/14 23:36:18
nit: What is "it"? You mean this object?
alexeypa (please no reviews)
2012/09/18 16:58:06
Done.
| |
| 86 // port represented by |ipc_message_loop|. The registration has to be done on | 301 // port represented by |ipc_message_loop|. The registration has to be done on |
| 87 // the I/O thread because MessageLoopForIO::RegisterJobObject() can only be | 302 // the I/O thread because MessageLoopForIO::RegisterJobObject() can only be |
| 88 // called via MessageLoopForIO::current(). | 303 // called via MessageLoopForIO::current(). |
| 89 ipc_message_loop_->PostTask(FROM_HERE, base::Bind( | 304 ipc_task_runner_->PostTask(FROM_HERE, |
| 90 &WtsSessionProcessLauncher::InitializeJob, | 305 base::Bind(&InJobLauncher::InitializeJob, this)); |
| 91 base::Unretained(this))); | |
| 92 } | 306 } |
| 93 | 307 |
| 94 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | 308 void InJobLauncher::OnIOCompleted( |
| 95 // Make sure that the object is completely stopped. The same check exists | |
| 96 // in Stoppable::~Stoppable() but this one helps us to fail early and | |
| 97 // predictably. | |
| 98 CHECK_EQ(stoppable_state(), Stoppable::kStopped); | |
| 99 | |
| 100 monitor_->RemoveWtsConsoleObserver(this); | |
| 101 | |
| 102 CHECK(!attached_); | |
| 103 CHECK(!timer_.IsRunning()); | |
| 104 } | |
| 105 | |
| 106 void WtsSessionProcessLauncher::OnIOCompleted( | |
| 107 base::MessagePumpForIO::IOContext* context, | 309 base::MessagePumpForIO::IOContext* context, |
| 108 DWORD bytes_transferred, | 310 DWORD bytes_transferred, |
| 109 DWORD error) { | 311 DWORD error) { |
| 110 DCHECK(ipc_message_loop_->BelongsToCurrentThread()); | 312 DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| 111 | 313 |
| 112 // |bytes_transferred| is used in job object notifications to supply | 314 // |bytes_transferred| is used in job object notifications to supply |
| 113 // the message ID; |context| carries process ID. | 315 // the message ID; |context| carries process ID. |
| 114 main_message_loop_->PostTask(FROM_HERE, base::Bind( | 316 main_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 115 &WtsSessionProcessLauncher::OnJobNotification, | 317 &InJobLauncher::OnJobNotification, this, bytes_transferred, |
| 116 base::Unretained(this), bytes_transferred, | |
| 117 reinterpret_cast<DWORD>(context))); | 318 reinterpret_cast<DWORD>(context))); |
| 118 } | 319 } |
| 119 | 320 |
| 120 bool WtsSessionProcessLauncher::DoLaunchProcess( | 321 bool InJobLauncher::DoLaunchProcess( |
| 121 const std::string& channel_name, | 322 const std::string& channel_name, |
| 122 ScopedHandle* process_exit_event_out) { | 323 base::win::ScopedHandle* process_exit_event_out) { |
| 123 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 324 DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| 124 | 325 |
| 125 // The job object is not ready. Retry starting the host process later. | 326 // The job object is not ready. Retry starting the host process later. |
| 126 if (!job_.IsValid()) { | 327 if (!job_.IsValid()) { |
| 127 return false; | 328 return false; |
| 128 } | 329 } |
| 129 | 330 |
| 130 // Construct the host binary name. | 331 // Construct the helper binary binary name. |
| 131 FilePath dir_path; | 332 FilePath dir_path; |
| 132 if (!PathService::Get(base::DIR_EXE, &dir_path)) { | 333 if (!PathService::Get(base::DIR_EXE, &dir_path)) { |
| 133 LOG(ERROR) << "Failed to get the executable file name."; | 334 LOG(ERROR) << "Failed to get the executable file name."; |
| 134 return false; | 335 return false; |
| 135 } | 336 } |
| 136 FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); | |
| 137 FilePath service_binary = dir_path.Append(kMe2meServiceBinaryName); | 337 FilePath service_binary = dir_path.Append(kMe2meServiceBinaryName); |
| 138 | 338 |
| 139 // Create the host process command line passing the name of the IPC channel | 339 // Create the host process command line passing the name of the IPC channel |
| 140 // to use and copying known switches from the service's command line. | 340 // to use and copying known switches from the service's command line. |
| 141 CommandLine command_line(service_binary); | 341 CommandLine command_line(service_binary); |
| 142 command_line.AppendSwitchPath(kElevateSwitchName, host_binary); | 342 command_line.AppendSwitchPath(kElevateSwitchName, binary_); |
| 143 command_line.AppendSwitchNative(kDaemonIpcSwitchName, | 343 command_line.AppendSwitchNative(kDaemonIpcSwitchName, |
| 144 UTF8ToWide(channel_name)); | 344 UTF8ToWide(channel_name)); |
| 145 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), | 345 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), |
| 146 kCopiedSwitchNames, | 346 kCopiedSwitchNames, |
| 147 _countof(kCopiedSwitchNames)); | 347 _countof(kCopiedSwitchNames)); |
| 148 | 348 |
| 149 CHECK(ResetEvent(process_exit_event_)); | 349 CHECK(ResetEvent(process_exit_event_)); |
| 150 | 350 |
| 151 // Try to launch the process and attach an object watcher to the returned | 351 // Try to launch the process and attach an object watcher to the returned |
| 152 // handle so that we get notified when the process terminates. | 352 // handle so that we get notified when the process terminates. |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 183 0)) { | 383 0)) { |
| 184 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; | 384 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; |
| 185 DoKillProcess(CONTROL_C_EXIT); | 385 DoKillProcess(CONTROL_C_EXIT); |
| 186 return false; | 386 return false; |
| 187 } | 387 } |
| 188 | 388 |
| 189 *process_exit_event_out = process_exit_event.Pass(); | 389 *process_exit_event_out = process_exit_event.Pass(); |
| 190 return true; | 390 return true; |
| 191 } | 391 } |
| 192 | 392 |
| 193 void WtsSessionProcessLauncher::DoKillProcess(DWORD exit_code) { | 393 void InJobLauncher::DoKillProcess(DWORD exit_code) { |
| 194 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 394 DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| 195 | 395 |
| 196 if (job_.IsValid()) { | 396 if (job_.IsValid()) { |
| 197 TerminateJobObject(job_, exit_code); | 397 TerminateJobObject(job_, exit_code); |
| 198 } | 398 } |
| 199 } | 399 } |
| 200 | 400 |
| 201 void WtsSessionProcessLauncher::OnChannelConnected() { | 401 DWORD InJobLauncher::GetExitCode() { |
| 202 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 402 DWORD exit_code = CONTROL_C_EXIT; |
| 403 if (worker_process_.IsValid()) { | |
| 404 if (!::GetExitCodeProcess(worker_process_, &exit_code)) { | |
| 405 LOG_GETLASTERROR(INFO) | |
| 406 << "Failed to query the exit code of the worker process"; | |
| 407 exit_code = CONTROL_C_EXIT; | |
| 408 } | |
| 409 | |
| 410 worker_process_.Close(); | |
| 411 } | |
| 412 | |
| 413 return exit_code; | |
| 203 } | 414 } |
| 204 | 415 |
| 205 bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { | 416 bool InJobLauncher::SetSessionToken(HANDLE session_token) { |
| 206 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 417 session_token_.Close(); |
| 418 if (!DuplicateHandle(GetCurrentProcess(), | |
| 419 session_token, | |
| 420 GetCurrentProcess(), | |
| 421 session_token_.Receive(), | |
| 422 0, | |
| 423 FALSE, | |
| 424 DUPLICATE_SAME_ACCESS)) { | |
| 425 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; | |
| 426 return false; | |
| 427 } | |
| 207 | 428 |
| 208 return false; | 429 return true; |
| 209 } | 430 } |
| 210 | 431 |
| 211 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { | 432 void InJobLauncher::Stop() { |
| 212 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 433 // Drain the completion queue to make sure all job object notification have |
| 213 | 434 // been received. |
| 214 if (stoppable_state() != Stoppable::kRunning) { | 435 DrainJobNotificationsCompleted(); |
| 215 return; | |
| 216 } | |
| 217 | |
| 218 DCHECK(!attached_); | |
| 219 DCHECK(!timer_.IsRunning()); | |
| 220 | |
| 221 attached_ = true; | |
| 222 | |
| 223 // Create a session token for the launched process. | |
| 224 if (!CreateSessionToken(session_id, &session_token_)) | |
| 225 return; | |
| 226 | |
| 227 // Now try to launch the host. | |
| 228 LaunchProcess(); | |
| 229 } | 436 } |
| 230 | 437 |
| 231 void WtsSessionProcessLauncher::OnSessionDetached() { | 438 void InJobLauncher::DrainJobNotifications() { |
| 232 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 439 DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| 233 DCHECK(attached_); | |
| 234 | |
| 235 attached_ = false; | |
| 236 launch_backoff_ = base::TimeDelta(); | |
| 237 session_token_.Close(); | |
| 238 timer_.Stop(); | |
| 239 | |
| 240 if (launcher_.get() != NULL) { | |
| 241 launcher_->Stop(); | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 void WtsSessionProcessLauncher::DoStop() { | |
| 246 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 247 | |
| 248 if (attached_) { | |
| 249 OnSessionDetached(); | |
| 250 } | |
| 251 | |
| 252 job_.Close(); | |
| 253 | |
| 254 // Drain the completion queue to make sure all job object notification have | |
| 255 // been received. | |
| 256 if (job_state_ == kJobRunning) { | |
| 257 job_state_ = kJobStopping; | |
| 258 ipc_message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 259 &WtsSessionProcessLauncher::DrainJobNotifications, | |
| 260 base::Unretained(this))); | |
| 261 } | |
| 262 | |
| 263 // Don't complete shutdown if |launcher_| is not completely stopped. | |
| 264 if (launcher_.get() != NULL) { | |
| 265 return; | |
| 266 } | |
| 267 | |
| 268 // Don't complete shutdown if the completion queue hasn't been drained. | |
| 269 if (job_state_ != kJobUninitialized && job_state_ != kJobStopped) { | |
| 270 return; | |
| 271 } | |
| 272 | |
| 273 CompleteStopping(); | |
| 274 } | |
| 275 | |
| 276 void WtsSessionProcessLauncher::DrainJobNotifications() { | |
| 277 DCHECK(ipc_message_loop_->BelongsToCurrentThread()); | |
| 278 | 440 |
| 279 // DrainJobNotifications() is posted after the job object is destroyed, so | 441 // DrainJobNotifications() is posted after the job object is destroyed, so |
| 280 // by this time all notifications from the job object have been processed | 442 // by this time all notifications from the job object have been processed |
| 281 // already. Let the main thread know that the queue has been drained. | 443 // already. Let the main thread know that the queue has been drained. |
| 282 main_message_loop_->PostTask(FROM_HERE, base::Bind( | 444 main_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 283 &WtsSessionProcessLauncher::DrainJobNotificationsCompleted, | 445 &InJobLauncher::DrainJobNotificationsCompleted, this)); |
| 284 base::Unretained(this))); | |
| 285 } | 446 } |
| 286 | 447 |
| 287 void WtsSessionProcessLauncher::DrainJobNotificationsCompleted() { | 448 void InJobLauncher::DrainJobNotificationsCompleted() { |
| 288 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 449 DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| 289 DCHECK_EQ(job_state_, kJobStopping); | |
| 290 | 450 |
| 291 job_state_ = kJobStopped; | 451 if (job_.IsValid()) { |
| 292 Stop(); | 452 job_.Close(); |
| 453 | |
| 454 // Drain the completion queue to make sure all job object notification have | |
| 455 // been received. | |
| 456 ipc_task_runner_->PostTask(FROM_HERE, base::Bind( | |
| 457 &InJobLauncher::DrainJobNotifications, this)); | |
| 458 } | |
| 293 } | 459 } |
| 294 | 460 |
| 295 void WtsSessionProcessLauncher::InitializeJob() { | 461 void InJobLauncher::InitializeJob() { |
| 296 DCHECK(ipc_message_loop_->BelongsToCurrentThread()); | 462 DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| 297 | 463 |
| 298 ScopedHandle job; | 464 ScopedHandle job; |
| 299 job.Set(CreateJobObject(NULL, NULL)); | 465 job.Set(CreateJobObject(NULL, NULL)); |
| 300 if (!job.IsValid()) { | 466 if (!job.IsValid()) { |
| 301 LOG_GETLASTERROR(ERROR) << "Failed to create a job object"; | 467 LOG_GETLASTERROR(ERROR) << "Failed to create a job object"; |
| 302 return; | 468 return; |
| 303 } | 469 } |
| 304 | 470 |
| 305 // Limit the number of active processes in the job to two (the process | 471 // Limit the number of active processes in the job to two (the process |
| 306 // performing elevation and the host) and make sure that all processes will be | 472 // performing elevation and the host) and make sure that all processes will be |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 325 << "Failed to associate the job object with a completion port"; | 491 << "Failed to associate the job object with a completion port"; |
| 326 return; | 492 return; |
| 327 } | 493 } |
| 328 | 494 |
| 329 // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped | 495 // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped |
| 330 // pointer. | 496 // pointer. |
| 331 scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle()); | 497 scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle()); |
| 332 *job_wrapper = job.Pass(); | 498 *job_wrapper = job.Pass(); |
| 333 | 499 |
| 334 // Let the main thread know that initialization is complete. | 500 // Let the main thread know that initialization is complete. |
| 335 main_message_loop_->PostTask(FROM_HERE, base::Bind( | 501 main_task_runner_->PostTask(FROM_HERE, base::Bind( |
| 336 &WtsSessionProcessLauncher::InitializeJobCompleted, | 502 &InJobLauncher::InitializeJobCompleted, this, |
| 337 base::Unretained(this), base::Passed(&job_wrapper))); | 503 base::Passed(&job_wrapper))); |
| 338 } | 504 } |
| 339 | 505 |
| 340 void WtsSessionProcessLauncher::InitializeJobCompleted( | 506 void InJobLauncher::InitializeJobCompleted( |
| 341 scoped_ptr<ScopedHandle> job) { | 507 scoped_ptr<ScopedHandle> job) { |
| 342 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 508 DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| 343 DCHECK(!job_.IsValid()); | 509 DCHECK(!job_.IsValid()); |
| 344 DCHECK_EQ(job_state_, kJobUninitialized); | |
| 345 | 510 |
| 346 if (stoppable_state() == Stoppable::kRunning) { | 511 job_ = job->Pass(); |
| 347 job_ = job->Pass(); | |
| 348 job_state_ = kJobRunning; | |
| 349 } | |
| 350 } | 512 } |
| 351 | 513 |
| 352 void WtsSessionProcessLauncher::OnJobNotification(DWORD message, DWORD pid) { | 514 void InJobLauncher::OnJobNotification(DWORD message, DWORD pid) { |
| 353 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 515 DCHECK(main_task_runner_->BelongsToCurrentThread()); |
| 354 | 516 |
| 355 switch (message) { | 517 switch (message) { |
| 356 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: | 518 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: |
| 357 CHECK(SetEvent(process_exit_event_)); | 519 CHECK(SetEvent(process_exit_event_)); |
| 358 break; | 520 break; |
| 359 | 521 |
| 360 case JOB_OBJECT_MSG_NEW_PROCESS: | 522 case JOB_OBJECT_MSG_NEW_PROCESS: |
| 361 // We report the exit code of the worker process to be |CONTROL_C_EXIT| | 523 // We report the exit code of the worker process to be |CONTROL_C_EXIT| |
| 362 // if we cannot get the actual exit code. So here we can safely ignore | 524 // if we cannot get the actual exit code. So here we can safely ignore |
| 363 // the error returned by OpenProcess(). | 525 // the error returned by OpenProcess(). |
| 364 worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)); | 526 worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)); |
| 365 break; | 527 break; |
| 366 } | 528 } |
| 367 } | 529 } |
| 368 | 530 |
| 531 } // namespace | |
| 532 | |
| 533 WtsProcessLauncherImpl::~WtsProcessLauncherImpl() { | |
| 534 } | |
| 535 | |
| 536 WtsSessionProcessLauncher::WtsSessionProcessLauncher( | |
| 537 const base::Closure& stopped_callback, | |
| 538 WtsConsoleMonitor* monitor, | |
| 539 scoped_refptr<base::SingleThreadTaskRunner> main_message_loop, | |
| 540 scoped_refptr<base::SingleThreadTaskRunner> ipc_message_loop) | |
| 541 : Stoppable(main_message_loop, stopped_callback), | |
| 542 attached_(false), | |
| 543 main_message_loop_(main_message_loop), | |
| 544 ipc_message_loop_(ipc_message_loop), | |
| 545 monitor_(monitor) { | |
| 546 monitor_->AddWtsConsoleObserver(this); | |
| 547 | |
| 548 // Construct the host binary name. | |
| 549 FilePath dir_path; | |
| 550 if (!PathService::Get(base::DIR_EXE, &dir_path)) { | |
| 551 LOG(ERROR) << "Failed to get the executable file name."; | |
| 552 Stop(); | |
| 553 return; | |
| 554 } | |
| 555 FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); | |
| 556 | |
| 557 // The workaround we use to launch a process in a not yet logged in session | |
| 558 // (see CreateRemoteSessionProcess()) on Windows XP/2K3 does not let us to | |
| 559 // assign the started process to a job. However on Vista+ we have to start | |
| 560 // the host via a helper process. The helper process calls ShellExecute() that | |
| 561 // does not work across the session boundary but it required to launch | |
| 562 // a binary specifying uiAccess='true' in its manifest. | |
| 563 // | |
| 564 // Below we choose which implementation of |WorkerProcessLauncher::Delegate| | |
| 565 // to use. A single process is launched on XP (since uiAccess='true' does not | |
| 566 // have effect any way). Vista+ utilizes a helper process and assign it to | |
| 567 // a job object to control it. | |
| 568 if (base::win::GetVersion() == base::win::VERSION_XP) { | |
| 569 impl_ = new SingleProcessLauncher(host_binary, main_message_loop); | |
| 570 } else { | |
| 571 impl_ = new InJobLauncher(host_binary, main_message_loop, ipc_message_loop); | |
| 572 } | |
| 573 } | |
| 574 | |
| 575 WtsSessionProcessLauncher::~WtsSessionProcessLauncher() { | |
| 576 // Make sure that the object is completely stopped. The same check exists | |
| 577 // in Stoppable::~Stoppable() but this one helps us to fail early and | |
| 578 // predictably. | |
| 579 CHECK_EQ(stoppable_state(), Stoppable::kStopped); | |
| 580 | |
| 581 monitor_->RemoveWtsConsoleObserver(this); | |
| 582 | |
| 583 CHECK(!attached_); | |
| 584 CHECK(!timer_.IsRunning()); | |
| 585 } | |
| 586 | |
| 587 void WtsSessionProcessLauncher::OnChannelConnected() { | |
| 588 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 589 } | |
| 590 | |
| 591 bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) { | |
| 592 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 593 | |
| 594 return false; | |
| 595 } | |
| 596 | |
| 597 void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) { | |
| 598 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 599 | |
| 600 if (stoppable_state() != Stoppable::kRunning) { | |
| 601 return; | |
| 602 } | |
| 603 | |
| 604 DCHECK(!attached_); | |
| 605 DCHECK(!timer_.IsRunning()); | |
| 606 | |
| 607 attached_ = true; | |
| 608 | |
| 609 // Create a session token and launch the host. | |
| 610 base::win::ScopedHandle session_token; | |
| 611 if (CreateSessionToken(session_id, &session_token) && | |
| 612 impl_->SetSessionToken(session_token)) { | |
| 613 LaunchProcess(); | |
| 614 } | |
| 615 } | |
| 616 | |
| 617 void WtsSessionProcessLauncher::OnSessionDetached() { | |
| 618 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 619 DCHECK(attached_); | |
| 620 | |
| 621 attached_ = false; | |
| 622 launch_backoff_ = base::TimeDelta(); | |
| 623 timer_.Stop(); | |
| 624 | |
| 625 if (launcher_.get() != NULL) { | |
| 626 launcher_->Stop(); | |
| 627 } | |
| 628 } | |
| 629 | |
| 630 void WtsSessionProcessLauncher::DoStop() { | |
| 631 DCHECK(main_message_loop_->BelongsToCurrentThread()); | |
| 632 | |
| 633 if (attached_) { | |
| 634 OnSessionDetached(); | |
| 635 } | |
| 636 | |
| 637 // Don't complete shutdown if |launcher_| is not completely stopped. | |
| 638 if (launcher_.get() != NULL) { | |
| 639 return; | |
| 640 } | |
| 641 | |
| 642 // Tear down the core asynchromously. | |
| 643 if (impl_.get()) { | |
| 644 impl_->Stop(); | |
|
Wez
2012/09/14 23:36:18
nit: Stop() doesn't seem to actually stop anything
alexeypa (please no reviews)
2012/09/18 16:58:06
It does "stop" job object notification. I'd like t
| |
| 645 impl_ = NULL; | |
| 646 } | |
| 647 | |
| 648 CompleteStopping(); | |
| 649 } | |
| 650 | |
| 369 void WtsSessionProcessLauncher::LaunchProcess() { | 651 void WtsSessionProcessLauncher::LaunchProcess() { |
| 370 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 652 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 371 DCHECK(attached_); | 653 DCHECK(attached_); |
| 372 DCHECK(launcher_.get() == NULL); | 654 DCHECK(launcher_.get() == NULL); |
| 373 DCHECK(!timer_.IsRunning()); | 655 DCHECK(!timer_.IsRunning()); |
| 374 | 656 |
| 375 launch_time_ = base::Time::Now(); | 657 launch_time_ = base::Time::Now(); |
| 376 launcher_.reset(new WorkerProcessLauncher( | 658 launcher_.reset(new WorkerProcessLauncher( |
| 377 this, this, | 659 impl_.get(), this, |
| 378 base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped, | 660 base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped, |
| 379 base::Unretained(this)), | 661 base::Unretained(this)), |
| 380 main_message_loop_, | 662 main_message_loop_, |
| 381 ipc_message_loop_)); | 663 ipc_message_loop_)); |
| 382 launcher_->Start(kDaemonIpcSecurityDescriptor); | 664 launcher_->Start(kDaemonIpcSecurityDescriptor); |
| 383 } | 665 } |
| 384 | 666 |
| 385 void WtsSessionProcessLauncher::OnLauncherStopped() { | 667 void WtsSessionProcessLauncher::OnLauncherStopped() { |
| 386 DCHECK(main_message_loop_->BelongsToCurrentThread()); | 668 DCHECK(main_message_loop_->BelongsToCurrentThread()); |
| 387 | 669 |
| 388 DWORD exit_code = CONTROL_C_EXIT; | 670 // Retrieve the exit code of the worker process. |
| 389 if (worker_process_.IsValid()) { | 671 DWORD exit_code = impl_->GetExitCode(); |
| 390 if (!::GetExitCodeProcess(worker_process_, &exit_code)) { | |
| 391 LOG_GETLASTERROR(INFO) | |
| 392 << "Failed to query the exit code of the worker process"; | |
| 393 exit_code = CONTROL_C_EXIT; | |
| 394 } | |
| 395 | |
| 396 worker_process_.Close(); | |
| 397 } | |
| 398 | 672 |
| 399 launcher_.reset(NULL); | 673 launcher_.reset(NULL); |
| 400 | 674 |
| 401 // Do not relaunch the worker process if the caller has asked us to stop. | 675 // Do not relaunch the worker process if the caller has asked us to stop. |
| 402 if (stoppable_state() != Stoppable::kRunning) { | 676 if (stoppable_state() != Stoppable::kRunning) { |
| 403 Stop(); | 677 Stop(); |
| 404 return; | 678 return; |
| 405 } | 679 } |
| 406 | 680 |
| 407 // Stop trying to restart the worker process if its process exited due to | 681 // Stop trying to restart the worker process if its process exited due to |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 427 launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | 701 launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); |
| 428 } | 702 } |
| 429 | 703 |
| 430 // Try to launch the worker process. | 704 // Try to launch the worker process. |
| 431 timer_.Start(FROM_HERE, launch_backoff_, | 705 timer_.Start(FROM_HERE, launch_backoff_, |
| 432 this, &WtsSessionProcessLauncher::LaunchProcess); | 706 this, &WtsSessionProcessLauncher::LaunchProcess); |
| 433 } | 707 } |
| 434 } | 708 } |
| 435 | 709 |
| 436 } // namespace remoting | 710 } // namespace remoting |
| OLD | NEW |