Revert "Merged all Chromoting Host code into remoting_core.dll (Windows)."
[chromium-blink-merge.git] / remoting / host / win / host_service.cc
blob84c7f7cfa45b8a69740e241264ba651e6cb0aa4f
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.
8 #include "remoting/host/win/host_service.h"
10 #include <windows.h>
11 #include <shellapi.h>
12 #include <wtsapi32.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;
49 namespace {
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[] =
74 L"\n"
75 L"Usage: %ls [options]\n"
76 L"\n"
77 L"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());
92 } // namespace
94 namespace remoting {
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());
123 child_.reset(NULL);
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,
138 console_observers_,
139 OnSessionDetached());
142 console_session_id_ = console_session_id;
144 if (console_session_id_ != kInvalidSessionId) {
145 FOR_EACH_OBSERVER(WtsConsoleObserver,
146 console_observers_,
147 OnSessionAttached(console_session_id_));
152 BOOL WINAPI HostService::ConsoleControlHandler(DWORD event) {
153 HostService* self = HostService::GetInstance();
154 switch (event) {
155 case CTRL_C_EVENT:
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();
163 return TRUE;
165 default:
166 return FALSE;
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;
180 return true;
183 if (!args.empty()) {
184 LOG(ERROR) << "No positional parameters expected.";
185 return false;
188 // Run interactively if needed.
189 if (run_routine_ == &HostService::RunAsService &&
190 command_line->HasSwitch(kConsoleSwitchName)) {
191 run_routine_ = &HostService::RunInConsole;
194 return true;
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";
209 return;
212 #if defined(REMOTING_MULTI_PROCESS)
214 child_ = DaemonProcess::Create(
215 task_runner,
216 io_task_runner,
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)),
225 this,
226 task_runner,
227 io_task_runner));
229 #endif // !defined(REMOTING_MULTI_PROCESS)
232 int HostService::Elevate() {
233 // Get the name of the binary to launch.
234 FilePath binary =
235 CommandLine::ForCurrentProcess()->GetSwitchValuePath(kElevateSwitchName);
237 // Create the child process command line by copying known switches from our
238 // command line.
239 CommandLine command_line(CommandLine::NO_PROGRAM);
240 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
241 kCopiedSwitchNames,
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 },
264 { NULL, NULL }
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";
292 return;
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";
307 return;
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())));
317 // Run the service.
318 run_loop.Run();
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";
326 return;
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";
341 return result;
344 // Create a window for receiving session change notifications.
345 HWND window = NULL;
346 WNDCLASSEX window_class;
347 base::win::InitializeWindowClass(
348 kSessionNotificationWindowClass,
349 &base::win::WrappedWindowProc<SessionChangeNotificationProc>,
350 0, 0, 0, NULL, NULL, NULL, NULL, NULL,
351 &window_class);
352 HINSTANCE instance = window_class.hInstance;
353 ATOM atom = RegisterClassExW(&window_class);
354 if (atom == 0) {
355 LOG_GETLASTERROR(ERROR)
356 << "Failed to register the window class '"
357 << kSessionNotificationWindowClass << "'";
358 goto cleanup;
361 window = CreateWindowW(MAKEINTATOM(atom), 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0,
362 instance, 0);
363 if (window == NULL) {
364 LOG_GETLASTERROR(ERROR)
365 << "Failed to creat the session notificationwindow";
366 goto cleanup;
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())));
379 // Run the service.
380 run_loop.Run();
382 // Release the control handler.
383 stopped_event_.Signal();
385 WTSUnRegisterSessionNotification(window);
386 result = kSuccessExitCode;
389 cleanup:
390 if (window != NULL) {
391 DestroyWindow(window);
394 if (atom != 0) {
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);
403 return result;
406 DWORD WINAPI HostService::ServiceControlHandler(DWORD control,
407 DWORD event_type,
408 LPVOID event_data,
409 LPVOID context) {
410 HostService* self = reinterpret_cast<HostService*>(context);
411 switch (control) {
412 case SERVICE_CONTROL_INTERROGATE:
413 return NO_ERROR;
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();
420 return NO_ERROR;
422 case SERVICE_CONTROL_SESSIONCHANGE:
423 self->main_task_runner_->PostTask(FROM_HERE, base::Bind(
424 &HostService::OnSessionChange, base::Unretained(self)));
425 return NO_ERROR;
427 default:
428 return ERROR_CALL_NOT_IMPLEMENTED;
432 VOID WINAPI HostService::ServiceMain(DWORD argc, WCHAR* argv[]) {
433 HostService* self = HostService::GetInstance();
435 // Run the service.
436 self->RunAsServiceImpl();
438 // Release the control handler and notify the main thread that it can exit
439 // now.
440 self->stopped_event_.Signal();
443 LRESULT CALLBACK HostService::SessionChangeNotificationProc(HWND hwnd,
444 UINT message,
445 WPARAM wparam,
446 LPARAM lparam) {
447 switch (message) {
448 case WM_WTSSESSION_CHANGE: {
449 HostService* self = HostService::GetInstance();
450 self->OnSessionChange();
451 return 0;
454 default:
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,
464 int show_command) {
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();