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

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

Powered by Google App Engine
This is Rietveld 408576698