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. | |
Wez
2012/03/30 22:11:01
nit: Consider adding a sentence to explain what th
alexeypa (please no reviews)
2012/03/30 23:47:09
This is typical RTFM. I.e. one sentence is too lit
| |
33 const char kElevationMoniker[] = "Elevation:Administrator!new:" | |
34 "{430a9403-8176-4733-afdc-0b325a8fda84}"; | |
35 | |
36 // Name of the worker thread. | |
Wez
2012/03/30 22:11:01
nit: Similarly, what worker thread? What does the
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
37 const char kWorkerThreadName[] = "Daemon Controller thread"; | |
38 | |
39 // A simple wrapper around base::Thread making sure that COM is initialized on | |
40 // the owner thread. | |
Wez
2012/03/30 22:11:01
nit: I think you mean "A base::Thread implementati
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
41 class ComThread : public base::Thread { | |
42 public: | |
43 explicit ComThread(const char* name); | |
44 | |
45 // Activates an elevated instance of elevated controller and returns | |
Wez
2012/03/30 22:11:01
nit: ... elevated instance of the controller ...
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
46 // the pointer to the control interface in |control_out|. This routine keeps | |
Wez
2012/03/30 22:11:01
nit: The routine can't keep ownership; do you mean
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
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_; | |
Wez
2012/03/30 22:11:01
nit: Perhaps we should have a ScopedIPtr... ;)
alexeypa (please no reviews)
2012/03/30 23:47:09
It is not going to help in this case.
| |
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 // Activates an elevated instance of elevated controller and returns | |
Wez
2012/03/30 22:11:01
nit: See above.
alexeypa (please no reviews)
2012/03/30 23:47:09
Stale code. Removed.
| |
74 // the pointer to the control interface in |control_out|. | |
75 HRESULT ActivateElevatedController(IDaemonControl** control_out); | |
76 | |
77 // Opens the controlled service handle. | |
Wez
2012/03/30 22:11:01
nit: What is the controlled service handle? You m
alexeypa (please no reviews)
2012/03/30 23:47:09
Reworded.
| |
78 DWORD OpenService(ScopedScHandle* service_out); | |
79 | |
80 // Worker functions called in the context of the worker thread. | |
Wez
2012/03/30 22:11:01
nit: Double use of the term "worker" makes this co
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
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. | |
Wez
2012/03/30 22:11:01
nit: "Converts a Windows service status code to a
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
87 static State ConvertToDaemonState(DWORD service_state); | |
88 | |
89 // Service status change notification callback. | |
Wez
2012/03/30 22:11:01
nit: Suggest rewording e.g. "Status change callbac
alexeypa (please no reviews)
2012/03/30 23:47:09
Stale code.
| |
90 static VOID CALLBACK OnServiceStatusChange(PVOID context); | |
91 | |
92 // The worker thread used for servicing long running operations. | |
93 ComThread 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. | |
Wez
2012/03/30 22:11:01
nit: This is the state as of the most recent statu
alexeypa (please no reviews)
2012/03/30 23:47:09
No, it is not. Reworded.
| |
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. | |
Wez
2012/03/30 22:11:01
This comment is very confusing, as is the name |fo
alexeypa (please no reviews)
2012/03/30 23:47:09
forbidden_state_ is a hack and it is confusing bec
| |
108 // TODO(alexeypa): remove this variable once JS interafce supports callbacks. | |
Wez
2012/03/30 22:11:01
typo: interafce
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
109 State forbidden_state_; | |
110 | |
28 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); | 111 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); |
29 }; | 112 }; |
30 | 113 |
31 DaemonControllerWin::DaemonControllerWin() { | 114 ComThread::ComThread(const char* name) : base::Thread(name), control_(NULL) { |
32 } | 115 } |
33 | 116 |
34 DaemonController::State DaemonControllerWin::GetState() { | 117 void ComThread::Init() { |
35 return DaemonController::STATE_NOT_IMPLEMENTED; | 118 CoInitialize(NULL); |
119 } | |
120 | |
121 void ComThread::CleanUp() { | |
122 if (control_ != NULL) { | |
123 control_->Release(); | |
124 } | |
125 | |
Wez
2012/03/30 22:11:01
nit: Personally I'd lose the {} on the if, and thi
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
126 CoUninitialize(); | |
127 } | |
128 | |
129 HRESULT ComThread::ActivateElevatedController( | |
130 IDaemonControl** control_out) { | |
131 | |
Wez
2012/03/30 22:11:01
nit: No need for blank line.
alexeypa (please no reviews)
2012/03/30 23:47:09
I replaced it with a comment.
| |
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; | |
Wez
2012/03/30 22:11:01
This doesn't seem to be used?
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
140 HRESULT hr = ::CoGetObject(ASCIIToUTF16(kElevationMoniker).c_str(), | |
141 &bind_options, | |
142 IID_IDaemonControl, | |
143 reinterpret_cast<void**>(&control_)); | |
144 if (FAILED(hr)) { | |
145 LOG(ERROR) << "Failed to create the elevated controller (error: 0x" | |
146 << std::hex << hr << std::dec << ")."; | |
147 return hr; | |
148 } | |
149 } | |
150 | |
151 *control_out = control_; | |
152 return S_OK; | |
153 } | |
154 | |
155 DaemonControllerWin::DaemonControllerWin() | |
156 : last_error_(S_OK), | |
157 state_(STATE_UNKNOWN), | |
158 forbidden_state_(STATE_UNKNOWN), | |
159 worker_thread_(kWorkerThreadName) { | |
160 // N.B. The UI message loop is required for the single threaded COM apartment. | |
Wez
2012/03/30 22:11:01
typo: The UI ... is required for ... -> COM must
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
161 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); | |
162 if (!worker_thread_.StartWithOptions(thread_options)) { | |
Wez
2012/03/30 22:11:01
Since ComThread doesn't make sense unless started
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
163 // N.B. StartWithOptions() does not report the error code returned by | |
164 // the system. | |
165 last_error_ = E_FAIL; | |
166 } | |
167 } | |
168 | |
169 DaemonControllerWin::~DaemonControllerWin() { | |
170 worker_thread_.Stop(); | |
171 } | |
172 | |
173 remoting::DaemonController::State DaemonControllerWin::GetState() { | |
174 // TODO(alexeypa): convert polling to async callbacks once there is a thread | |
175 // that can receive APC callbacks. | |
Wez
2012/03/30 22:11:01
nit: Suggest "Make the thread alertable, so we can
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
176 ScopedScHandle service; | |
177 DWORD error = OpenService(&service); | |
178 | |
179 if (error == ERROR_SUCCESS) { | |
180 SERVICE_STATUS status; | |
181 if (::QueryServiceStatus(service, &status)) { | |
182 State new_state = ConvertToDaemonState(status.dwCurrentState); | |
183 | |
184 base::AutoLock lock(lock_); | |
185 // TODO(alexeypa): remove |forbidden_state_| hack once JS interface | |
186 // supports callbacks. | |
Wez
2012/03/30 22:11:01
nit: remove ... -> Remove ...
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
187 if (forbidden_state_ != new_state || FAILED(last_error_)) { | |
188 state_ = new_state; | |
189 } | |
190 | |
191 // TODO(alexeypa): remove this hack once JS nicely reports errors. | |
Wez
2012/03/30 22:11:01
nit: It's not immediately obvious what the hack is
alexeypa (please no reviews)
2012/03/30 23:47:09
I'm sorry. It is there. Look more closely. :-)
| |
192 if (FAILED(last_error_)) { | |
193 state_ = STATE_START_FAILED; | |
194 } | |
195 | |
196 return state_; | |
197 } else { | |
198 error = GetLastError(); | |
199 LOG_GETLASTERROR(ERROR) | |
200 << "Failed to query the state of the '" << kWindowsServiceName | |
201 << "' service"; | |
202 } | |
203 } | |
204 | |
205 base::AutoLock lock(lock_); | |
206 if (error == ERROR_SERVICE_DOES_NOT_EXIST) { | |
207 state_ = STATE_NOT_IMPLEMENTED; | |
208 } else { | |
209 last_error_ = HRESULT_FROM_WIN32(error); | |
210 state_ = STATE_UNKNOWN; | |
211 } | |
212 | |
213 return state_; | |
36 } | 214 } |
37 | 215 |
38 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { | 216 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { |
39 NOTIMPLEMENTED(); | 217 worker_thread_.message_loop_proxy()->PostTask( |
218 FROM_HERE, | |
219 base::Bind(&DaemonControllerWin::DoGetConfig, | |
220 base::Unretained(this), callback)); | |
40 } | 221 } |
41 | 222 |
42 void DaemonControllerWin::SetConfigAndStart( | 223 void DaemonControllerWin::SetConfigAndStart( |
43 scoped_ptr<base::DictionaryValue> config) { | 224 scoped_ptr<base::DictionaryValue> config) { |
44 NOTIMPLEMENTED(); | 225 base::AutoLock lock(lock_); |
226 | |
227 // TODO(alexeypa): implement on-demand installation. | |
Wez
2012/03/30 22:11:01
typo: implement ... -> Implement ...
Wez
2012/03/30 22:11:01
nit: We generally prefer to create TODOs with an a
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
alexeypa (please no reviews)
2012/03/30 23:47:09
While being a good practice (I presume) I haven't
| |
228 if (state_ == STATE_STOPPED) { | |
229 last_error_ = S_OK; | |
230 forbidden_state_ = STATE_STOPPED; | |
231 state_ = STATE_STARTING; | |
232 worker_thread_.message_loop_proxy()->PostTask( | |
233 FROM_HERE, | |
234 base::Bind(&DaemonControllerWin::DoSetConfigAndStart, | |
235 base::Unretained(this), base::Passed(&config))); | |
236 } | |
45 } | 237 } |
46 | 238 |
47 void DaemonControllerWin::SetPin(const std::string& pin) { | 239 void DaemonControllerWin::SetPin(const std::string& pin) { |
48 NOTIMPLEMENTED(); | 240 NOTIMPLEMENTED(); |
49 } | 241 } |
50 | 242 |
51 void DaemonControllerWin::Stop() { | 243 void DaemonControllerWin::Stop() { |
52 NOTIMPLEMENTED(); | 244 base::AutoLock lock(lock_); |
245 | |
246 if (state_ == STATE_STARTING || | |
247 state_ == STATE_STARTED || | |
248 state_ == STATE_STOPPING) { | |
Wez
2012/03/30 22:11:01
These are cached states; isn't there a risk that s
alexeypa (please no reviews)
2012/03/30 23:47:09
There is no danger. The state is inherently out-on
| |
249 | |
Wez
2012/03/30 22:11:01
nit: Errant blank line.
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
250 last_error_ = S_OK; | |
251 forbidden_state_ = STATE_STARTED; | |
252 state_ = STATE_STOPPING; | |
253 worker_thread_.message_loop_proxy()->PostTask( | |
254 FROM_HERE, | |
255 base::Bind(&DaemonControllerWin::DoStop, base::Unretained(this))); | |
256 } | |
257 } | |
258 | |
259 DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) { | |
Wez
2012/03/30 22:11:01
nit: This function looks oddly familiar from Eleva
alexeypa (please no reviews)
2012/03/30 23:47:09
There is little benefit in sharing the same implem
| |
260 // Open the service and query its current state. | |
261 ScopedScHandle scmanager( | |
262 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, | |
263 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); | |
264 if (!scmanager.IsValid()) { | |
265 DWORD error = GetLastError(); | |
266 LOG_GETLASTERROR(ERROR) | |
267 << "Failed to connect to the service control manager"; | |
268 return error; | |
269 } | |
270 | |
271 ScopedScHandle service( | |
272 ::OpenServiceW(scmanager, ASCIIToUTF16(kWindowsServiceName).c_str(), | |
273 SERVICE_QUERY_STATUS)); | |
274 if (!service.IsValid()) { | |
275 DWORD error = GetLastError(); | |
276 if (error != ERROR_SERVICE_DOES_NOT_EXIST) { | |
277 LOG_GETLASTERROR(ERROR) | |
278 << "Failed to open to the '" << kWindowsServiceName << "' service"; | |
279 } | |
280 return error; | |
281 } | |
282 | |
283 service_out->Set(service.Take()); | |
284 return ERROR_SUCCESS; | |
285 } | |
286 | |
287 void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) { | |
288 IDaemonControl* control = NULL; | |
289 HRESULT hr = worker_thread_.ActivateElevatedController(&control); | |
290 if (FAILED(hr)) { | |
291 callback.Run(scoped_ptr<base::DictionaryValue>()); | |
292 return; | |
293 } | |
294 | |
295 // Get the host configuration. | |
296 BSTR host_config = NULL; | |
Wez
2012/03/30 22:11:01
Is there no managed container for BSTR?
alexeypa (please no reviews)
2012/03/30 23:47:09
There is. It drags ATL. We don't want it here.
| |
297 hr = control->GetConfig(&host_config); | |
298 if (FAILED(hr)) { | |
299 callback.Run(scoped_ptr<base::DictionaryValue>()); | |
300 return; | |
301 } | |
302 | |
303 string16 file_content(static_cast<char16*>(host_config), | |
304 ::SysStringLen(host_config)); | |
305 SysFreeString(host_config); | |
306 | |
307 // Parse the string into a dictionary. | |
308 scoped_ptr<base::Value> config( | |
309 base::JSONReader::Read(UTF16ToUTF8(file_content), true)); | |
310 | |
311 base::DictionaryValue* dictionary; | |
312 if (config.get() == NULL || !config->GetAsDictionary(&dictionary)) { | |
313 callback.Run(scoped_ptr<base::DictionaryValue>()); | |
314 return; | |
315 } | |
316 | |
317 config.release(); | |
318 callback.Run(scoped_ptr<base::DictionaryValue>(dictionary)); | |
Wez
2012/03/30 22:11:01
Yup. Still gross ... :P
| |
319 } | |
320 | |
321 void DaemonControllerWin::DoSetConfigAndStart( | |
322 scoped_ptr<base::DictionaryValue> config) { | |
323 | |
Wez
2012/03/30 22:11:01
nit: Errant blank line.
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
324 IDaemonControl* control = NULL; | |
325 HRESULT hr = worker_thread_.ActivateElevatedController(&control); | |
326 if (FAILED(hr)) { | |
327 base::AutoLock lock(lock_); | |
328 last_error_ = hr; | |
Wez
2012/03/30 22:11:01
Why do we lock when setting |last_error_|? Surely
alexeypa (please no reviews)
2012/03/30 23:47:09
Race is fine. Corruption (state_ is not synched wi
| |
329 return; | |
330 } | |
331 | |
332 // Set the configuration file | |
Wez
2012/03/30 22:11:01
Suggest "Store the configuration."
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
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 | |
Wez
2012/03/30 22:11:01
nit: No need for blank line.
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
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 | |
Wez
2012/03/30 22:11:01
nit: No need for blank line.
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
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 | |
Wez
2012/03/30 22:11:01
nit: Blank line.
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
372 if (FAILED(hr)) { | |
373 base::AutoLock lock(lock_); | |
374 last_error_ = hr; | |
375 } | |
376 } | |
377 | |
378 // static | |
379 remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState( | |
380 DWORD service_state) { | |
381 | |
Wez
2012/03/30 22:11:01
nit: Blank line.
alexeypa (please no reviews)
2012/03/30 23:47:09
Done.
| |
382 switch (service_state) { | |
383 case SERVICE_RUNNING: | |
384 return STATE_STARTED; | |
385 | |
Wez
2012/03/30 22:11:01
nit: No need for these blank lines.
alexeypa (please no reviews)
2012/03/30 23:47:09
My eyes hurt. It is unreadable.
| |
386 case SERVICE_CONTINUE_PENDING: | |
387 case SERVICE_START_PENDING: | |
388 return STATE_STARTING; | |
389 break; | |
Wez
2012/03/30 22:11:01
nit: Nor for this break, or the ones below.
alexeypa (please no reviews)
2012/03/30 23:47:09
See above.
| |
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 |