OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "remoting/host/plugin/daemon_controller.h" | 5 #include "remoting/host/plugin/daemon_controller.h" |
6 | 6 |
| 7 #include <objbase.h> |
| 8 |
7 #include "base/basictypes.h" | 9 #include "base/basictypes.h" |
| 10 #include "base/bind.h" |
8 #include "base/compiler_specific.h" | 11 #include "base/compiler_specific.h" |
| 12 #include "base/file_path.h" |
| 13 #include "base/file_util.h" |
| 14 #include "base/json/json_reader.h" |
| 15 #include "base/json/json_writer.h" |
9 #include "base/logging.h" | 16 #include "base/logging.h" |
| 17 #include "base/synchronization/lock.h" |
| 18 #include "base/threading/thread.h" |
| 19 #include "base/utf_string_conversions.h" |
10 #include "base/values.h" | 20 #include "base/values.h" |
| 21 #include "remoting/base/scoped_sc_handle_win.h" |
| 22 #include "remoting/host/branding.h" |
| 23 |
| 24 // MIDL-generated declarations and definitions. |
| 25 #include "elevated_controller.h" |
| 26 #include "elevated_controller_i.c" |
11 | 27 |
12 namespace remoting { | 28 namespace remoting { |
13 | 29 |
14 namespace { | 30 namespace { |
15 | 31 |
| 32 // The COM elevation moniker for the elevated controller. |
| 33 const char kElevationMoniker[] = "Elevation:Administrator!new:" |
| 34 "{430a9403-8176-4733-afdc-0b325a8fda84}"; |
| 35 |
| 36 // Name of the Daemon Controller's worker thread. |
| 37 const char kDaemonControllerThreadName[] = "Daemon Controller thread"; |
| 38 |
| 39 // A base::Thread implementation that initializes COM on the new thread. |
| 40 class ComThread : public base::Thread { |
| 41 public: |
| 42 explicit ComThread(const char* name); |
| 43 |
| 44 // Activates an elevated instance of the controller and returns the pointer |
| 45 // to the control interface in |control_out|. This class keeps the ownership |
| 46 // of the pointer so the caller should not call call AddRef() or Release(). |
| 47 HRESULT ActivateElevatedController(IDaemonControl** control_out); |
| 48 |
| 49 bool Start(); |
| 50 |
| 51 protected: |
| 52 virtual void Init() OVERRIDE; |
| 53 virtual void CleanUp() OVERRIDE; |
| 54 |
| 55 IDaemonControl* control_; |
| 56 |
| 57 DISALLOW_COPY_AND_ASSIGN(ComThread); |
| 58 }; |
| 59 |
16 class DaemonControllerWin : public remoting::DaemonController { | 60 class DaemonControllerWin : public remoting::DaemonController { |
17 public: | 61 public: |
18 DaemonControllerWin(); | 62 DaemonControllerWin(); |
| 63 virtual ~DaemonControllerWin(); |
19 | 64 |
20 virtual State GetState() OVERRIDE; | 65 virtual State GetState() OVERRIDE; |
21 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; | 66 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; |
22 virtual void SetConfigAndStart( | 67 virtual void SetConfigAndStart( |
23 scoped_ptr<base::DictionaryValue> config) OVERRIDE; | 68 scoped_ptr<base::DictionaryValue> config) OVERRIDE; |
24 virtual void SetPin(const std::string& pin) OVERRIDE; | 69 virtual void SetPin(const std::string& pin) OVERRIDE; |
25 virtual void Stop() OVERRIDE; | 70 virtual void Stop() OVERRIDE; |
26 | 71 |
27 private: | 72 private: |
| 73 // Opens the Chromoting service returning its handle in |service_out|. |
| 74 DWORD OpenService(ScopedScHandle* service_out); |
| 75 |
| 76 // The functions that actually do the work. They should be called in |
| 77 // the context of |worker_thread_|; |
| 78 void DoGetConfig(const GetConfigCallback& callback); |
| 79 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config); |
| 80 void DoStop(); |
| 81 |
| 82 // Converts a Windows service status code to a Daemon state. |
| 83 static State ConvertToDaemonState(DWORD service_state); |
| 84 |
| 85 // The worker thread used for servicing long running operations. |
| 86 ComThread worker_thread_; |
| 87 |
| 88 // The lock protecting access to all data members below. |
| 89 base::Lock lock_; |
| 90 |
| 91 // The error occurred during the last transition. |
| 92 HRESULT last_error_; |
| 93 |
| 94 // The daemon state reported to the JavaScript code. |
| 95 State state_; |
| 96 |
| 97 // The state that should never be reported to JS unless there is an error. |
| 98 // For instance, when Start() is called, the state of the service doesn't |
| 99 // switch to "starting" immediately. This could lead to JS interpreting |
| 100 // "stopped" as a failure to start the service. |
| 101 // TODO(alexeypa): remove this variable once JS interface supports callbacks. |
| 102 State forbidden_state_; |
| 103 |
28 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); | 104 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); |
29 }; | 105 }; |
30 | 106 |
31 DaemonControllerWin::DaemonControllerWin() { | 107 ComThread::ComThread(const char* name) : base::Thread(name), control_(NULL) { |
32 } | 108 } |
33 | 109 |
34 DaemonController::State DaemonControllerWin::GetState() { | 110 void ComThread::Init() { |
35 return DaemonController::STATE_NOT_IMPLEMENTED; | 111 CoInitialize(NULL); |
| 112 } |
| 113 |
| 114 void ComThread::CleanUp() { |
| 115 if (control_ != NULL) |
| 116 control_->Release(); |
| 117 CoUninitialize(); |
| 118 } |
| 119 |
| 120 HRESULT ComThread::ActivateElevatedController( |
| 121 IDaemonControl** control_out) { |
| 122 // Chache the instance of Elevated Controller to prevent a UAC prompt on every |
| 123 // operation. |
| 124 if (control_ == NULL) { |
| 125 BIND_OPTS3 bind_options; |
| 126 memset(&bind_options, 0, sizeof(bind_options)); |
| 127 bind_options.cbStruct = sizeof(bind_options); |
| 128 bind_options.hwnd = NULL; |
| 129 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; |
| 130 |
| 131 HRESULT hr = ::CoGetObject(ASCIIToUTF16(kElevationMoniker).c_str(), |
| 132 &bind_options, |
| 133 IID_IDaemonControl, |
| 134 reinterpret_cast<void**>(&control_)); |
| 135 if (FAILED(hr)) { |
| 136 LOG(ERROR) << "Failed to create the elevated controller (error: 0x" |
| 137 << std::hex << hr << std::dec << ")."; |
| 138 return hr; |
| 139 } |
| 140 } |
| 141 |
| 142 *control_out = control_; |
| 143 return S_OK; |
| 144 } |
| 145 |
| 146 bool ComThread::Start() { |
| 147 // N.B. The single threaded COM apartment must be run on a UI message loop. |
| 148 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); |
| 149 return StartWithOptions(thread_options); |
| 150 } |
| 151 |
| 152 DaemonControllerWin::DaemonControllerWin() |
| 153 : last_error_(S_OK), |
| 154 state_(STATE_UNKNOWN), |
| 155 forbidden_state_(STATE_UNKNOWN), |
| 156 worker_thread_(kDaemonControllerThreadName) { |
| 157 if (!worker_thread_.Start()) { |
| 158 // N.B. Start() does not report the error code returned by the system. |
| 159 last_error_ = E_FAIL; |
| 160 } |
| 161 } |
| 162 |
| 163 DaemonControllerWin::~DaemonControllerWin() { |
| 164 worker_thread_.Stop(); |
| 165 } |
| 166 |
| 167 remoting::DaemonController::State DaemonControllerWin::GetState() { |
| 168 // TODO(alexeypa): Make the thread alertable, so we can switch to APC |
| 169 // notifications rather than polling. |
| 170 ScopedScHandle service; |
| 171 DWORD error = OpenService(&service); |
| 172 |
| 173 if (error == ERROR_SUCCESS) { |
| 174 SERVICE_STATUS status; |
| 175 if (::QueryServiceStatus(service, &status)) { |
| 176 State new_state = ConvertToDaemonState(status.dwCurrentState); |
| 177 |
| 178 base::AutoLock lock(lock_); |
| 179 // TODO(alexeypa): Remove |forbidden_state_| hack once JS interface |
| 180 // supports callbacks. |
| 181 if (forbidden_state_ != new_state || FAILED(last_error_)) { |
| 182 state_ = new_state; |
| 183 } |
| 184 |
| 185 // TODO(alexeypa): Remove this hack once JS nicely reports errors. |
| 186 if (FAILED(last_error_)) { |
| 187 state_ = STATE_START_FAILED; |
| 188 } |
| 189 |
| 190 return state_; |
| 191 } else { |
| 192 error = GetLastError(); |
| 193 LOG_GETLASTERROR(ERROR) |
| 194 << "Failed to query the state of the '" << kWindowsServiceName |
| 195 << "' service"; |
| 196 } |
| 197 } |
| 198 |
| 199 base::AutoLock lock(lock_); |
| 200 if (error == ERROR_SERVICE_DOES_NOT_EXIST) { |
| 201 state_ = STATE_NOT_IMPLEMENTED; |
| 202 } else { |
| 203 last_error_ = HRESULT_FROM_WIN32(error); |
| 204 state_ = STATE_UNKNOWN; |
| 205 } |
| 206 |
| 207 return state_; |
36 } | 208 } |
37 | 209 |
38 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { | 210 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { |
39 NOTIMPLEMENTED(); | 211 worker_thread_.message_loop_proxy()->PostTask( |
| 212 FROM_HERE, |
| 213 base::Bind(&DaemonControllerWin::DoGetConfig, |
| 214 base::Unretained(this), callback)); |
40 } | 215 } |
41 | 216 |
42 void DaemonControllerWin::SetConfigAndStart( | 217 void DaemonControllerWin::SetConfigAndStart( |
43 scoped_ptr<base::DictionaryValue> config) { | 218 scoped_ptr<base::DictionaryValue> config) { |
44 NOTIMPLEMENTED(); | 219 base::AutoLock lock(lock_); |
| 220 |
| 221 // TODO(alexeypa): Implement on-demand installation. |
| 222 if (state_ == STATE_STOPPED) { |
| 223 last_error_ = S_OK; |
| 224 forbidden_state_ = STATE_STOPPED; |
| 225 state_ = STATE_STARTING; |
| 226 worker_thread_.message_loop_proxy()->PostTask( |
| 227 FROM_HERE, |
| 228 base::Bind(&DaemonControllerWin::DoSetConfigAndStart, |
| 229 base::Unretained(this), base::Passed(&config))); |
| 230 } |
45 } | 231 } |
46 | 232 |
47 void DaemonControllerWin::SetPin(const std::string& pin) { | 233 void DaemonControllerWin::SetPin(const std::string& pin) { |
48 NOTIMPLEMENTED(); | 234 NOTIMPLEMENTED(); |
49 } | 235 } |
50 | 236 |
51 void DaemonControllerWin::Stop() { | 237 void DaemonControllerWin::Stop() { |
52 NOTIMPLEMENTED(); | 238 base::AutoLock lock(lock_); |
| 239 |
| 240 if (state_ == STATE_STARTING || |
| 241 state_ == STATE_STARTED || |
| 242 state_ == STATE_STOPPING) { |
| 243 last_error_ = S_OK; |
| 244 forbidden_state_ = STATE_STARTED; |
| 245 state_ = STATE_STOPPING; |
| 246 worker_thread_.message_loop_proxy()->PostTask( |
| 247 FROM_HERE, |
| 248 base::Bind(&DaemonControllerWin::DoStop, base::Unretained(this))); |
| 249 } |
| 250 } |
| 251 |
| 252 DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) { |
| 253 // Open the service and query its current state. |
| 254 ScopedScHandle scmanager( |
| 255 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, |
| 256 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); |
| 257 if (!scmanager.IsValid()) { |
| 258 DWORD error = GetLastError(); |
| 259 LOG_GETLASTERROR(ERROR) |
| 260 << "Failed to connect to the service control manager"; |
| 261 return error; |
| 262 } |
| 263 |
| 264 ScopedScHandle service( |
| 265 ::OpenServiceW(scmanager, UTF8ToUTF16(kWindowsServiceName).c_str(), |
| 266 SERVICE_QUERY_STATUS)); |
| 267 if (!service.IsValid()) { |
| 268 DWORD error = GetLastError(); |
| 269 if (error != ERROR_SERVICE_DOES_NOT_EXIST) { |
| 270 LOG_GETLASTERROR(ERROR) |
| 271 << "Failed to open to the '" << kWindowsServiceName << "' service"; |
| 272 } |
| 273 return error; |
| 274 } |
| 275 |
| 276 service_out->Set(service.Take()); |
| 277 return ERROR_SUCCESS; |
| 278 } |
| 279 |
| 280 void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) { |
| 281 IDaemonControl* control = NULL; |
| 282 HRESULT hr = worker_thread_.ActivateElevatedController(&control); |
| 283 if (FAILED(hr)) { |
| 284 callback.Run(scoped_ptr<base::DictionaryValue>()); |
| 285 return; |
| 286 } |
| 287 |
| 288 // Get the host configuration. |
| 289 BSTR host_config = NULL; |
| 290 hr = control->GetConfig(&host_config); |
| 291 if (FAILED(hr)) { |
| 292 callback.Run(scoped_ptr<base::DictionaryValue>()); |
| 293 return; |
| 294 } |
| 295 |
| 296 string16 file_content(static_cast<char16*>(host_config), |
| 297 ::SysStringLen(host_config)); |
| 298 SysFreeString(host_config); |
| 299 |
| 300 // Parse the string into a dictionary. |
| 301 scoped_ptr<base::Value> config( |
| 302 base::JSONReader::Read(UTF16ToUTF8(file_content), true)); |
| 303 |
| 304 base::DictionaryValue* dictionary; |
| 305 if (config.get() == NULL || !config->GetAsDictionary(&dictionary)) { |
| 306 callback.Run(scoped_ptr<base::DictionaryValue>()); |
| 307 return; |
| 308 } |
| 309 |
| 310 config.release(); |
| 311 callback.Run(scoped_ptr<base::DictionaryValue>(dictionary)); |
| 312 } |
| 313 |
| 314 void DaemonControllerWin::DoSetConfigAndStart( |
| 315 scoped_ptr<base::DictionaryValue> config) { |
| 316 IDaemonControl* control = NULL; |
| 317 HRESULT hr = worker_thread_.ActivateElevatedController(&control); |
| 318 if (FAILED(hr)) { |
| 319 base::AutoLock lock(lock_); |
| 320 last_error_ = hr; |
| 321 return; |
| 322 } |
| 323 |
| 324 // Store the configuration. |
| 325 std::string file_content; |
| 326 base::JSONWriter::Write(config.get(), &file_content); |
| 327 |
| 328 BSTR host_config = ::SysAllocString(UTF8ToUTF16(file_content).c_str()); |
| 329 if (host_config == NULL) { |
| 330 base::AutoLock lock(lock_); |
| 331 last_error_ = E_OUTOFMEMORY; |
| 332 return; |
| 333 } |
| 334 |
| 335 hr = control->SetConfig(host_config); |
| 336 ::SysFreeString(host_config); |
| 337 if (FAILED(hr)) { |
| 338 base::AutoLock lock(lock_); |
| 339 last_error_ = hr; |
| 340 return; |
| 341 } |
| 342 |
| 343 // Start daemon. |
| 344 hr = control->StartDaemon(); |
| 345 if (FAILED(hr)) { |
| 346 base::AutoLock lock(lock_); |
| 347 last_error_ = hr; |
| 348 } |
| 349 } |
| 350 |
| 351 void DaemonControllerWin::DoStop() { |
| 352 IDaemonControl* control = NULL; |
| 353 HRESULT hr = worker_thread_.ActivateElevatedController(&control); |
| 354 if (FAILED(hr)) { |
| 355 base::AutoLock lock(lock_); |
| 356 last_error_ = hr; |
| 357 return; |
| 358 } |
| 359 |
| 360 hr = control->StopDaemon(); |
| 361 if (FAILED(hr)) { |
| 362 base::AutoLock lock(lock_); |
| 363 last_error_ = hr; |
| 364 } |
| 365 } |
| 366 |
| 367 // static |
| 368 remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState( |
| 369 DWORD service_state) { |
| 370 switch (service_state) { |
| 371 case SERVICE_RUNNING: |
| 372 return STATE_STARTED; |
| 373 |
| 374 case SERVICE_CONTINUE_PENDING: |
| 375 case SERVICE_START_PENDING: |
| 376 return STATE_STARTING; |
| 377 break; |
| 378 |
| 379 case SERVICE_PAUSE_PENDING: |
| 380 case SERVICE_STOP_PENDING: |
| 381 return STATE_STOPPING; |
| 382 break; |
| 383 |
| 384 case SERVICE_PAUSED: |
| 385 case SERVICE_STOPPED: |
| 386 return STATE_STOPPED; |
| 387 break; |
| 388 |
| 389 default: |
| 390 NOTREACHED(); |
| 391 return STATE_UNKNOWN; |
| 392 } |
53 } | 393 } |
54 | 394 |
55 } // namespace | 395 } // namespace |
56 | 396 |
57 DaemonController* remoting::DaemonController::Create() { | 397 DaemonController* remoting::DaemonController::Create() { |
58 return new DaemonControllerWin(); | 398 return new DaemonControllerWin(); |
59 } | 399 } |
60 | 400 |
61 } // namespace remoting | 401 } // namespace remoting |
OLD | NEW |