Chromium Code Reviews| 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> | |
|
Sergey Ulanov
2012/03/30 07:42:35
nit: I think this should be in quotes, it's not a
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
| |
| 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 worker thread. | |
| 37 const char kWorkerThreadName[] = "Daemon Controller thread"; | |
| 38 | |
| 39 // A simple wrapper arounf base::Thread making sure that COM is initialized on | |
|
Jamie
2012/03/30 01:11:47
Nit: s/arounf/around/
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
| |
| 40 // the owner thread. | |
| 41 class CoThread: public base::Thread { | |
|
Jamie
2012/03/30 01:11:47
Nit: spacing around colon.
Sergey Ulanov
2012/03/30 07:42:35
should it be called ComThread?
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
| |
| 42 public: | |
| 43 explicit CoThread(const char* name); | |
| 44 | |
| 45 // Activates an elevated instance of elevated controller and returns | |
| 46 // the pointer to the control interface in |control_out|. This routine keeps | |
| 47 // the ownership of the pointer so the caller should not call call AddRef() or | |
| 48 // Release(). | |
| 49 HRESULT ActivateElevatedController(IDaemonControl** control_out); | |
| 50 | |
| 51 protected: | |
| 52 virtual void Init() OVERRIDE; | |
| 53 virtual void CleanUp() OVERRIDE; | |
| 54 | |
| 55 IDaemonControl* control_; | |
| 56 | |
| 57 DISALLOW_COPY_AND_ASSIGN(CoThread); | |
| 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 // Activates an elevated instance of elevated controller and returns | |
| 74 // the pointer to the control interface in |control_out|. | |
| 75 HRESULT ActivateElevatedController(IDaemonControl** control_out); | |
| 76 | |
| 77 // Opens the controlled service handle. | |
| 78 DWORD OpenService(ScopedScHandle* service_out); | |
| 79 | |
| 80 // Worker functions called in the context of the worker thread. | |
| 81 void DoGetConfig(const GetConfigCallback& callback); | |
| 82 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config); | |
| 83 void DoStop(); | |
| 84 | |
| 85 // Converts SERVICE_XXX contants representing the service state (i.e. | |
| 86 // SERVICE_RUNNING, SERVICE_STOPPED) to the daemon state. | |
| 87 static State ConvertToDaemonState(DWORD service_state); | |
| 88 | |
| 89 // Service status change notification callback. | |
| 90 static VOID CALLBACK OnServiceStatusChange(PVOID context); | |
| 91 | |
| 92 // The worker thread used for servicing long running operations. | |
| 93 CoThread worker_thread_; | |
| 94 | |
| 95 // The lock protecting access to all data members below. | |
| 96 base::Lock lock_; | |
| 97 | |
| 98 // The error occurred during the last transition. | |
| 99 HRESULT last_error_; | |
| 100 | |
| 101 // Cached daemon state. | |
| 102 State state_; | |
| 103 | |
| 104 // The state that should never be reported to JS unless there is an error. | |
| 105 // For instance, when Start() is called, the state of the service doesn't | |
| 106 // switch to "starting" immediately. This could lead to JS interpreting | |
| 107 // "stopped" as a failure to start the service. | |
| 108 State forbidden_state_; | |
|
Jamie
2012/03/30 01:11:47
This is a confusing name. If it's doing what I thi
Sergey Ulanov
2012/03/30 07:42:35
Alternatively you can have flags like start_pendin
Sergey Ulanov
2012/03/30 07:42:35
It's not really termination state. The problem is
alexeypa (please no reviews)
2012/03/30 16:30:55
Exactly. I added a TODO.
| |
| 109 | |
| 28 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); | 110 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); |
| 29 }; | 111 }; |
| 30 | 112 |
| 31 DaemonControllerWin::DaemonControllerWin() { | 113 CoThread::CoThread(const char* name) : base::Thread(name), control_(NULL) { |
| 32 } | 114 } |
| 33 | 115 |
| 34 DaemonController::State DaemonControllerWin::GetState() { | 116 void CoThread::Init() { |
| 35 return DaemonController::STATE_NOT_IMPLEMENTED; | 117 CoInitialize(NULL); |
| 118 } | |
| 119 | |
| 120 void CoThread::CleanUp() { | |
| 121 if (control_ != NULL) { | |
| 122 control_->Release(); | |
| 123 } | |
| 124 | |
| 125 CoUninitialize(); | |
| 126 } | |
| 127 | |
| 128 HRESULT CoThread::ActivateElevatedController( | |
| 129 IDaemonControl** control_out) { | |
| 130 HRESULT hr = E_FAIL; | |
|
Jamie
2012/03/30 01:11:47
Couldn't you initialize this to S_OK and skip the
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
| |
| 131 | |
| 132 if (control_ == NULL) { | |
| 133 BIND_OPTS3 bind_options; | |
| 134 memset(&bind_options, 0, sizeof(bind_options)); | |
| 135 bind_options.cbStruct = sizeof(bind_options); | |
| 136 bind_options.hwnd = NULL; | |
| 137 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; | |
| 138 | |
| 139 IDaemonControl* control = NULL; | |
| 140 hr = ::CoGetObject(ASCIIToUTF16(kElevationMoniker).c_str(), | |
|
Sergey Ulanov
2012/03/30 07:42:35
Are you sure that the result of ASCIIToUTF16() is
alexeypa (please no reviews)
2012/03/30 16:30:55
I'm pretty sure. It is guaranteed by the C++ stand
| |
| 141 &bind_options, | |
| 142 IID_IDaemonControl, | |
| 143 (void**)&control_); | |
|
Sergey Ulanov
2012/03/30 07:42:35
C style casts are not allowed by code style: http:
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
| |
| 144 } else { | |
| 145 hr = S_OK; | |
| 146 } | |
| 147 | |
| 148 if (SUCCEEDED(hr)) { | |
| 149 *control_out = control_; | |
| 150 } else { | |
| 151 LOG(ERROR) << "Failed to create the elevated controller (error: 0x" | |
| 152 << std::hex << hr << std::dec << ")."; | |
|
Jamie
2012/03/30 01:11:47
Is the explicit reversion to std::dec necessary?
Sergey Ulanov
2012/03/30 07:42:35
May be cleaner to use StringPrintf() for format he
alexeypa (please no reviews)
2012/03/30 16:30:55
Yes.
alexeypa (please no reviews)
2012/03/30 16:30:55
I tried using StringPrintf. The mixture of streams
| |
| 153 } | |
| 154 | |
| 155 return hr; | |
| 156 } | |
| 157 | |
| 158 DaemonControllerWin::DaemonControllerWin() | |
| 159 : last_error_(S_OK), | |
| 160 state_(STATE_UNKNOWN), | |
| 161 forbidden_state_(STATE_UNKNOWN), | |
| 162 worker_thread_(kWorkerThreadName) { | |
| 163 | |
|
Sergey Ulanov
2012/03/30 07:42:35
nit: don't need this empty line.
alexeypa (please no reviews)
2012/03/30 16:30:55
Without the empty line it looks cluttered. I added
| |
| 164 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); | |
| 165 if (!worker_thread_.StartWithOptions(thread_options)) { | |
| 166 // N.B. StartWithOptions() does not report the error code returned by | |
| 167 // the system. | |
| 168 last_error_ = E_FAIL; | |
| 169 } | |
| 170 } | |
| 171 | |
| 172 DaemonControllerWin::~DaemonControllerWin() { | |
| 173 worker_thread_.Stop(); | |
| 174 } | |
| 175 | |
| 176 remoting::DaemonController::State DaemonControllerWin::GetState() { | |
| 177 // TODO(alexeypa): convert polling to async callbacks once there is a thread | |
| 178 // that can receive APC callbacks. | |
| 179 ScopedScHandle service; | |
| 180 DWORD error = OpenService(&service); | |
| 181 | |
| 182 if (error == ERROR_SUCCESS) { | |
| 183 SERVICE_STATUS status; | |
| 184 if (::QueryServiceStatus(service, &status)) { | |
| 185 State new_state = ConvertToDaemonState(status.dwCurrentState); | |
| 186 | |
| 187 base::AutoLock lock(lock_); | |
| 188 if (forbidden_state_ != new_state || FAILED(last_error_)) { | |
|
Jamie
2012/03/30 01:11:47
Why the second part of the OR? It's overruled by t
alexeypa (please no reviews)
2012/03/30 16:30:55
There are two hacks in there. First we don't repor
| |
| 189 state_ = new_state; | |
| 190 } | |
| 191 | |
| 192 // TODO(alexeypa): remove this hack once JS nicely reports errors. | |
| 193 if (FAILED(last_error_)) { | |
| 194 state_ = STATE_START_FAILED; | |
| 195 } | |
| 196 | |
| 197 return state_; | |
| 198 } else { | |
| 199 error = GetLastError(); | |
| 200 LOG_GETLASTERROR(ERROR) | |
| 201 << "Failed to query the state of the '" << kWindowsServiceName | |
| 202 << "' service"; | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 base::AutoLock lock(lock_); | |
| 207 if (error == ERROR_SERVICE_DOES_NOT_EXIST) { | |
| 208 state_ = STATE_NOT_INSTALLED; | |
|
Jamie
2012/03/30 01:11:47
Should return NOT_IMPLEMENTED for now, because we
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
| |
| 209 } else { | |
| 210 last_error_ = HRESULT_FROM_WIN32(error); | |
| 211 state_ = STATE_UNKNOWN; | |
| 212 } | |
| 213 | |
| 214 return state_; | |
| 36 } | 215 } |
| 37 | 216 |
| 38 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { | 217 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { |
| 39 NOTIMPLEMENTED(); | 218 worker_thread_.message_loop_proxy()->PostTask( |
| 219 FROM_HERE, | |
| 220 base::Bind(&DaemonControllerWin::DoGetConfig, | |
| 221 base::Unretained(this), callback)); | |
| 40 } | 222 } |
| 41 | 223 |
| 42 void DaemonControllerWin::SetConfigAndStart( | 224 void DaemonControllerWin::SetConfigAndStart( |
| 43 scoped_ptr<base::DictionaryValue> config) { | 225 scoped_ptr<base::DictionaryValue> config) { |
| 44 NOTIMPLEMENTED(); | 226 base::AutoLock lock(lock_); |
| 227 | |
| 228 // TODO(alexeypa): implement on-demand installation. | |
| 229 if (state_ == STATE_STOPPED) { | |
| 230 last_error_ = S_OK; | |
| 231 forbidden_state_ = STATE_STOPPED; | |
| 232 state_ = STATE_STARTING; | |
| 233 worker_thread_.message_loop_proxy()->PostTask( | |
| 234 FROM_HERE, | |
| 235 base::Bind(&DaemonControllerWin::DoSetConfigAndStart, | |
| 236 base::Unretained(this), base::Passed(&config))); | |
| 237 } | |
| 45 } | 238 } |
| 46 | 239 |
| 47 void DaemonControllerWin::SetPin(const std::string& pin) { | 240 void DaemonControllerWin::SetPin(const std::string& pin) { |
| 48 NOTIMPLEMENTED(); | 241 NOTIMPLEMENTED(); |
| 49 } | 242 } |
| 50 | 243 |
| 51 void DaemonControllerWin::Stop() { | 244 void DaemonControllerWin::Stop() { |
| 52 NOTIMPLEMENTED(); | 245 base::AutoLock lock(lock_); |
| 246 | |
| 247 if (state_ == STATE_STARTING || | |
| 248 state_ == STATE_STARTED || | |
| 249 state_ == STATE_STOPPING) { | |
| 250 | |
| 251 last_error_ = S_OK; | |
| 252 forbidden_state_ = STATE_STARTED; | |
| 253 state_ = STATE_STOPPING; | |
| 254 worker_thread_.message_loop_proxy()->PostTask( | |
| 255 FROM_HERE, | |
| 256 base::Bind(&DaemonControllerWin::DoStop, base::Unretained(this))); | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) { | |
| 261 // Open the service and query its current state. | |
| 262 ScopedScHandle scmanager( | |
| 263 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, | |
| 264 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); | |
| 265 if (!scmanager.IsValid()) { | |
| 266 DWORD error = GetLastError(); | |
| 267 LOG_GETLASTERROR(ERROR) | |
| 268 << "Failed to connect to the service control manager"; | |
| 269 return error; | |
| 270 } | |
| 271 | |
| 272 ScopedScHandle service( | |
| 273 ::OpenServiceW(scmanager, ASCIIToUTF16(kWindowsServiceName).c_str(), | |
| 274 SERVICE_QUERY_STATUS)); | |
| 275 if (!service.IsValid()) { | |
| 276 DWORD error = GetLastError(); | |
| 277 if (error != ERROR_SERVICE_DOES_NOT_EXIST) { | |
| 278 LOG_GETLASTERROR(ERROR) | |
| 279 << "Failed to open to the '" << kWindowsServiceName << "' service"; | |
| 280 } | |
| 281 return error; | |
| 282 } | |
| 283 | |
| 284 service_out->Set(service.Take()); | |
| 285 return ERROR_SUCCESS; | |
| 286 } | |
| 287 | |
| 288 void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) { | |
| 289 IDaemonControl* control = NULL; | |
| 290 HRESULT hr = worker_thread_.ActivateElevatedController(&control); | |
| 291 if (FAILED(hr)) { | |
| 292 callback.Run(scoped_ptr<base::DictionaryValue>()); | |
| 293 return; | |
| 294 } | |
| 295 | |
| 296 // Get the host configuration. | |
| 297 BSTR host_config = NULL; | |
| 298 hr = control->GetConfig(&host_config); | |
| 299 if (FAILED(hr)) { | |
| 300 callback.Run(scoped_ptr<base::DictionaryValue>()); | |
| 301 return; | |
| 302 } | |
| 303 | |
| 304 string16 file_content(static_cast<char16*>(host_config), | |
| 305 ::SysStringLen(host_config)); | |
| 306 SysFreeString(host_config); | |
| 307 | |
| 308 // Parse the string into a dictionary. | |
| 309 scoped_ptr<base::Value> config( | |
| 310 base::JSONReader::Read(UTF16ToUTF8(file_content), true)); | |
|
Sergey Ulanov
2012/03/30 07:42:35
We should probably change this interface to pass t
alexeypa (please no reviews)
2012/03/30 16:30:55
It would definitely make my life easier.
| |
| 311 | |
| 312 if (config.get() == NULL || !config->IsType(base::Value::TYPE_DICTIONARY)) { | |
|
Sergey Ulanov
2012/03/30 07:42:35
It would be cleaner to use GetAsDictionary() metho
alexeypa (please no reviews)
2012/03/30 16:30:55
Done.
| |
| 313 callback.Run(scoped_ptr<base::DictionaryValue>()); | |
| 314 return; | |
| 315 } | |
| 316 | |
| 317 callback.Run(scoped_ptr<base::DictionaryValue>( | |
| 318 static_cast<base::DictionaryValue*>(config.release()))); | |
| 319 } | |
| 320 | |
| 321 void DaemonControllerWin::DoSetConfigAndStart( | |
| 322 scoped_ptr<base::DictionaryValue> config) { | |
| 323 | |
| 324 IDaemonControl* control = NULL; | |
| 325 HRESULT hr = worker_thread_.ActivateElevatedController(&control); | |
| 326 if (FAILED(hr)) { | |
| 327 base::AutoLock lock(lock_); | |
| 328 last_error_ = hr; | |
| 329 return; | |
| 330 } | |
| 331 | |
| 332 // Set the configuration file | |
| 333 std::string file_content; | |
| 334 base::JSONWriter::Write(config.get(), &file_content); | |
| 335 | |
| 336 BSTR host_config = ::SysAllocString(UTF8ToUTF16(file_content).c_str()); | |
| 337 if (host_config == NULL) { | |
| 338 base::AutoLock lock(lock_); | |
| 339 last_error_ = E_OUTOFMEMORY; | |
| 340 return; | |
| 341 } | |
| 342 | |
| 343 hr = control->SetConfig(host_config); | |
| 344 ::SysFreeString(host_config); | |
| 345 | |
| 346 if (FAILED(hr)) { | |
| 347 base::AutoLock lock(lock_); | |
| 348 last_error_ = hr; | |
| 349 return; | |
| 350 } | |
| 351 | |
| 352 // Start daemon. | |
| 353 hr = control->StartDaemon(); | |
| 354 | |
| 355 if (FAILED(hr)) { | |
| 356 base::AutoLock lock(lock_); | |
| 357 last_error_ = hr; | |
| 358 } | |
| 359 } | |
| 360 | |
| 361 void DaemonControllerWin::DoStop() { | |
| 362 IDaemonControl* control = NULL; | |
| 363 HRESULT hr = worker_thread_.ActivateElevatedController(&control); | |
| 364 if (FAILED(hr)) { | |
| 365 base::AutoLock lock(lock_); | |
| 366 last_error_ = hr; | |
| 367 return; | |
| 368 } | |
| 369 | |
| 370 hr = control->StopDaemon(); | |
| 371 | |
| 372 if (FAILED(hr)) { | |
| 373 base::AutoLock lock(lock_); | |
| 374 last_error_ = hr; | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 // static | |
| 379 remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState( | |
|
Sergey Ulanov
2012/03/30 07:42:35
nit: doesn't look like this need to be a class mem
alexeypa (please no reviews)
2012/03/30 16:30:55
It is a static member. It does not have to be a me
| |
| 380 DWORD service_state) { | |
| 381 | |
| 382 switch (service_state) { | |
| 383 case SERVICE_RUNNING: | |
| 384 return STATE_STARTED; | |
| 385 | |
| 386 case SERVICE_CONTINUE_PENDING: | |
| 387 case SERVICE_START_PENDING: | |
| 388 return STATE_STARTING; | |
| 389 break; | |
| 390 | |
| 391 case SERVICE_PAUSE_PENDING: | |
| 392 case SERVICE_STOP_PENDING: | |
| 393 return STATE_STOPPING; | |
| 394 break; | |
| 395 | |
| 396 case SERVICE_PAUSED: | |
| 397 case SERVICE_STOPPED: | |
| 398 return STATE_STOPPED; | |
| 399 break; | |
| 400 | |
| 401 default: | |
| 402 NOTREACHED(); | |
| 403 return STATE_UNKNOWN; | |
| 404 } | |
| 53 } | 405 } |
| 54 | 406 |
| 55 } // namespace | 407 } // namespace |
| 56 | 408 |
| 57 DaemonController* remoting::DaemonController::Create() { | 409 DaemonController* remoting::DaemonController::Create() { |
| 58 return new DaemonControllerWin(); | 410 return new DaemonControllerWin(); |
| 59 } | 411 } |
| 60 | 412 |
| 61 } // namespace remoting | 413 } // namespace remoting |
| OLD | NEW |