Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(119)

Side by Side Diff: remoting/host/win/wts_session_process_delegate.cc

Issue 11040065: [Chromoting] Reimplemented the worker process launcher to take into account the encountered issues: (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebased Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4 //
5 // This file implements the Windows service controlling Me2Me host processes
6 // running within user sessions.
7
8 #include "remoting/host/win/wts_session_process_delegate.h"
9
10 #include <sddl.h>
11 #include <limits>
12
13 #include "base/base_switches.h"
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/command_line.h"
17 #include "base/file_path.h"
18 #include "base/file_util.h"
19 #include "base/logging.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "base/message_loop.h"
22 #include "base/path_service.h"
23 #include "base/single_thread_task_runner.h"
24 #include "base/time.h"
25 #include "base/timer.h"
26 #include "base/utf_string_conversions.h"
27 #include "base/win/scoped_handle.h"
28 #include "base/win/windows_version.h"
29 #include "ipc/ipc_channel.h"
30 #include "ipc/ipc_channel_proxy.h"
31 #include "ipc/ipc_message.h"
32 #include "remoting/host/host_exit_codes.h"
33 #include "remoting/host/win/launch_process_with_token.h"
34 #include "remoting/host/win/worker_process_launcher.h"
35 #include "remoting/host/win/wts_console_monitor.h"
36 #include "remoting/host/worker_process_ipc_delegate.h"
37
38 using base::TimeDelta;
39 using base::win::ScopedHandle;
40
41 const FilePath::CharType kDaemonBinaryName[] =
42 FILE_PATH_LITERAL("remoting_daemon.exe");
43
44 // The command line switch specifying the name of the daemon IPC endpoint.
45 const char kDaemonIpcSwitchName[] = "daemon-pipe";
46
47 const char kElevateSwitchName[] = "elevate";
48
49 // The command line parameters that should be copied from the service's command
50 // line to the host process.
51 const char* kCopiedSwitchNames[] = {
52 "host-config", switches::kV, switches::kVModule };
53
54 namespace remoting {
55
56 // A private class actually implementing the functionality provided by
57 // |WtsSessionProcessDelegate|. This class is ref-counted and implements
58 // asynchronous fire-and-forget shutdown.
59 class WtsSessionProcessDelegate::Core
60 : public base::RefCountedThreadSafe<WtsSessionProcessDelegate::Core>,
61 public base::MessagePumpForIO::IOHandler,
62 public WorkerProcessLauncher::Delegate {
63 public:
64 // The caller must ensure that |delegate| remains valid at least until
65 // Stop() method has been called.
66 Core(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
67 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
68 const FilePath& binary_path,
69 bool launch_elevated);
70
71 // base::MessagePumpForIO::IOHandler implementation.
72 virtual void OnIOCompleted(base::MessagePumpForIO::IOContext* context,
73 DWORD bytes_transferred,
74 DWORD error) OVERRIDE;
75
76 // WorkerProcessLauncher::Delegate implementation.
77 virtual DWORD GetExitCode() OVERRIDE;
78 virtual void KillProcess(DWORD exit_code) OVERRIDE;
79 virtual bool LaunchProcess(
80 const std::string& channel_name,
81 base::win::ScopedHandle* process_exit_event_out) OVERRIDE;
82
83 // Initializes the object returning true on success.
84 bool Initialize(uint32 session_id);
85
86 // Stops the object asynchronously.
87 void Stop();
88
89 private:
90 friend class base::RefCountedThreadSafe<Core>;
91 virtual ~Core();
92
93 // Drains the completion port queue to make sure that all job object
94 // notifications have been received.
95 void DrainJobNotifications();
96
97 // Notified that the completion port queue has been drained.
98 void DrainJobNotificationsCompleted();
99
100 // Creates and initializes the job object that will sandbox the launched child
101 // processes.
102 void InitializeJob();
103
104 // Notified that the job object initialization is complete.
105 void InitializeJobCompleted(scoped_ptr<base::win::ScopedHandle> job);
106
107 // Called to process incoming job object notifications.
108 void OnJobNotification(DWORD message, DWORD pid);
109
110 // The task runner all public methods of this class should be called on.
111 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
112
113 // The task runner serving job object notifications.
114 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
115
116 // Path to the worker process binary.
117 FilePath binary_path_;
118
119 // The job object used to control the lifetime of child processes.
120 base::win::ScopedHandle job_;
121
122 // True if the worker process should be launched elevated.
123 bool launch_elevated_;
124
125 // A handle that becomes signalled once all processes associated with the job
126 // have been terminated.
127 base::win::ScopedHandle process_exit_event_;
128
129 // The token to be used to launch a process in a different session.
130 base::win::ScopedHandle session_token_;
131
132 // True if Stop() has been called.
133 bool stopping_;
134
135 // The handle of the worker process, if launched.
136 base::win::ScopedHandle worker_process_;
137
138 DISALLOW_COPY_AND_ASSIGN(Core);
139 };
140
141 WtsSessionProcessDelegate::Core::Core(
142 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
143 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
144 const FilePath& binary_path,
145 bool launch_elevated)
146 : main_task_runner_(main_task_runner),
147 io_task_runner_(io_task_runner),
148 binary_path_(binary_path),
149 launch_elevated_(launch_elevated),
150 stopping_(false) {
151 DCHECK(main_task_runner_->BelongsToCurrentThread());
152 }
153
154 void WtsSessionProcessDelegate::Core::OnIOCompleted(
155 base::MessagePumpForIO::IOContext* context,
156 DWORD bytes_transferred,
157 DWORD error) {
158 DCHECK(io_task_runner_->BelongsToCurrentThread());
159
160 // |bytes_transferred| is used in job object notifications to supply
161 // the message ID; |context| carries process ID.
162 main_task_runner_->PostTask(FROM_HERE, base::Bind(
163 &Core::OnJobNotification, this, bytes_transferred,
164 reinterpret_cast<DWORD>(context)));
165 }
166
167 DWORD WtsSessionProcessDelegate::Core::GetExitCode() {
168 DCHECK(main_task_runner_->BelongsToCurrentThread());
169
170 DWORD exit_code = CONTROL_C_EXIT;
171 if (worker_process_.IsValid()) {
172 if (!::GetExitCodeProcess(worker_process_, &exit_code)) {
173 LOG_GETLASTERROR(INFO)
174 << "Failed to query the exit code of the worker process";
175 exit_code = CONTROL_C_EXIT;
176 }
177 }
178
179 return exit_code;
180 }
181
182 void WtsSessionProcessDelegate::Core::KillProcess(DWORD exit_code) {
183 DCHECK(main_task_runner_->BelongsToCurrentThread());
184
185 if (launch_elevated_) {
186 if (job_.IsValid()) {
187 TerminateJobObject(job_, exit_code);
188 }
189 } else {
190 if (worker_process_.IsValid()) {
191 TerminateProcess(worker_process_, exit_code);
192 }
193 }
194 }
195
196 bool WtsSessionProcessDelegate::Core::LaunchProcess(
197 const std::string& channel_name,
198 ScopedHandle* process_exit_event_out) {
199 DCHECK(main_task_runner_->BelongsToCurrentThread());
200
201 CommandLine command_line(CommandLine::NO_PROGRAM);
202 if (launch_elevated_) {
203 // The job object is not ready. Retry starting the host process later.
204 if (!job_.IsValid()) {
205 return false;
206 }
207
208 // Construct the helper binary name.
209 FilePath dir_path;
210 if (!PathService::Get(base::DIR_EXE, &dir_path)) {
211 LOG(ERROR) << "Failed to get the executable file name.";
212 return false;
213 }
214 FilePath daemon_binary = dir_path.Append(kDaemonBinaryName);
215
216 // Create the command line passing the name of the IPC channel to use and
217 // copying known switches from the caller's command line.
218 command_line.SetProgram(daemon_binary);
219 command_line.AppendSwitchPath(kElevateSwitchName, binary_path_);
220
221 CHECK(ResetEvent(process_exit_event_));
222 } else {
223 command_line.SetProgram(binary_path_);
224 }
225
226 // Create the command line passing the name of the IPC channel to use and
227 // copying known switches from the caller's command line.
228 command_line.AppendSwitchNative(kDaemonIpcSwitchName,
229 UTF8ToWide(channel_name));
230 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
231 kCopiedSwitchNames,
232 arraysize(kCopiedSwitchNames));
233
234 // Try to launch the process.
235 ScopedHandle worker_process;
236 ScopedHandle worker_thread;
237 if (!LaunchProcessWithToken(command_line.GetProgram(),
238 command_line.GetCommandLineString(),
239 session_token_,
240 CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB,
241 &worker_process,
242 &worker_thread)) {
243 return false;
244 }
245
246 HANDLE local_process_exit_event;
247 if (launch_elevated_) {
248 if (!AssignProcessToJobObject(job_, worker_process)) {
249 LOG_GETLASTERROR(ERROR)
250 << "Failed to assign the worker to the job object";
251 TerminateProcess(worker_process, CONTROL_C_EXIT);
252 return false;
253 }
254
255 local_process_exit_event = process_exit_event_;
256 } else {
257 worker_process_ = worker_process.Pass();
258 local_process_exit_event = worker_process_;
259 }
260
261 if (!ResumeThread(worker_thread)) {
262 LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread";
263 KillProcess(CONTROL_C_EXIT);
264 return false;
265 }
266
267 // Return a handle that the caller can wait on to get notified when
268 // the process terminates.
269 ScopedHandle process_exit_event;
270 if (!DuplicateHandle(GetCurrentProcess(),
271 local_process_exit_event,
272 GetCurrentProcess(),
273 process_exit_event.Receive(),
274 SYNCHRONIZE,
275 FALSE,
276 0)) {
277 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle";
278 KillProcess(CONTROL_C_EXIT);
279 return false;
280 }
281
282 *process_exit_event_out = process_exit_event.Pass();
283 return true;
284 }
285
286 bool WtsSessionProcessDelegate::Core::Initialize(uint32 session_id) {
287 if (base::win::GetVersion() == base::win::VERSION_XP)
288 launch_elevated_ = false;
289
290 if (launch_elevated_) {
291 process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL));
292 if (!process_exit_event_.IsValid()) {
293 LOG(ERROR) << "Failed to create a nameless event";
294 return false;
295 }
296
297 // To receive job object notifications the job object is registered with
298 // the completion port represented by |io_task_runner|. The registration has
299 // to be done on the I/O thread because
300 // MessageLoopForIO::RegisterJobObject() can only be called via
301 // MessageLoopForIO::current().
302 io_task_runner_->PostTask(FROM_HERE,
303 base::Bind(&Core::InitializeJob, this));
304 }
305
306 // Create a session token for the launched process.
307 return CreateSessionToken(session_id, &session_token_);
308 }
309
310 void WtsSessionProcessDelegate::Core::Stop() {
311 DCHECK(main_task_runner_->BelongsToCurrentThread());
312
313 if (!stopping_) {
314 stopping_ = true;
315
316 // Drain the completion queue to make sure all job object notifications have
317 // been received.
318 DrainJobNotificationsCompleted();
319 }
320 }
321
322 WtsSessionProcessDelegate::Core::~Core() {
323 }
324
325 void WtsSessionProcessDelegate::Core::DrainJobNotifications() {
326 DCHECK(io_task_runner_->BelongsToCurrentThread());
327
328 // DrainJobNotifications() is posted after the job object is destroyed, so
329 // by this time all notifications from the job object have been processed
330 // already. Let the main thread know that the queue has been drained.
331 main_task_runner_->PostTask(FROM_HERE, base::Bind(
332 &Core::DrainJobNotificationsCompleted, this));
333 }
334
335 void WtsSessionProcessDelegate::Core::DrainJobNotificationsCompleted() {
336 DCHECK(main_task_runner_->BelongsToCurrentThread());
337
338 if (job_.IsValid()) {
339 job_.Close();
340
341 // Drain the completion queue to make sure all job object notification have
342 // been received.
343 io_task_runner_->PostTask(FROM_HERE, base::Bind(
344 &Core::DrainJobNotifications, this));
345 }
346 }
347
348 void WtsSessionProcessDelegate::Core::InitializeJob() {
349 DCHECK(io_task_runner_->BelongsToCurrentThread());
350
351 ScopedHandle job;
352 job.Set(CreateJobObject(NULL, NULL));
353 if (!job.IsValid()) {
354 LOG_GETLASTERROR(ERROR) << "Failed to create a job object";
355 return;
356 }
357
358 // Limit the number of active processes in the job to two (the process
359 // performing elevation and the host) and make sure that all processes will be
360 // killed once the job object is destroyed.
361 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
362 memset(&info, 0, sizeof(info));
363 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS |
364 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
365 info.BasicLimitInformation.ActiveProcessLimit = 2;
366 if (!SetInformationJobObject(job,
367 JobObjectExtendedLimitInformation,
368 &info,
369 sizeof(info))) {
370 LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object";
371 return;
372 }
373
374 // Register to receive job notifications via the I/O thread's completion port.
375 if (!MessageLoopForIO::current()->RegisterJobObject(job, this)) {
376 LOG_GETLASTERROR(ERROR)
377 << "Failed to associate the job object with a completion port";
378 return;
379 }
380
381 // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped
382 // pointer.
383 scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle());
384 *job_wrapper = job.Pass();
385
386 // Let the main thread know that initialization is complete.
387 main_task_runner_->PostTask(FROM_HERE, base::Bind(
388 &Core::InitializeJobCompleted, this, base::Passed(&job_wrapper)));
389 }
390
391 void WtsSessionProcessDelegate::Core::InitializeJobCompleted(
392 scoped_ptr<ScopedHandle> job) {
393 DCHECK(main_task_runner_->BelongsToCurrentThread());
394 DCHECK(!job_.IsValid());
395
396 job_ = job->Pass();
397 }
398
399 void WtsSessionProcessDelegate::Core::OnJobNotification(DWORD message,
400 DWORD pid) {
401 DCHECK(main_task_runner_->BelongsToCurrentThread());
402
403 switch (message) {
404 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
405 CHECK(SetEvent(process_exit_event_));
406 break;
407
408 case JOB_OBJECT_MSG_NEW_PROCESS:
409 // We report the exit code of the worker process to be |CONTROL_C_EXIT|
410 // if we cannot get the actual exit code. So here we can safely ignore
411 // the error returned by OpenProcess().
412 worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid));
413 break;
414 }
415 }
416
417 WtsSessionProcessDelegate::WtsSessionProcessDelegate(
418 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
419 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
420 const FilePath& binary_path,
421 uint32 session_id,
422 bool launch_elevated) {
423 core_ = new Core(main_task_runner, io_task_runner, binary_path,
424 launch_elevated);
425 if (!core_->Initialize(session_id)) {
426 core_->Stop();
427 core_ = NULL;
428 }
429 }
430
431 WtsSessionProcessDelegate::~WtsSessionProcessDelegate() {
432 core_->Stop();
433 }
434
435 DWORD WtsSessionProcessDelegate::GetExitCode() {
436 if (!core_)
437 return CONTROL_C_EXIT;
438
439 return core_->GetExitCode();
440 }
441
442 void WtsSessionProcessDelegate::KillProcess(DWORD exit_code) {
443 if (core_) {
444 core_->KillProcess(exit_code);
445 }
446 }
447
448 bool WtsSessionProcessDelegate::LaunchProcess(
449 const std::string& channel_name,
450 base::win::ScopedHandle* process_exit_event_out) {
451 if (!core_)
452 return false;
453
454 return core_->LaunchProcess(channel_name, process_exit_event_out);
455 }
456
457 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/host/win/wts_session_process_delegate.h ('k') | remoting/host/win/wts_session_process_launcher.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698