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 |