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

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: CR feedback. 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/single_thread_task_runner.h"
Wez 2012/10/09 03:40:39 nit: Re-order this and the next include.
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
22 #include "base/path_service.h"
23 #include "base/time.h"
24 #include "base/timer.h"
25 #include "base/utf_string_conversions.h"
26 #include "base/win/scoped_handle.h"
27 #include "base/win/windows_version.h"
28 #include "ipc/ipc_channel.h"
29 #include "ipc/ipc_channel_proxy.h"
30 #include "ipc/ipc_message.h"
31 #include "remoting/host/host_exit_codes.h"
32 #include "remoting/host/win/launch_process_with_token.h"
33 #include "remoting/host/win/worker_process_launcher.h"
34 #include "remoting/host/win/wts_console_monitor.h"
35 #include "remoting/host/worker_process_ipc_delegate.h"
36
37 using base::win::ScopedHandle;
38 using base::TimeDelta;
Wez 2012/10/09 03:40:39 nit: Swap the order of these so they're alphabetic
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
39
40 namespace {
41
42 const FilePath::CharType kDaemonBinaryName[] =
43 FILE_PATH_LITERAL("remoting_daemon.exe");
44
45 // The command line switch specifying the name of the daemon IPC endpoint.
46 const char kDaemonIpcSwitchName[] = "daemon-pipe";
47
48 const char kElevateSwitchName[] = "elevate";
49
50 // The command line parameters that should be copied from the service's command
51 // line to the host process.
52 const char* kCopiedSwitchNames[] = {
53 "host-config", switches::kV, switches::kVModule };
54
55 } // namespace
Wez 2012/10/09 03:40:39 nit: IIRC, const implies static, so you don't actu
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
56
57 namespace remoting {
58
59 // A private class actually implementing the functionality provided by
60 // |WtsSessionProcessDelegate|. This class is ref-counted and implements
61 // asynchronous fire-and-forget shutdown.
62 class WtsSessionProcessDelegateImpl
63 : public base::RefCountedThreadSafe<WtsSessionProcessDelegateImpl>,
64 public base::MessagePumpForIO::IOHandler,
65 public WorkerProcessLauncher::Delegate {
66 public:
67 // The caller must ensure that |delegate| remains valid at least until
68 // Stop() method has been called.
69 WtsSessionProcessDelegateImpl(
70 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
71 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
72 const FilePath& binary_path,
73 uint32 session_id,
74 bool launch_elevated);
75
76 // base::MessagePumpForIO::IOHandler implementation.
77 virtual void OnIOCompleted(base::MessagePumpForIO::IOContext* context,
78 DWORD bytes_transferred,
79 DWORD error) OVERRIDE;
80
81 // WorkerProcessLauncher::Delegate implementation.
82 virtual DWORD GetExitCode() OVERRIDE;
83 virtual void KillProcess(DWORD exit_code) OVERRIDE;
84 virtual bool LaunchProcess(
85 const std::string& channel_name,
86 base::win::ScopedHandle* process_exit_event_out) OVERRIDE;
87
88 // Stops the object asynchronously.
89 void Stop();
90
91 private:
92 friend class base::RefCountedThreadSafe<WtsSessionProcessDelegateImpl>;
93 virtual ~WtsSessionProcessDelegateImpl();
94
95 // Drains the completion port queue to make sure that all job object
96 // notifications have been received.
97 void DrainJobNotifications();
98
99 // Notified that the completion port queue has been drained.
100 void DrainJobNotificationsCompleted();
101
102 // Creates and initializes the job object that will sandbox the launched child
103 // processes.
104 void InitializeJob();
105
106 // Notified that the job object initialization is complete.
107 void InitializeJobCompleted(scoped_ptr<base::win::ScopedHandle> job);
108
109 // Called to process incoming job object notifications.
110 void OnJobNotification(DWORD message, DWORD pid);
111
112 // The task runner all public methods of this class should be called on.
113 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
114
115 // The task runner serving job object notifications.
116 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
117
118 // Path to the worker process binary.
119 FilePath binary_path_;
120
121 // The job object used to control the lifetime of child processes.
122 base::win::ScopedHandle job_;
123
124 // True if the worker process should be launched elevated.
125 bool launch_elevated_;
126
127 // A waiting handle that becomes signalled once all process associated with
Wez 2012/10/09 03:40:39 typo: processes
Wez 2012/10/09 03:40:39 nit: Drop "A waiting"
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
128 // the job have been terminated.
129 base::win::ScopedHandle process_exit_event_;
130
131 // The token to be used to launch a process in a different session.
132 base::win::ScopedHandle session_token_;
133
134 // True if Stop() has been called.
135 bool stopping_;
136
137 // The handle of the worker process, if launched.
138 base::win::ScopedHandle worker_process_;
139
140 DISALLOW_COPY_AND_ASSIGN(WtsSessionProcessDelegateImpl);
141 };
142
143 WtsSessionProcessDelegateImpl::WtsSessionProcessDelegateImpl(
144 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
145 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
146 const FilePath& binary_path,
147 uint32 session_id,
148 bool launch_elevated)
149 : main_task_runner_(main_task_runner),
150 io_task_runner_(io_task_runner),
151 binary_path_(binary_path),
152 launch_elevated_(launch_elevated),
153 stopping_(false) {
154 DCHECK(main_task_runner_->BelongsToCurrentThread());
155
156 if (base::win::GetVersion() == base::win::VERSION_XP)
157 launch_elevated_ = false;
158
159 if (launch_elevated_) {
160 process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL));
161 CHECK(process_exit_event_.IsValid());
162
163 // To receive job object notifications the job object is registered with
164 // the completion port represented by |io_task_runner|. The registration has
165 // to be done on the I/O thread because
166 // MessageLoopForIO::RegisterJobObject() can only be called via
167 // MessageLoopForIO::current().
168 io_task_runner_->PostTask(
169 FROM_HERE,
170 base::Bind(&WtsSessionProcessDelegateImpl::InitializeJob, this));
171 }
172
173 // Create a session token for the launched process.
174 CHECK(CreateSessionToken(session_id, &session_token_));
175 }
176
177 void WtsSessionProcessDelegateImpl::OnIOCompleted(
178 base::MessagePumpForIO::IOContext* context,
179 DWORD bytes_transferred,
180 DWORD error) {
181 DCHECK(io_task_runner_->BelongsToCurrentThread());
182
183 // |bytes_transferred| is used in job object notifications to supply
184 // the message ID; |context| carries process ID.
185 main_task_runner_->PostTask(FROM_HERE, base::Bind(
186 &WtsSessionProcessDelegateImpl::OnJobNotification, this,
187 bytes_transferred, reinterpret_cast<DWORD>(context)));
188 }
189
190 DWORD WtsSessionProcessDelegateImpl::GetExitCode() {
191 DCHECK(main_task_runner_->BelongsToCurrentThread());
192
193 DWORD exit_code = CONTROL_C_EXIT;
194 if (worker_process_.IsValid()) {
195 if (!::GetExitCodeProcess(worker_process_, &exit_code)) {
196 LOG_GETLASTERROR(INFO)
197 << "Failed to query the exit code of the worker process";
198 exit_code = CONTROL_C_EXIT;
199 }
200 }
201
202 return exit_code;
203 }
204
205 void WtsSessionProcessDelegateImpl::KillProcess(DWORD exit_code) {
206 DCHECK(main_task_runner_->BelongsToCurrentThread());
207
208 if (launch_elevated_) {
209 if (job_.IsValid()) {
210 TerminateJobObject(job_, exit_code);
211 }
212 } else {
213 if (worker_process_.IsValid()) {
214 TerminateProcess(worker_process_, exit_code);
215 }
216 }
217 }
218
219 bool WtsSessionProcessDelegateImpl::LaunchProcess(
220 const std::string& channel_name,
221 ScopedHandle* process_exit_event_out) {
222 DCHECK(main_task_runner_->BelongsToCurrentThread());
223
224 CommandLine command_line(CommandLine::NO_PROGRAM);
225 if (launch_elevated_) {
226 // The job object is not ready. Retry starting the host process later.
227 if (!job_.IsValid()) {
228 return false;
229 }
230
231 // Construct the helper binary name.
232 FilePath dir_path;
233 if (!PathService::Get(base::DIR_EXE, &dir_path)) {
234 LOG(ERROR) << "Failed to get the executable file name.";
235 return false;
236 }
237 FilePath daemon_binary = dir_path.Append(kDaemonBinaryName);
238
239 // Create the command line passing the name of the IPC channel to use and
240 // copying known switches from the caller's command line.
241 command_line.SetProgram(daemon_binary);
242 command_line.AppendSwitchPath(kElevateSwitchName, binary_path_);
243
244 CHECK(ResetEvent(process_exit_event_));
245 } else {
246 command_line.SetProgram(binary_path_);
247 }
248
249 // Create the command line passing the name of the IPC channel to use and
250 // copying known switches from the caller's command line.
251 command_line.AppendSwitchNative(kDaemonIpcSwitchName,
252 UTF8ToWide(channel_name));
253 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
254 kCopiedSwitchNames,
255 arraysize(kCopiedSwitchNames));
256
257 // Try to launch the process and attach an object watcher to the returned
258 // handle so that we get notified when the process terminates.
Wez 2012/10/09 03:40:39 nit: You're not watching the handle, you're duping
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
259 ScopedHandle worker_process;
260 ScopedHandle worker_thread;
261 if (!LaunchProcessWithToken(command_line.GetProgram(),
262 command_line.GetCommandLineString(),
263 session_token_,
264 CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB,
265 &worker_process,
266 &worker_thread)) {
267 return false;
268 }
269
270 HANDLE local_process_exit_event;
271 if (launch_elevated_) {
272 if (!AssignProcessToJobObject(job_, worker_process)) {
273 LOG_GETLASTERROR(ERROR)
274 << "Failed to assign the worker to the job object";
275 TerminateProcess(worker_process, CONTROL_C_EXIT);
276 return false;
Wez 2012/10/09 03:40:39 Do you want to exit here, before you've set |proce
alexeypa (please no reviews) 2012/10/09 19:42:04 It will not. It checks the return value and handle
277 }
278
279 local_process_exit_event = process_exit_event_;
280 } else {
281 worker_process_ = worker_process.Pass();
282 local_process_exit_event = worker_process_;
283 }
284
285 if (!ResumeThread(worker_thread)) {
286 LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread";
287 KillProcess(CONTROL_C_EXIT);
288 return false;
289 }
290
291 ScopedHandle process_exit_event;
292 if (!DuplicateHandle(GetCurrentProcess(),
293 local_process_exit_event,
294 GetCurrentProcess(),
295 process_exit_event.Receive(),
296 SYNCHRONIZE,
297 FALSE,
298 0)) {
299 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle";
300 KillProcess(CONTROL_C_EXIT);
301 return false;
302 }
303
304 *process_exit_event_out = process_exit_event.Pass();
305 return true;
306 }
307
308 void WtsSessionProcessDelegateImpl::Stop() {
309 DCHECK(main_task_runner_->BelongsToCurrentThread());
310
311 if (!stopping_) {
312 stopping_ = true;
313
314 // Drain the completion queue to make sure all job object notification have
Wez 2012/10/09 03:40:39 typo: notifications
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
315 // been received.
316 DrainJobNotificationsCompleted();
Wez 2012/10/09 03:40:39 It's weird to call this DrainJobNotificationsCompl
alexeypa (please no reviews) 2012/10/09 19:42:04 I cannot avoid checking for hob_ being valid in Dr
317 }
318 }
319
320 WtsSessionProcessDelegateImpl::~WtsSessionProcessDelegateImpl() {
321 }
322
323 void WtsSessionProcessDelegateImpl::DrainJobNotifications() {
324 DCHECK(io_task_runner_->BelongsToCurrentThread());
325
326 // DrainJobNotifications() is posted after the job object is destroyed, so
327 // by this time all notifications from the job object have been processed
328 // already. Let the main thread know that the queue has been drained.
329 main_task_runner_->PostTask(FROM_HERE, base::Bind(
330 &WtsSessionProcessDelegateImpl::DrainJobNotificationsCompleted, this));
331 }
332
333 void WtsSessionProcessDelegateImpl::DrainJobNotificationsCompleted() {
Wez 2012/10/09 03:40:39 You're not doing anything in this method in the ca
alexeypa (please no reviews) 2012/10/09 19:42:04 Right now it is derived from Stoppable because the
334 DCHECK(main_task_runner_->BelongsToCurrentThread());
335
336 if (job_.IsValid()) {
337 job_.Close();
338
339 // Drain the completion queue to make sure all job object notification have
340 // been received.
341 io_task_runner_->PostTask(FROM_HERE, base::Bind(
342 &WtsSessionProcessDelegateImpl::DrainJobNotifications, this));
343 }
344 }
345
346 void WtsSessionProcessDelegateImpl::InitializeJob() {
347 DCHECK(io_task_runner_->BelongsToCurrentThread());
348
349 ScopedHandle job;
350 job.Set(CreateJobObject(NULL, NULL));
351 if (!job.IsValid()) {
352 LOG_GETLASTERROR(ERROR) << "Failed to create a job object";
Wez 2012/10/09 03:40:39 nit: Elsewhere you're CHECK()ing that APIs succeed
alexeypa (please no reviews) 2012/10/09 19:42:04 I removed most of CHECKs. The one checking the res
353 return;
354 }
355
356 // Limit the number of active processes in the job to two (the process
357 // performing elevation and the host) and make sure that all processes will be
Wez 2012/10/09 03:40:39 nit: Is there a risk that the APIs we use for elev
alexeypa (please no reviews) 2012/10/09 19:42:04 I prefer to be very strict in this case because we
358 // killed once the job object is destroyed.
359 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
360 memset(&info, 0, sizeof(info));
361 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS |
362 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
363 info.BasicLimitInformation.ActiveProcessLimit = 2;
364 if (!SetInformationJobObject(job,
365 JobObjectExtendedLimitInformation,
366 &info,
367 sizeof(info))) {
368 LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object";
369 return;
370 }
371
372 // Register the job object with the completion port in the I/O thread to
Wez 2012/10/09 03:40:39 nit: "Register to receive job notifications via th
alexeypa (please no reviews) 2012/10/09 19:42:04 Done.
373 // receive job notifications.
374 if (!MessageLoopForIO::current()->RegisterJobObject(job, this)) {
375 LOG_GETLASTERROR(ERROR)
376 << "Failed to associate the job object with a completion port";
377 return;
378 }
379
380 // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped
381 // pointer.
382 scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle());
383 *job_wrapper = job.Pass();
384
385 // Let the main thread know that initialization is complete.
386 main_task_runner_->PostTask(FROM_HERE, base::Bind(
387 &WtsSessionProcessDelegateImpl::InitializeJobCompleted, this,
388 base::Passed(&job_wrapper)));
389 }
390
391 void WtsSessionProcessDelegateImpl::InitializeJobCompleted(
392 scoped_ptr<ScopedHandle> job) {
393 DCHECK(main_task_runner_->BelongsToCurrentThread());
394 DCHECK(!job_.IsValid());
395
396 job_ = job->Pass();
Wez 2012/10/09 03:40:39 Isn't there a race condition here between the job
alexeypa (please no reviews) 2012/10/09 19:42:04 This race is addressed by the check in DrainJobNot
397 }
398
399 void WtsSessionProcessDelegateImpl::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 impl_ = new WtsSessionProcessDelegateImpl(main_task_runner, io_task_runner,
424 binary_path, session_id,
425 launch_elevated);
426 }
427
428 WtsSessionProcessDelegate::~WtsSessionProcessDelegate() {
429 impl_->Stop();
430 }
431
432 DWORD WtsSessionProcessDelegate::GetExitCode() {
433 return impl_->GetExitCode();
434 }
435
436 void WtsSessionProcessDelegate::KillProcess(DWORD exit_code) {
437 impl_->KillProcess(exit_code);
438 }
439
440 bool WtsSessionProcessDelegate::LaunchProcess(
441 const std::string& channel_name,
442 base::win::ScopedHandle* process_exit_event_out) {
443 return impl_->LaunchProcess(channel_name, process_exit_event_out);
444 }
445
446 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698