Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(541)

Side by Side Diff: remoting/host/plugin/daemon_controller_win.cc

Issue 10021003: Implemented on-demand installation of the Chromoting Host on Windows. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fixed errors. Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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> 7 #include <objbase.h>
8 8
9 #include "base/basictypes.h" 9 #include "base/basictypes.h"
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/compiler_specific.h" 11 #include "base/compiler_specific.h"
12 #include "base/file_path.h" 12 #include "base/file_path.h"
13 #include "base/file_util.h" 13 #include "base/file_util.h"
14 #include "base/json/json_reader.h" 14 #include "base/json/json_reader.h"
15 #include "base/json/json_writer.h" 15 #include "base/json/json_writer.h"
16 #include "base/logging.h" 16 #include "base/logging.h"
17 #include "base/synchronization/lock.h"
17 #include "base/threading/thread.h" 18 #include "base/threading/thread.h"
18 #include "base/utf_string_conversions.h" 19 #include "base/utf_string_conversions.h"
19 #include "base/values.h" 20 #include "base/values.h"
21 #include "base/win/scoped_bstr.h"
22 #include "base/win/scoped_comptr.h"
20 #include "remoting/base/scoped_sc_handle_win.h" 23 #include "remoting/base/scoped_sc_handle_win.h"
21 #include "remoting/host/branding.h" 24 #include "remoting/host/branding.h"
25 #include "remoting/host/plugin/daemon_installer_win.h"
22 26
23 // MIDL-generated declarations and definitions. 27 // MIDL-generated declarations and definitions.
24 #include "remoting/host/elevated_controller.h" 28 #include "remoting/host/elevated_controller.h"
25 29
30 using base::win::ScopedBstr;
31 using base::win::ScopedComPtr;
32
26 namespace remoting { 33 namespace remoting {
27 34
28 namespace { 35 namespace {
29 36
30 // The COM elevation moniker for the elevated controller. 37 // The COM elevation moniker for the elevated controller.
31 const char kElevationMoniker[] = "Elevation:Administrator!new:" 38 const char kDaemonControllerElevationMoniker[] = "Elevation:Administrator!new:"
32 "{430a9403-8176-4733-afdc-0b325a8fda84}"; 39 "ChromotingElevatedController.ElevatedController";
33 40
34 // Name of the Daemon Controller's worker thread. 41 // Name of the Daemon Controller's worker thread.
35 const char kDaemonControllerThreadName[] = "Daemon Controller thread"; 42 const char kDaemonControllerThreadName[] = "Daemon Controller thread";
36 43
37 // A base::Thread implementation that initializes COM on the new thread. 44 // A base::Thread implementation that initializes COM on the new thread.
38 class ComThread : public base::Thread { 45 class ComThread : public base::Thread {
39 public: 46 public:
40 explicit ComThread(const char* name); 47 explicit ComThread(const char* name);
41 48
42 // Activates an elevated instance of the controller and returns the pointer 49 // Activates an elevated instance of the controller and returns the pointer
43 // to the control interface in |control_out|. This class keeps the ownership 50 // to the control interface in |control_out|. This class keeps the ownership
44 // of the pointer so the caller should not call call AddRef() or Release(). 51 // of the pointer so the caller should not call call AddRef() or Release().
45 HRESULT ActivateElevatedController(IDaemonControl** control_out); 52 HRESULT ActivateElevatedController(IDaemonControl** control_out);
46 53
47 bool Start(); 54 bool Start();
48 55
49 protected: 56 protected:
50 virtual void Init() OVERRIDE; 57 virtual void Init() OVERRIDE;
51 virtual void CleanUp() OVERRIDE; 58 virtual void CleanUp() OVERRIDE;
52 59
53 IDaemonControl* control_; 60 ScopedComPtr<IDaemonControl> control_;
54 61
55 DISALLOW_COPY_AND_ASSIGN(ComThread); 62 DISALLOW_COPY_AND_ASSIGN(ComThread);
56 }; 63 };
57 64
58 class DaemonControllerWin : public remoting::DaemonController { 65 class DaemonControllerWin : public remoting::DaemonController {
59 public: 66 public:
60 DaemonControllerWin(); 67 DaemonControllerWin();
61 virtual ~DaemonControllerWin(); 68 virtual ~DaemonControllerWin();
62 69
63 virtual State GetState() OVERRIDE; 70 virtual State GetState() OVERRIDE;
64 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; 71 virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE;
65 virtual void SetConfigAndStart( 72 virtual void SetConfigAndStart(
66 scoped_ptr<base::DictionaryValue> config, 73 scoped_ptr<base::DictionaryValue> config,
67 const CompletionCallback& done_callback) OVERRIDE; 74 const CompletionCallback& done_callback) OVERRIDE;
68 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config, 75 virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config,
69 const CompletionCallback& done_callback) OVERRIDE; 76 const CompletionCallback& done_callback) OVERRIDE;
70 virtual void Stop(const CompletionCallback& done_callback) OVERRIDE; 77 virtual void Stop(const CompletionCallback& done_callback) OVERRIDE;
71 78
72 private: 79 private:
73 // Converts a Windows service status code to a Daemon state. 80 // Converts a Windows service status code to a Daemon state.
74 static State ConvertToDaemonState(DWORD service_state); 81 static State ConvertToDaemonState(DWORD service_state);
75 82
76 // Converts HRESULT to the AsyncResult. 83 // Converts HRESULT to the AsyncResult.
77 static AsyncResult HResultToAsyncResult(HRESULT hr); 84 static AsyncResult HResultToAsyncResult(HRESULT hr);
78 85
86 // Procedes with the daemon configuration if the installation succeeded,
87 // otherwise reports the error.
88 void OnInstallationComplete(HRESULT result);
89
79 // Opens the Chromoting service returning its handle in |service_out|. 90 // Opens the Chromoting service returning its handle in |service_out|.
80 DWORD OpenService(ScopedScHandle* service_out); 91 DWORD OpenService(ScopedScHandle* service_out);
81 92
82 // The functions that actually do the work. They should be called in 93 // The functions that actually do the work. They should be called in
83 // the context of |worker_thread_|; 94 // the context of |worker_thread_|;
84 void DoGetConfig(const GetConfigCallback& callback); 95 void DoGetConfig(const GetConfigCallback& callback);
96 void DoInstall(scoped_ptr<base::DictionaryValue> config,
97 const CompletionCallback& done_callback);
85 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config, 98 void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config,
86 const CompletionCallback& done_callback); 99 const CompletionCallback& done_callback);
87 void DoStop(const CompletionCallback& done_callback); 100 void DoStop(const CompletionCallback& done_callback);
88 101
89 // The worker thread used for servicing long running operations. 102 // The worker thread used for servicing long running operations.
90 ComThread worker_thread_; 103 ComThread worker_thread_;
91 104
105 // The lock protecting the data members below.
106 base::Lock lock_;
107
108 scoped_ptr<base::DictionaryValue> config_;
109 CompletionCallback done_callback_;
Sergey Ulanov 2012/04/07 18:09:21 Since this is used only during installation maybe
alexeypa (please no reviews) 2012/04/09 20:24:11 It was removed from the class.
110 scoped_ptr<DaemonInstallerWin> installer_;
111
92 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); 112 DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin);
93 }; 113 };
94 114
95 ComThread::ComThread(const char* name) : base::Thread(name), control_(NULL) { 115 ComThread::ComThread(const char* name) : base::Thread(name), control_(NULL) {
96 } 116 }
97 117
98 void ComThread::Init() { 118 void ComThread::Init() {
99 CoInitialize(NULL); 119 CoInitialize(NULL);
100 } 120 }
101 121
102 void ComThread::CleanUp() { 122 void ComThread::CleanUp() {
103 if (control_ != NULL) 123 control_.Release();
104 control_->Release();
105 CoUninitialize(); 124 CoUninitialize();
106 } 125 }
107 126
108 HRESULT ComThread::ActivateElevatedController( 127 HRESULT ComThread::ActivateElevatedController(
109 IDaemonControl** control_out) { 128 IDaemonControl** control_out) {
110 // Chache the instance of Elevated Controller to prevent a UAC prompt on every 129 // Chache the instance of Elevated Controller to prevent a UAC prompt on every
111 // operation. 130 // operation.
112 if (control_ == NULL) { 131 if (control_.get() == NULL) {
113 BIND_OPTS3 bind_options; 132 BIND_OPTS3 bind_options;
114 memset(&bind_options, 0, sizeof(bind_options)); 133 memset(&bind_options, 0, sizeof(bind_options));
115 bind_options.cbStruct = sizeof(bind_options); 134 bind_options.cbStruct = sizeof(bind_options);
116 bind_options.hwnd = NULL; 135 bind_options.hwnd = NULL;
117 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; 136 bind_options.dwClassContext = CLSCTX_LOCAL_SERVER;
118 137
119 HRESULT hr = ::CoGetObject(ASCIIToUTF16(kElevationMoniker).c_str(), 138 HRESULT hr = ::CoGetObject(
120 &bind_options, 139 ASCIIToUTF16(kDaemonControllerElevationMoniker).c_str(),
121 IID_IDaemonControl, 140 &bind_options,
122 reinterpret_cast<void**>(&control_)); 141 IID_IDaemonControl,
142 control_.ReceiveVoid());
123 if (FAILED(hr)) { 143 if (FAILED(hr)) {
124 LOG(ERROR) << "Failed to create the elevated controller (error: 0x"
125 << std::hex << hr << std::dec << ").";
126 return hr; 144 return hr;
127 } 145 }
128 } 146 }
129 147
130 *control_out = control_; 148 *control_out = control_.get();
131 return S_OK; 149 return S_OK;
132 } 150 }
133 151
134 bool ComThread::Start() { 152 bool ComThread::Start() {
135 // N.B. The single threaded COM apartment must be run on a UI message loop. 153 // N.B. The single threaded COM apartment must be run on a UI message loop.
136 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); 154 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0);
137 return StartWithOptions(thread_options); 155 return StartWithOptions(thread_options);
138 } 156 }
139 157
140 DaemonControllerWin::DaemonControllerWin() 158 DaemonControllerWin::DaemonControllerWin()
(...skipping 20 matching lines...) Expand all
161 return ConvertToDaemonState(status.dwCurrentState); 179 return ConvertToDaemonState(status.dwCurrentState);
162 } else { 180 } else {
163 LOG_GETLASTERROR(ERROR) 181 LOG_GETLASTERROR(ERROR)
164 << "Failed to query the state of the '" << kWindowsServiceName 182 << "Failed to query the state of the '" << kWindowsServiceName
165 << "' service"; 183 << "' service";
166 return STATE_UNKNOWN; 184 return STATE_UNKNOWN;
167 } 185 }
168 break; 186 break;
169 } 187 }
170 case ERROR_SERVICE_DOES_NOT_EXIST: 188 case ERROR_SERVICE_DOES_NOT_EXIST:
171 return STATE_NOT_IMPLEMENTED; 189 return STATE_NOT_INSTALLED;
172 default: 190 default:
173 return STATE_UNKNOWN; 191 return STATE_UNKNOWN;
174 } 192 }
175 } 193 }
176 194
177 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { 195 void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) {
178 worker_thread_.message_loop_proxy()->PostTask( 196 worker_thread_.message_loop_proxy()->PostTask(
179 FROM_HERE, 197 FROM_HERE,
180 base::Bind(&DaemonControllerWin::DoGetConfig, 198 base::Bind(&DaemonControllerWin::DoGetConfig,
181 base::Unretained(this), callback)); 199 base::Unretained(this), callback));
182 } 200 }
183 201
184 void DaemonControllerWin::SetConfigAndStart( 202 void DaemonControllerWin::SetConfigAndStart(
185 scoped_ptr<base::DictionaryValue> config, 203 scoped_ptr<base::DictionaryValue> config,
186 const CompletionCallback& done_callback) { 204 const CompletionCallback& done_callback) {
187 205
188 worker_thread_.message_loop_proxy()->PostTask( 206 worker_thread_.message_loop_proxy()->PostTask(
189 FROM_HERE, base::Bind( 207 FROM_HERE, base::Bind(
190 &DaemonControllerWin::DoSetConfigAndStart, base::Unretained(this), 208 &DaemonControllerWin::DoInstall, base::Unretained(this),
191 base::Passed(&config), done_callback)); 209 base::Passed(&config), done_callback));
192 } 210 }
193 211
194 void DaemonControllerWin::UpdateConfig( 212 void DaemonControllerWin::UpdateConfig(
195 scoped_ptr<base::DictionaryValue> config, 213 scoped_ptr<base::DictionaryValue> config,
196 const CompletionCallback& done_callback) { 214 const CompletionCallback& done_callback) {
197 NOTIMPLEMENTED(); 215 NOTIMPLEMENTED();
198 done_callback.Run(RESULT_FAILED); 216 done_callback.Run(RESULT_FAILED);
199 } 217 }
200 218
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
234 } 252 }
235 253
236 // static 254 // static
237 DaemonController::AsyncResult DaemonControllerWin::HResultToAsyncResult( 255 DaemonController::AsyncResult DaemonControllerWin::HResultToAsyncResult(
238 HRESULT hr) { 256 HRESULT hr) {
239 // TODO(sergeyu): Report other errors to the webapp once it knows 257 // TODO(sergeyu): Report other errors to the webapp once it knows
240 // how to handle them. 258 // how to handle them.
241 return FAILED(hr) ? RESULT_FAILED : RESULT_OK; 259 return FAILED(hr) ? RESULT_FAILED : RESULT_OK;
242 } 260 }
243 261
262 void DaemonControllerWin::OnInstallationComplete(HRESULT result) {
263 scoped_ptr<base::DictionaryValue> config;
264 CompletionCallback done_callback;
265
266 {
267 base::AutoLock lock(lock_);
268 installer_.reset();
269 std::swap(done_callback, done_callback_);
270 config = config_.Pass();
271 }
272
273 if (SUCCEEDED(result)) {
274 DoSetConfigAndStart(config.Pass(), done_callback);
275 } else {
276 LOG(ERROR) << "Failed to install the Chromoting Host "
277 << "(error: 0x" << std::hex << result << std::dec << ").";
278 done_callback.Run(HResultToAsyncResult(result));
279 }
280 }
281
244 DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) { 282 DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) {
245 // Open the service and query its current state. 283 // Open the service and query its current state.
246 ScopedScHandle scmanager( 284 ScopedScHandle scmanager(
247 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, 285 ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE,
248 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); 286 SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
249 if (!scmanager.IsValid()) { 287 if (!scmanager.IsValid()) {
250 DWORD error = GetLastError(); 288 DWORD error = GetLastError();
251 LOG_GETLASTERROR(ERROR) 289 LOG_GETLASTERROR(ERROR)
252 << "Failed to connect to the service control manager"; 290 << "Failed to connect to the service control manager";
253 return error; 291 return error;
(...skipping 17 matching lines...) Expand all
271 309
272 void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) { 310 void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) {
273 IDaemonControl* control = NULL; 311 IDaemonControl* control = NULL;
274 HRESULT hr = worker_thread_.ActivateElevatedController(&control); 312 HRESULT hr = worker_thread_.ActivateElevatedController(&control);
275 if (FAILED(hr)) { 313 if (FAILED(hr)) {
276 callback.Run(scoped_ptr<base::DictionaryValue>()); 314 callback.Run(scoped_ptr<base::DictionaryValue>());
277 return; 315 return;
278 } 316 }
279 317
280 // Get the host configuration. 318 // Get the host configuration.
281 BSTR host_config = NULL; 319 ScopedBstr host_config;
282 hr = control->GetConfig(&host_config); 320 hr = control->GetConfig(host_config.Receive());
283 if (FAILED(hr)) { 321 if (FAILED(hr)) {
284 callback.Run(scoped_ptr<base::DictionaryValue>()); 322 callback.Run(scoped_ptr<base::DictionaryValue>());
285 return; 323 return;
286 } 324 }
287 325
288 string16 file_content(static_cast<char16*>(host_config), 326 string16 file_content(static_cast<BSTR>(host_config), host_config.Length());
289 ::SysStringLen(host_config));
290 SysFreeString(host_config);
291 327
292 // Parse the string into a dictionary. 328 // Parse the string into a dictionary.
293 scoped_ptr<base::Value> config( 329 scoped_ptr<base::Value> config(
294 base::JSONReader::Read(UTF16ToUTF8(file_content), true)); 330 base::JSONReader::Read(UTF16ToUTF8(file_content), true));
295 331
296 base::DictionaryValue* dictionary; 332 base::DictionaryValue* dictionary;
297 if (config.get() == NULL || !config->GetAsDictionary(&dictionary)) { 333 if (config.get() == NULL || !config->GetAsDictionary(&dictionary)) {
298 callback.Run(scoped_ptr<base::DictionaryValue>()); 334 callback.Run(scoped_ptr<base::DictionaryValue>());
299 return; 335 return;
300 } 336 }
301 337
302 config.release(); 338 config.release();
303 callback.Run(scoped_ptr<base::DictionaryValue>(dictionary)); 339 callback.Run(scoped_ptr<base::DictionaryValue>(dictionary));
304 } 340 }
305 341
342 void DaemonControllerWin::DoInstall(scoped_ptr<base::DictionaryValue> config,
Sergey Ulanov 2012/04/07 18:09:21 Since this method installs the host only if it isn
alexeypa (please no reviews) 2012/04/09 20:24:11 Renamed to DoInstallAsNeededAndStart to preserve "
343 const CompletionCallback& done_callback) {
344 IDaemonControl* control = NULL;
345 HRESULT hr = worker_thread_.ActivateElevatedController(&control);
346
347 // Just configure and start the Daemon Controller if it is installed already.
348 if (SUCCEEDED(hr)) {
349 DoSetConfigAndStart(config.Pass(), done_callback);
350 return;
351 }
352
353 // Otherwise, install it if it's COM registration entry is missing.
354 if (hr == CO_E_CLASSSTRING) {
355 base::AutoLock lock(lock_);
356 DCHECK(!config_.get());
357 DCHECK(!installer_.get());
358
359 hr = DaemonInstallerWin::Create(&installer_);
360 if (SUCCEEDED(hr)) {
361 config_ = config.Pass();
Sergey Ulanov 2012/04/07 18:09:21 Do we really need to store these values as class m
alexeypa (please no reviews) 2012/04/09 20:24:11 Done. I kept installer_ as a class member (instea
362 done_callback_ = done_callback;
363 }
364 }
365
366 // Run the installer or report the error if any.
367 if (SUCCEEDED(hr)) {
368 installer_->Install(base::Bind(&DaemonControllerWin::OnInstallationComplete,
369 base::Unretained(this)));
Sergey Ulanov 2012/04/07 18:09:21 nit: not indented properly
alexeypa (please no reviews) 2012/04/09 20:24:11 Done.
370 } else {
371 LOG(ERROR) << "Failed to initiate the Chromoting Host installation "
372 << "(error: 0x" << std::hex << hr << std::dec << ").";
373 done_callback.Run(HResultToAsyncResult(hr));
374 }
375 }
376
306 void DaemonControllerWin::DoSetConfigAndStart( 377 void DaemonControllerWin::DoSetConfigAndStart(
307 scoped_ptr<base::DictionaryValue> config, 378 scoped_ptr<base::DictionaryValue> config,
308 const CompletionCallback& done_callback) { 379 const CompletionCallback& done_callback) {
309 IDaemonControl* control = NULL; 380 IDaemonControl* control = NULL;
310 HRESULT hr = worker_thread_.ActivateElevatedController(&control); 381 HRESULT hr = worker_thread_.ActivateElevatedController(&control);
311 if (FAILED(hr)) { 382 if (FAILED(hr)) {
312 done_callback.Run(HResultToAsyncResult(hr)); 383 done_callback.Run(HResultToAsyncResult(hr));
313 return; 384 return;
314 } 385 }
315 386
316 // Store the configuration. 387 // Store the configuration.
317 std::string file_content; 388 std::string file_content;
318 base::JSONWriter::Write(config.get(), &file_content); 389 base::JSONWriter::Write(config.get(), &file_content);
319 390
320 BSTR host_config = ::SysAllocString(UTF8ToUTF16(file_content).c_str()); 391 ScopedBstr host_config(UTF8ToUTF16(file_content).c_str());
321 if (host_config == NULL) { 392 if (host_config == NULL) {
322 done_callback.Run(HResultToAsyncResult(E_OUTOFMEMORY)); 393 done_callback.Run(HResultToAsyncResult(E_OUTOFMEMORY));
323 return; 394 return;
324 } 395 }
325 396
326 hr = control->SetConfig(host_config); 397 hr = control->SetConfig(host_config);
327 ::SysFreeString(host_config);
328 if (FAILED(hr)) { 398 if (FAILED(hr)) {
329 done_callback.Run(HResultToAsyncResult(hr)); 399 done_callback.Run(HResultToAsyncResult(hr));
330 return; 400 return;
331 } 401 }
332 402
333 // Start daemon. 403 // Start daemon.
334 hr = control->StartDaemon(); 404 hr = control->StartDaemon();
335 done_callback.Run(HResultToAsyncResult(hr)); 405 done_callback.Run(HResultToAsyncResult(hr));
336 } 406 }
337 407
338 void DaemonControllerWin::DoStop(const CompletionCallback& done_callback) { 408 void DaemonControllerWin::DoStop(const CompletionCallback& done_callback) {
339 IDaemonControl* control = NULL; 409 IDaemonControl* control = NULL;
340 HRESULT hr = worker_thread_.ActivateElevatedController(&control); 410 HRESULT hr = worker_thread_.ActivateElevatedController(&control);
341 if (FAILED(hr)) { 411 if (FAILED(hr)) {
342 done_callback.Run(HResultToAsyncResult(hr)); 412 done_callback.Run(HResultToAsyncResult(hr));
343 return; 413 return;
344 } 414 }
345 415
346 hr = control->StopDaemon(); 416 hr = control->StopDaemon();
347 done_callback.Run(HResultToAsyncResult(hr)); 417 done_callback.Run(HResultToAsyncResult(hr));
348 } 418 }
349 419
350 } // namespace 420 } // namespace
351 421
352 scoped_ptr<DaemonController> remoting::DaemonController::Create() { 422 scoped_ptr<DaemonController> remoting::DaemonController::Create() {
353 return scoped_ptr<DaemonController>(new DaemonControllerWin()); 423 return scoped_ptr<DaemonController>(new DaemonControllerWin());
354 } 424 }
355 425
356 } // namespace remoting 426 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698