OLD | NEW |
| (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/host_service_win.h" | |
9 | |
10 #include <windows.h> | |
11 #include <wtsapi32.h> | |
12 #include <stdio.h> | |
13 | |
14 #include "base/at_exit.h" | |
15 #include "base/base_paths.h" | |
16 #include "base/bind.h" | |
17 #include "base/command_line.h" | |
18 #include "base/file_util.h" | |
19 #include "base/logging.h" | |
20 #include "base/message_loop.h" | |
21 #include "base/path_service.h" | |
22 #include "base/stringprintf.h" | |
23 #include "base/threading/thread.h" | |
24 #include "base/utf_string_conversions.h" | |
25 #include "base/win/wrapped_window_proc.h" | |
26 #include "remoting/base/breakpad.h" | |
27 #include "remoting/base/scoped_sc_handle_win.h" | |
28 #include "remoting/host/branding.h" | |
29 #include "remoting/host/host_service_resource.h" | |
30 #include "remoting/host/usage_stats_consent.h" | |
31 #include "remoting/host/wts_console_observer_win.h" | |
32 #include "remoting/host/wts_session_process_launcher_win.h" | |
33 | |
34 using base::StringPrintf; | |
35 | |
36 namespace { | |
37 | |
38 const char kIoThreadName[] = "I/O thread"; | |
39 | |
40 // A window class for the session change notifications window. | |
41 const wchar_t kSessionNotificationWindowClass[] = | |
42 L"Chromoting_SessionNotificationWindow"; | |
43 | |
44 // Command line actions and switches: | |
45 // "run" sumply runs the service as usual. | |
46 const wchar_t kRunActionName[] = L"run"; | |
47 | |
48 // "--console" runs the service interactively for debugging purposes. | |
49 const char kConsoleSwitchName[] = "console"; | |
50 | |
51 // "--host-binary" specifies the host binary to run in console session. | |
52 const char kHostBinarySwitchName[] = "host-binary"; | |
53 | |
54 // "--help" or "--?" prints the usage message. | |
55 const char kHelpSwitchName[] = "help"; | |
56 const char kQuestionSwitchName[] = "?"; | |
57 | |
58 const char kUsageMessage[] = | |
59 "\n" | |
60 "Usage: %s [action] [options]\n" | |
61 "\n" | |
62 "Actions:\n" | |
63 " run - Run the service (default if no action was specified).\n" | |
64 "\n" | |
65 "Options:\n" | |
66 " --console - Run the service interactively for debugging purposes.\n" | |
67 " --host-binary - Specifies the host binary to run.\n" | |
68 " --help, --? - Print this message.\n"; | |
69 | |
70 // Exit codes: | |
71 const int kSuccessExitCode = 0; | |
72 const int kUsageExitCode = 1; | |
73 const int kErrorExitCode = 2; | |
74 | |
75 void usage(const char* program_name) { | |
76 fprintf(stderr, kUsageMessage, program_name); | |
77 } | |
78 | |
79 } // namespace | |
80 | |
81 namespace remoting { | |
82 | |
83 HostService::HostService() : | |
84 console_session_id_(kInvalidSessionId), | |
85 message_loop_(NULL), | |
86 run_routine_(&HostService::RunAsService), | |
87 service_name_(kWindowsServiceName), | |
88 service_status_handle_(0), | |
89 shutting_down_(false), | |
90 stopped_event_(true, false) { | |
91 } | |
92 | |
93 HostService::~HostService() { | |
94 } | |
95 | |
96 void HostService::AddWtsConsoleObserver(WtsConsoleObserver* observer) { | |
97 DCHECK(message_loop_->message_loop_proxy()->BelongsToCurrentThread()); | |
98 | |
99 console_observers_.AddObserver(observer); | |
100 } | |
101 | |
102 void HostService::RemoveWtsConsoleObserver(WtsConsoleObserver* observer) { | |
103 DCHECK(message_loop_->message_loop_proxy()->BelongsToCurrentThread()); | |
104 | |
105 console_observers_.RemoveObserver(observer); | |
106 | |
107 // Stop the service if there are no more observers. | |
108 if (!console_observers_.might_have_observers()) { | |
109 message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); | |
110 } | |
111 } | |
112 | |
113 void HostService::OnSessionChange() { | |
114 // WTSGetActiveConsoleSessionId is a very cheap API. It basically reads | |
115 // a single value from shared memory. Therefore it is better to check if | |
116 // the console session is still the same every time a session change | |
117 // notification event is posted. This also takes care of coalescing multiple | |
118 // events into one since we look at the latest state. | |
119 uint32 console_session_id = kInvalidSessionId; | |
120 if (!shutting_down_) { | |
121 console_session_id = WTSGetActiveConsoleSessionId(); | |
122 } | |
123 if (console_session_id_ != console_session_id) { | |
124 if (console_session_id_ != kInvalidSessionId) { | |
125 FOR_EACH_OBSERVER(WtsConsoleObserver, | |
126 console_observers_, | |
127 OnSessionDetached()); | |
128 } | |
129 | |
130 console_session_id_ = console_session_id; | |
131 | |
132 if (console_session_id_ != kInvalidSessionId) { | |
133 FOR_EACH_OBSERVER(WtsConsoleObserver, | |
134 console_observers_, | |
135 OnSessionAttached(console_session_id_)); | |
136 } | |
137 } | |
138 } | |
139 | |
140 BOOL WINAPI HostService::ConsoleControlHandler(DWORD event) { | |
141 HostService* self = HostService::GetInstance(); | |
142 switch (event) { | |
143 case CTRL_C_EVENT: | |
144 case CTRL_BREAK_EVENT: | |
145 case CTRL_CLOSE_EVENT: | |
146 case CTRL_LOGOFF_EVENT: | |
147 case CTRL_SHUTDOWN_EVENT: | |
148 self->message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); | |
149 self->stopped_event_.Wait(); | |
150 return TRUE; | |
151 | |
152 default: | |
153 return FALSE; | |
154 } | |
155 } | |
156 | |
157 HostService* HostService::GetInstance() { | |
158 return Singleton<HostService>::get(); | |
159 } | |
160 | |
161 bool HostService::InitWithCommandLine(const CommandLine* command_line) { | |
162 CommandLine::StringVector args = command_line->GetArgs(); | |
163 | |
164 // Choose the action to perform. | |
165 if (!args.empty()) { | |
166 if (args.size() > 1) { | |
167 LOG(ERROR) << "Invalid command line: more than one action requested."; | |
168 return false; | |
169 } | |
170 if (args[0] != kRunActionName) { | |
171 LOG(ERROR) << "Invalid command line: invalid action specified: " | |
172 << args[0]; | |
173 return false; | |
174 } | |
175 } | |
176 | |
177 if (command_line->HasSwitch(kHostBinarySwitchName)) { | |
178 host_binary_ = command_line->GetSwitchValuePath(kHostBinarySwitchName); | |
179 } else { | |
180 LOG(ERROR) << "Invalid command line: --" << kHostBinarySwitchName | |
181 << " is required."; | |
182 return false; | |
183 } | |
184 | |
185 // Run interactively if needed. | |
186 if (run_routine_ == &HostService::RunAsService && | |
187 command_line->HasSwitch(kConsoleSwitchName)) { | |
188 run_routine_ = &HostService::RunInConsole; | |
189 } | |
190 | |
191 return true; | |
192 } | |
193 | |
194 int HostService::Run() { | |
195 return (this->*run_routine_)(); | |
196 } | |
197 | |
198 void HostService::RunMessageLoop() { | |
199 // Launch the I/O thread. | |
200 base::Thread io_thread(kIoThreadName); | |
201 base::Thread::Options io_thread_options(MessageLoop::TYPE_IO, 0); | |
202 if (!io_thread.StartWithOptions(io_thread_options)) { | |
203 LOG(FATAL) << "Failed to start the I/O thread"; | |
204 shutting_down_ = true; | |
205 stopped_event_.Signal(); | |
206 return; | |
207 } | |
208 | |
209 WtsSessionProcessLauncher launcher(this, host_binary_, | |
210 message_loop_->message_loop_proxy(), | |
211 io_thread.message_loop_proxy()); | |
212 | |
213 // Run the service. | |
214 message_loop_->Run(); | |
215 | |
216 // Clean up the observers by emulating detaching from the console. | |
217 shutting_down_ = true; | |
218 OnSessionChange(); | |
219 | |
220 // Release the control handler. | |
221 stopped_event_.Signal(); | |
222 } | |
223 | |
224 int HostService::RunAsService() { | |
225 SERVICE_TABLE_ENTRYW dispatch_table[] = { | |
226 { const_cast<LPWSTR>(service_name_.c_str()), &HostService::ServiceMain }, | |
227 { NULL, NULL } | |
228 }; | |
229 | |
230 if (!StartServiceCtrlDispatcherW(dispatch_table)) { | |
231 LOG_GETLASTERROR(ERROR) | |
232 << "Failed to connect to the service control manager"; | |
233 return kErrorExitCode; | |
234 } | |
235 | |
236 return kSuccessExitCode; | |
237 } | |
238 | |
239 int HostService::RunInConsole() { | |
240 MessageLoop message_loop(MessageLoop::TYPE_UI); | |
241 | |
242 // Allow other threads to post to our message loop. | |
243 message_loop_ = &message_loop; | |
244 | |
245 int result = kErrorExitCode; | |
246 | |
247 // Subscribe to Ctrl-C and other console events. | |
248 if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, TRUE)) { | |
249 LOG_GETLASTERROR(ERROR) | |
250 << "Failed to set console control handler"; | |
251 return result; | |
252 } | |
253 | |
254 // Create a window for receiving session change notifications. | |
255 HWND window = NULL; | |
256 WNDCLASSEX window_class; | |
257 base::win::InitializeWindowClass( | |
258 kSessionNotificationWindowClass, | |
259 &base::win::WrappedWindowProc<SessionChangeNotificationProc>, | |
260 0, 0, 0, NULL, NULL, NULL, NULL, NULL, | |
261 &window_class); | |
262 HINSTANCE instance = window_class.hInstance; | |
263 ATOM atom = RegisterClassExW(&window_class); | |
264 if (atom == 0) { | |
265 LOG_GETLASTERROR(ERROR) | |
266 << "Failed to register the window class '" | |
267 << kSessionNotificationWindowClass << "'"; | |
268 goto cleanup; | |
269 } | |
270 | |
271 window = CreateWindowW(MAKEINTATOM(atom), 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, | |
272 instance, 0); | |
273 if (window == NULL) { | |
274 LOG_GETLASTERROR(ERROR) | |
275 << "Failed to creat the session notificationwindow"; | |
276 goto cleanup; | |
277 } | |
278 | |
279 // Post a dummy session change notification to peek up the current console | |
280 // session. | |
281 message_loop.PostTask(FROM_HERE, base::Bind( | |
282 &HostService::OnSessionChange, base::Unretained(this))); | |
283 | |
284 // Subscribe to session change notifications. | |
285 if (WTSRegisterSessionNotification(window, | |
286 NOTIFY_FOR_ALL_SESSIONS) != FALSE) { | |
287 // Run the service. | |
288 RunMessageLoop(); | |
289 | |
290 WTSUnRegisterSessionNotification(window); | |
291 result = kSuccessExitCode; | |
292 } | |
293 | |
294 cleanup: | |
295 if (window != NULL) { | |
296 DestroyWindow(window); | |
297 } | |
298 | |
299 if (atom != 0) { | |
300 UnregisterClass(MAKEINTATOM(atom), instance); | |
301 } | |
302 | |
303 // Unsubscribe from console events. Ignore the exit code. There is nothing | |
304 // we can do about it now and the program is about to exit anyway. Even if | |
305 // it crashes nothing is going to be broken because of it. | |
306 SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, FALSE); | |
307 | |
308 message_loop_ = NULL; | |
309 return result; | |
310 } | |
311 | |
312 DWORD WINAPI HostService::ServiceControlHandler(DWORD control, | |
313 DWORD event_type, | |
314 LPVOID event_data, | |
315 LPVOID context) { | |
316 HostService* self = reinterpret_cast<HostService*>(context); | |
317 switch (control) { | |
318 case SERVICE_CONTROL_INTERROGATE: | |
319 return NO_ERROR; | |
320 | |
321 case SERVICE_CONTROL_SHUTDOWN: | |
322 case SERVICE_CONTROL_STOP: | |
323 self->message_loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure()); | |
324 self->stopped_event_.Wait(); | |
325 return NO_ERROR; | |
326 | |
327 case SERVICE_CONTROL_SESSIONCHANGE: | |
328 self->message_loop_->PostTask(FROM_HERE, base::Bind( | |
329 &HostService::OnSessionChange, base::Unretained(self))); | |
330 return NO_ERROR; | |
331 | |
332 default: | |
333 return ERROR_CALL_NOT_IMPLEMENTED; | |
334 } | |
335 } | |
336 | |
337 VOID WINAPI HostService::ServiceMain(DWORD argc, WCHAR* argv[]) { | |
338 MessageLoop message_loop; | |
339 | |
340 // Allow other threads to post to our message loop. | |
341 HostService* self = HostService::GetInstance(); | |
342 self->message_loop_ = &message_loop; | |
343 | |
344 // Register the service control handler. | |
345 self->service_status_handle_ = | |
346 RegisterServiceCtrlHandlerExW(self->service_name_.c_str(), | |
347 &HostService::ServiceControlHandler, | |
348 self); | |
349 if (self->service_status_handle_ == 0) { | |
350 LOG_GETLASTERROR(ERROR) | |
351 << "Failed to register the service control handler"; | |
352 return; | |
353 } | |
354 | |
355 // Report running status of the service. | |
356 SERVICE_STATUS service_status; | |
357 ZeroMemory(&service_status, sizeof(service_status)); | |
358 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; | |
359 service_status.dwCurrentState = SERVICE_RUNNING; | |
360 service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | | |
361 SERVICE_ACCEPT_STOP | | |
362 SERVICE_ACCEPT_SESSIONCHANGE; | |
363 service_status.dwWin32ExitCode = kSuccessExitCode; | |
364 | |
365 if (!SetServiceStatus(self->service_status_handle_, &service_status)) { | |
366 LOG_GETLASTERROR(ERROR) | |
367 << "Failed to report service status to the service control manager"; | |
368 return; | |
369 } | |
370 | |
371 // Post a dummy session change notification to peek up the current console | |
372 // session. | |
373 message_loop.PostTask(FROM_HERE, base::Bind( | |
374 &HostService::OnSessionChange, base::Unretained(self))); | |
375 | |
376 // Run the service. | |
377 self->RunMessageLoop(); | |
378 | |
379 // Tell SCM that the service is stopped. | |
380 service_status.dwCurrentState = SERVICE_STOPPED; | |
381 service_status.dwControlsAccepted = 0; | |
382 | |
383 if (!SetServiceStatus(self->service_status_handle_, &service_status)) { | |
384 LOG_GETLASTERROR(ERROR) | |
385 << "Failed to report service status to the service control manager"; | |
386 return; | |
387 } | |
388 | |
389 self->message_loop_ = NULL; | |
390 } | |
391 | |
392 LRESULT CALLBACK HostService::SessionChangeNotificationProc(HWND hwnd, | |
393 UINT message, | |
394 WPARAM wparam, | |
395 LPARAM lparam) { | |
396 switch (message) { | |
397 case WM_WTSSESSION_CHANGE: { | |
398 HostService* self = HostService::GetInstance(); | |
399 self->OnSessionChange(); | |
400 return 0; | |
401 } | |
402 | |
403 default: | |
404 return DefWindowProc(hwnd, message, wparam, lparam); | |
405 } | |
406 } | |
407 | |
408 } // namespace remoting | |
409 | |
410 int main(int argc, char** argv) { | |
411 #ifdef OFFICIAL_BUILD | |
412 if (remoting::IsUsageStatsAllowed()) { | |
413 remoting::InitializeCrashReporting(); | |
414 } | |
415 #endif // OFFICIAL_BUILD | |
416 | |
417 CommandLine::Init(argc, argv); | |
418 | |
419 // This object instance is required by Chrome code (for example, | |
420 // FilePath, LazyInstance, MessageLoop). | |
421 base::AtExitManager exit_manager; | |
422 | |
423 // Write logs to the application profile directory. | |
424 FilePath debug_log = remoting::GetConfigDir(). | |
425 Append(FILE_PATH_LITERAL("debug.log")); | |
426 InitLogging(debug_log.value().c_str(), | |
427 logging::LOG_ONLY_TO_FILE, | |
428 logging::DONT_LOCK_LOG_FILE, | |
429 logging::APPEND_TO_OLD_LOG_FILE, | |
430 logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); | |
431 | |
432 const CommandLine* command_line = CommandLine::ForCurrentProcess(); | |
433 | |
434 if (command_line->HasSwitch(kHelpSwitchName) || | |
435 command_line->HasSwitch(kQuestionSwitchName)) { | |
436 usage(argv[0]); | |
437 return kSuccessExitCode; | |
438 } | |
439 | |
440 remoting::HostService* service = remoting::HostService::GetInstance(); | |
441 if (!service->InitWithCommandLine(command_line)) { | |
442 usage(argv[0]); | |
443 return kUsageExitCode; | |
444 } | |
445 | |
446 return service->Run(); | |
447 } | |
OLD | NEW |