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.
5 // This file implements the Windows service controlling Me2Me host processes
6 // running within user sessions.
8 #include "remoting/host/win/host_service.h"
14 #include "base/at_exit.h"
15 #include "base/base_paths.h"
16 #include "base/base_switches.h"
17 #include "base/bind.h"
18 #include "base/command_line.h"
19 #include "base/file_path.h"
20 #include "base/message_loop.h"
21 #include "base/run_loop.h"
22 #include "base/single_thread_task_runner.h"
23 #include "base/stringprintf.h"
24 #include "base/threading/thread.h"
25 #include "base/utf_string_conversions.h"
26 #include "base/win/wrapped_window_proc.h"
27 #include "remoting/base/auto_thread.h"
28 #include "remoting/base/breakpad.h"
29 #include "remoting/base/scoped_sc_handle_win.h"
30 #include "remoting/base/stoppable.h"
31 #include "remoting/host/branding.h"
32 #include "remoting/host/host_exit_codes.h"
33 #include "remoting/host/logging.h"
35 #if defined(REMOTING_MULTI_PROCESS)
36 #include "remoting/host/daemon_process.h"
37 #endif // defined(REMOTING_MULTI_PROCESS)
39 #include "remoting/host/usage_stats_consent.h"
40 #include "remoting/host/win/host_service_resource.h"
41 #include "remoting/host/win/wts_console_observer.h"
43 #if !defined(REMOTING_MULTI_PROCESS)
44 #include "remoting/host/win/wts_console_session_process_driver.h"
45 #endif // !defined(REMOTING_MULTI_PROCESS)
47 using base::StringPrintf
;
51 // Session id that does not represent any session.
52 const uint32 kInvalidSessionId
= 0xffffffffu
;
54 const char kIoThreadName
[] = "I/O thread";
56 // A window class for the session change notifications window.
57 const wchar_t kSessionNotificationWindowClass
[] =
58 L
"Chromoting_SessionNotificationWindow";
60 // Command line switches:
62 // "--console" runs the service interactively for debugging purposes.
63 const char kConsoleSwitchName
[] = "console";
65 // "--elevate=<binary>" requests <binary> to be launched elevated, presenting
66 // a UAC prompt if necessary.
67 const char kElevateSwitchName
[] = "elevate";
69 // "--help" or "--?" prints the usage message.
70 const char kHelpSwitchName
[] = "help";
71 const char kQuestionSwitchName
[] = "?";
73 const wchar_t kUsageMessage
[] =
75 L
"Usage: %ls [options]\n"
78 L
" --console - Run the service interactively for debugging purposes.\n"
79 L
" --elevate=<...> - Run <...> elevated.\n"
80 L
" --help, --? - Print this message.\n";
82 // The command line parameters that should be copied from the service's command
83 // line when launching an elevated child.
84 const char* kCopiedSwitchNames
[] = {
85 "host-config", "daemon-pipe", switches::kV
, switches::kVModule
};
87 void usage(const FilePath
& program_name
) {
88 LOG(INFO
) << StringPrintf(kUsageMessage
,
89 UTF16ToWide(program_name
.value()).c_str());
96 HostService::HostService() :
97 console_session_id_(kInvalidSessionId
),
98 run_routine_(&HostService::RunAsService
),
99 service_status_handle_(0),
100 stopped_event_(true, false) {
103 HostService::~HostService() {
106 void HostService::AddWtsConsoleObserver(WtsConsoleObserver
* observer
) {
107 DCHECK(main_task_runner_
->BelongsToCurrentThread());
109 console_observers_
.AddObserver(observer
);
110 if (console_session_id_
!= kInvalidSessionId
)
111 observer
->OnSessionAttached(console_session_id_
);
114 void HostService::RemoveWtsConsoleObserver(WtsConsoleObserver
* observer
) {
115 DCHECK(main_task_runner_
->BelongsToCurrentThread());
117 console_observers_
.RemoveObserver(observer
);
120 void HostService::OnChildStopped() {
121 DCHECK(main_task_runner_
->BelongsToCurrentThread());
126 void HostService::OnSessionChange() {
127 DCHECK(main_task_runner_
->BelongsToCurrentThread());
129 // WTSGetActiveConsoleSessionId is a very cheap API. It basically reads
130 // a single value from shared memory. Therefore it is better to check if
131 // the console session is still the same every time a session change
132 // notification event is posted. This also takes care of coalescing multiple
133 // events into one since we look at the latest state.
134 uint32 console_session_id
= WTSGetActiveConsoleSessionId();
135 if (console_session_id_
!= console_session_id
) {
136 if (console_session_id_
!= kInvalidSessionId
) {
137 FOR_EACH_OBSERVER(WtsConsoleObserver
,
139 OnSessionDetached());
142 console_session_id_
= console_session_id
;
144 if (console_session_id_
!= kInvalidSessionId
) {
145 FOR_EACH_OBSERVER(WtsConsoleObserver
,
147 OnSessionAttached(console_session_id_
));
152 BOOL WINAPI
HostService::ConsoleControlHandler(DWORD event
) {
153 HostService
* self
= HostService::GetInstance();
156 case CTRL_BREAK_EVENT
:
157 case CTRL_CLOSE_EVENT
:
158 case CTRL_LOGOFF_EVENT
:
159 case CTRL_SHUTDOWN_EVENT
:
160 self
->main_task_runner_
->PostTask(FROM_HERE
, base::Bind(
161 &Stoppable::Stop
, base::Unretained(self
->child_
.get())));
162 self
->stopped_event_
.Wait();
170 HostService
* HostService::GetInstance() {
171 return Singleton
<HostService
>::get();
174 bool HostService::InitWithCommandLine(const CommandLine
* command_line
) {
175 CommandLine::StringVector args
= command_line
->GetArgs();
177 // Check if launch with elevation was requested.
178 if (command_line
->HasSwitch(kElevateSwitchName
)) {
179 run_routine_
= &HostService::Elevate
;
184 LOG(ERROR
) << "No positional parameters expected.";
188 // Run interactively if needed.
189 if (run_routine_
== &HostService::RunAsService
&&
190 command_line
->HasSwitch(kConsoleSwitchName
)) {
191 run_routine_
= &HostService::RunInConsole
;
197 int HostService::Run() {
198 return (this->*run_routine_
)();
201 void HostService::CreateLauncher(
202 scoped_refptr
<AutoThreadTaskRunner
> task_runner
) {
203 // Launch the I/O thread.
204 scoped_refptr
<AutoThreadTaskRunner
> io_task_runner
=
205 AutoThread::CreateWithType(kIoThreadName
, task_runner
,
206 MessageLoop::TYPE_IO
);
207 if (!io_task_runner
) {
208 LOG(FATAL
) << "Failed to start the I/O thread";
212 #if defined(REMOTING_MULTI_PROCESS)
214 child_
= DaemonProcess::Create(
217 base::Bind(&HostService::OnChildStopped
,
218 base::Unretained(this))).PassAs
<Stoppable
>();
220 #else // !defined(REMOTING_MULTI_PROCESS)
222 // Create the console session process driver.
223 child_
.reset(new WtsConsoleSessionProcessDriver(
224 base::Bind(&HostService::OnChildStopped
, base::Unretained(this)),
229 #endif // !defined(REMOTING_MULTI_PROCESS)
232 int HostService::Elevate() {
233 // Get the name of the binary to launch.
235 CommandLine::ForCurrentProcess()->GetSwitchValuePath(kElevateSwitchName
);
237 // Create the child process command line by copying known switches from our
239 CommandLine
command_line(CommandLine::NO_PROGRAM
);
240 command_line
.CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
242 arraysize(kCopiedSwitchNames
));
243 CommandLine::StringType parameters
= command_line
.GetCommandLineString();
245 // Launch the child process requesting elevation.
246 SHELLEXECUTEINFO info
;
247 memset(&info
, 0, sizeof(info
));
248 info
.cbSize
= sizeof(info
);
249 info
.lpVerb
= L
"runas";
250 info
.lpFile
= binary
.value().c_str();
251 info
.lpParameters
= parameters
.c_str();
252 info
.nShow
= SW_SHOWNORMAL
;
254 if (!ShellExecuteEx(&info
)) {
255 return GetLastError();
258 return kSuccessExitCode
;
261 int HostService::RunAsService() {
262 SERVICE_TABLE_ENTRYW dispatch_table
[] = {
263 { const_cast<LPWSTR
>(kWindowsServiceName
), &HostService::ServiceMain
},
267 if (!StartServiceCtrlDispatcherW(dispatch_table
)) {
268 LOG_GETLASTERROR(ERROR
)
269 << "Failed to connect to the service control manager";
270 return kInitializationFailed
;
273 // Wait until the service thread completely exited to avoid concurrent
274 // teardown of objects registered with base::AtExitManager and object
275 // destoyed by the service thread.
276 stopped_event_
.Wait();
278 return kSuccessExitCode
;
281 void HostService::RunAsServiceImpl() {
282 MessageLoop
message_loop(MessageLoop::TYPE_DEFAULT
);
283 base::RunLoop run_loop
;
284 main_task_runner_
= message_loop
.message_loop_proxy();
286 // Register the service control handler.
287 service_status_handle_
= RegisterServiceCtrlHandlerExW(
288 kWindowsServiceName
, &HostService::ServiceControlHandler
, this);
289 if (service_status_handle_
== 0) {
290 LOG_GETLASTERROR(ERROR
)
291 << "Failed to register the service control handler";
295 // Report running status of the service.
296 SERVICE_STATUS service_status
;
297 ZeroMemory(&service_status
, sizeof(service_status
));
298 service_status
.dwServiceType
= SERVICE_WIN32_OWN_PROCESS
;
299 service_status
.dwCurrentState
= SERVICE_RUNNING
;
300 service_status
.dwControlsAccepted
= SERVICE_ACCEPT_SHUTDOWN
|
301 SERVICE_ACCEPT_STOP
|
302 SERVICE_ACCEPT_SESSIONCHANGE
;
303 service_status
.dwWin32ExitCode
= kSuccessExitCode
;
304 if (!SetServiceStatus(service_status_handle_
, &service_status
)) {
305 LOG_GETLASTERROR(ERROR
)
306 << "Failed to report service status to the service control manager";
310 // Peek up the current console session.
311 console_session_id_
= WTSGetActiveConsoleSessionId();
313 CreateLauncher(scoped_refptr
<AutoThreadTaskRunner
>(
314 new AutoThreadTaskRunner(main_task_runner_
,
315 run_loop
.QuitClosure())));
320 // Tell SCM that the service is stopped.
321 service_status
.dwCurrentState
= SERVICE_STOPPED
;
322 service_status
.dwControlsAccepted
= 0;
323 if (!SetServiceStatus(service_status_handle_
, &service_status
)) {
324 LOG_GETLASTERROR(ERROR
)
325 << "Failed to report service status to the service control manager";
330 int HostService::RunInConsole() {
331 MessageLoop
message_loop(MessageLoop::TYPE_UI
);
332 base::RunLoop run_loop
;
333 main_task_runner_
= message_loop
.message_loop_proxy();
335 int result
= kInitializationFailed
;
337 // Subscribe to Ctrl-C and other console events.
338 if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler
, TRUE
)) {
339 LOG_GETLASTERROR(ERROR
)
340 << "Failed to set console control handler";
344 // Create a window for receiving session change notifications.
346 WNDCLASSEX window_class
;
347 base::win::InitializeWindowClass(
348 kSessionNotificationWindowClass
,
349 &base::win::WrappedWindowProc
<SessionChangeNotificationProc
>,
350 0, 0, 0, NULL
, NULL
, NULL
, NULL
, NULL
,
352 HINSTANCE instance
= window_class
.hInstance
;
353 ATOM atom
= RegisterClassExW(&window_class
);
355 LOG_GETLASTERROR(ERROR
)
356 << "Failed to register the window class '"
357 << kSessionNotificationWindowClass
<< "'";
361 window
= CreateWindowW(MAKEINTATOM(atom
), 0, 0, 0, 0, 0, 0, HWND_MESSAGE
, 0,
363 if (window
== NULL
) {
364 LOG_GETLASTERROR(ERROR
)
365 << "Failed to creat the session notificationwindow";
369 // Subscribe to session change notifications.
370 if (WTSRegisterSessionNotification(window
,
371 NOTIFY_FOR_ALL_SESSIONS
) != FALSE
) {
372 // Peek up the current console session.
373 console_session_id_
= WTSGetActiveConsoleSessionId();
375 CreateLauncher(scoped_refptr
<AutoThreadTaskRunner
>(
376 new AutoThreadTaskRunner(main_task_runner_
,
377 run_loop
.QuitClosure())));
382 // Release the control handler.
383 stopped_event_
.Signal();
385 WTSUnRegisterSessionNotification(window
);
386 result
= kSuccessExitCode
;
390 if (window
!= NULL
) {
391 DestroyWindow(window
);
395 UnregisterClass(MAKEINTATOM(atom
), instance
);
398 // Unsubscribe from console events. Ignore the exit code. There is nothing
399 // we can do about it now and the program is about to exit anyway. Even if
400 // it crashes nothing is going to be broken because of it.
401 SetConsoleCtrlHandler(&HostService::ConsoleControlHandler
, FALSE
);
406 DWORD WINAPI
HostService::ServiceControlHandler(DWORD control
,
410 HostService
* self
= reinterpret_cast<HostService
*>(context
);
412 case SERVICE_CONTROL_INTERROGATE
:
415 case SERVICE_CONTROL_SHUTDOWN
:
416 case SERVICE_CONTROL_STOP
:
417 self
->main_task_runner_
->PostTask(FROM_HERE
, base::Bind(
418 &Stoppable::Stop
, base::Unretained(self
->child_
.get())));
419 self
->stopped_event_
.Wait();
422 case SERVICE_CONTROL_SESSIONCHANGE
:
423 self
->main_task_runner_
->PostTask(FROM_HERE
, base::Bind(
424 &HostService::OnSessionChange
, base::Unretained(self
)));
428 return ERROR_CALL_NOT_IMPLEMENTED
;
432 VOID WINAPI
HostService::ServiceMain(DWORD argc
, WCHAR
* argv
[]) {
433 HostService
* self
= HostService::GetInstance();
436 self
->RunAsServiceImpl();
438 // Release the control handler and notify the main thread that it can exit
440 self
->stopped_event_
.Signal();
443 LRESULT CALLBACK
HostService::SessionChangeNotificationProc(HWND hwnd
,
448 case WM_WTSSESSION_CHANGE
: {
449 HostService
* self
= HostService::GetInstance();
450 self
->OnSessionChange();
455 return DefWindowProc(hwnd
, message
, wparam
, lparam
);
459 } // namespace remoting
461 int CALLBACK
WinMain(HINSTANCE instance
,
462 HINSTANCE previous_instance
,
463 LPSTR raw_command_line
,
465 #ifdef OFFICIAL_BUILD
466 if (remoting::IsUsageStatsAllowed()) {
467 remoting::InitializeCrashReporting();
469 #endif // OFFICIAL_BUILD
471 // This object instance is required by Chrome code (for example,
472 // FilePath, LazyInstance, MessageLoop, Singleton, etc).
473 base::AtExitManager exit_manager
;
475 // CommandLine::Init() ignores the passed |argc| and |argv| on Windows getting
476 // the command line from GetCommandLineW(), so we can safely pass NULL here.
477 CommandLine::Init(0, NULL
);
479 remoting::InitHostLogging();
481 const CommandLine
* command_line
= CommandLine::ForCurrentProcess();
482 if (command_line
->HasSwitch(kHelpSwitchName
) ||
483 command_line
->HasSwitch(kQuestionSwitchName
)) {
484 usage(command_line
->GetProgram());
485 return remoting::kSuccessExitCode
;
488 remoting::HostService
* service
= remoting::HostService::GetInstance();
489 if (!service
->InitWithCommandLine(command_line
)) {
490 usage(command_line
->GetProgram());
491 return remoting::kUsageExitCode
;
494 return service
->Run();