Index: remoting/host/plugin/daemon_controller_win.cc |
diff --git a/remoting/host/plugin/daemon_controller_win.cc b/remoting/host/plugin/daemon_controller_win.cc |
index 901fe9594c9b7ff156b0f182e7e81ffb5ee8b211..f731175ba2a534bd5b1f14ee496dd96c87755738 100644 |
--- a/remoting/host/plugin/daemon_controller_win.cc |
+++ b/remoting/host/plugin/daemon_controller_win.cc |
@@ -4,18 +4,63 @@ |
#include "remoting/host/plugin/daemon_controller.h" |
+#include <objbase.h> |
+ |
#include "base/basictypes.h" |
+#include "base/bind.h" |
#include "base/compiler_specific.h" |
+#include "base/file_path.h" |
+#include "base/file_util.h" |
+#include "base/json/json_reader.h" |
+#include "base/json/json_writer.h" |
#include "base/logging.h" |
+#include "base/synchronization/lock.h" |
+#include "base/threading/thread.h" |
+#include "base/utf_string_conversions.h" |
#include "base/values.h" |
+#include "remoting/base/scoped_sc_handle_win.h" |
+#include "remoting/host/branding.h" |
+ |
+// MIDL-generated declarations and definitions. |
+#include "elevated_controller.h" |
+#include "elevated_controller_i.c" |
namespace remoting { |
namespace { |
+// The COM elevation moniker for the elevated controller. |
+const char kElevationMoniker[] = "Elevation:Administrator!new:" |
+ "{430a9403-8176-4733-afdc-0b325a8fda84}"; |
+ |
+// Name of the Daemon Controller's worker thread. |
+const char kDaemonControllerThreadName[] = "Daemon Controller thread"; |
+ |
+// A base::Thread implementation that initializes COM on the new thread. |
+class ComThread : public base::Thread { |
+ public: |
+ explicit ComThread(const char* name); |
+ |
+ // Activates an elevated instance of the controller and returns the pointer |
+ // to the control interface in |control_out|. This class keeps the ownership |
+ // of the pointer so the caller should not call call AddRef() or Release(). |
+ HRESULT ActivateElevatedController(IDaemonControl** control_out); |
+ |
+ bool Start(); |
+ |
+ protected: |
+ virtual void Init() OVERRIDE; |
+ virtual void CleanUp() OVERRIDE; |
+ |
+ IDaemonControl* control_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ComThread); |
+}; |
+ |
class DaemonControllerWin : public remoting::DaemonController { |
public: |
DaemonControllerWin(); |
+ virtual ~DaemonControllerWin(); |
virtual State GetState() OVERRIDE; |
virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; |
@@ -25,23 +70,164 @@ class DaemonControllerWin : public remoting::DaemonController { |
virtual void Stop() OVERRIDE; |
private: |
+ // Opens the Chromoting service returning its handle in |service_out|. |
+ DWORD OpenService(ScopedScHandle* service_out); |
+ |
+ // The functions that actually do the work. They should be called in |
+ // the context of |worker_thread_|; |
+ void DoGetConfig(const GetConfigCallback& callback); |
+ void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config); |
+ void DoStop(); |
+ |
+ // Converts a Windows service status code to a Daemon state. |
+ static State ConvertToDaemonState(DWORD service_state); |
+ |
+ // The worker thread used for servicing long running operations. |
+ ComThread worker_thread_; |
+ |
+ // The lock protecting access to all data members below. |
+ base::Lock lock_; |
+ |
+ // The error occurred during the last transition. |
+ HRESULT last_error_; |
+ |
+ // The daemon state reported to the JavaScript code. |
+ State state_; |
+ |
+ // The state that should never be reported to JS unless there is an error. |
+ // For instance, when Start() is called, the state of the service doesn't |
+ // switch to "starting" immediately. This could lead to JS interpreting |
+ // "stopped" as a failure to start the service. |
+ // TODO(alexeypa): remove this variable once JS interface supports callbacks. |
+ State forbidden_state_; |
+ |
DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); |
}; |
-DaemonControllerWin::DaemonControllerWin() { |
+ComThread::ComThread(const char* name) : base::Thread(name), control_(NULL) { |
+} |
+ |
+void ComThread::Init() { |
+ CoInitialize(NULL); |
+} |
+ |
+void ComThread::CleanUp() { |
+ if (control_ != NULL) |
+ control_->Release(); |
+ CoUninitialize(); |
+} |
+ |
+HRESULT ComThread::ActivateElevatedController( |
+ IDaemonControl** control_out) { |
+ // Chache the instance of Elevated Controller to prevent a UAC prompt on every |
+ // operation. |
+ if (control_ == NULL) { |
+ BIND_OPTS3 bind_options; |
+ memset(&bind_options, 0, sizeof(bind_options)); |
+ bind_options.cbStruct = sizeof(bind_options); |
+ bind_options.hwnd = NULL; |
+ bind_options.dwClassContext = CLSCTX_LOCAL_SERVER; |
+ |
+ HRESULT hr = ::CoGetObject(ASCIIToUTF16(kElevationMoniker).c_str(), |
+ &bind_options, |
+ IID_IDaemonControl, |
+ reinterpret_cast<void**>(&control_)); |
+ if (FAILED(hr)) { |
+ LOG(ERROR) << "Failed to create the elevated controller (error: 0x" |
+ << std::hex << hr << std::dec << ")."; |
+ return hr; |
+ } |
+ } |
+ |
+ *control_out = control_; |
+ return S_OK; |
+} |
+ |
+bool ComThread::Start() { |
+ // N.B. The single threaded COM apartment must be run on a UI message loop. |
+ base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); |
+ return StartWithOptions(thread_options); |
+} |
+ |
+DaemonControllerWin::DaemonControllerWin() |
+ : last_error_(S_OK), |
+ state_(STATE_UNKNOWN), |
+ forbidden_state_(STATE_UNKNOWN), |
+ worker_thread_(kDaemonControllerThreadName) { |
+ if (!worker_thread_.Start()) { |
+ // N.B. Start() does not report the error code returned by the system. |
+ last_error_ = E_FAIL; |
+ } |
} |
-DaemonController::State DaemonControllerWin::GetState() { |
- return DaemonController::STATE_NOT_IMPLEMENTED; |
+DaemonControllerWin::~DaemonControllerWin() { |
+ worker_thread_.Stop(); |
+} |
+ |
+remoting::DaemonController::State DaemonControllerWin::GetState() { |
+ // TODO(alexeypa): Make the thread alertable, so we can switch to APC |
+ // notifications rather than polling. |
+ ScopedScHandle service; |
+ DWORD error = OpenService(&service); |
+ |
+ if (error == ERROR_SUCCESS) { |
+ SERVICE_STATUS status; |
+ if (::QueryServiceStatus(service, &status)) { |
+ State new_state = ConvertToDaemonState(status.dwCurrentState); |
+ |
+ base::AutoLock lock(lock_); |
+ // TODO(alexeypa): Remove |forbidden_state_| hack once JS interface |
+ // supports callbacks. |
+ if (forbidden_state_ != new_state || FAILED(last_error_)) { |
+ state_ = new_state; |
+ } |
+ |
+ // TODO(alexeypa): Remove this hack once JS nicely reports errors. |
+ if (FAILED(last_error_)) { |
+ state_ = STATE_START_FAILED; |
+ } |
+ |
+ return state_; |
+ } else { |
+ error = GetLastError(); |
+ LOG_GETLASTERROR(ERROR) |
+ << "Failed to query the state of the '" << kWindowsServiceName |
+ << "' service"; |
+ } |
+ } |
+ |
+ base::AutoLock lock(lock_); |
+ if (error == ERROR_SERVICE_DOES_NOT_EXIST) { |
+ state_ = STATE_NOT_IMPLEMENTED; |
+ } else { |
+ last_error_ = HRESULT_FROM_WIN32(error); |
+ state_ = STATE_UNKNOWN; |
+ } |
+ |
+ return state_; |
} |
void DaemonControllerWin::GetConfig(const GetConfigCallback& callback) { |
- NOTIMPLEMENTED(); |
+ worker_thread_.message_loop_proxy()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&DaemonControllerWin::DoGetConfig, |
+ base::Unretained(this), callback)); |
} |
void DaemonControllerWin::SetConfigAndStart( |
scoped_ptr<base::DictionaryValue> config) { |
- NOTIMPLEMENTED(); |
+ base::AutoLock lock(lock_); |
+ |
+ // TODO(alexeypa): Implement on-demand installation. |
+ if (state_ == STATE_STOPPED) { |
+ last_error_ = S_OK; |
+ forbidden_state_ = STATE_STOPPED; |
+ state_ = STATE_STARTING; |
+ worker_thread_.message_loop_proxy()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&DaemonControllerWin::DoSetConfigAndStart, |
+ base::Unretained(this), base::Passed(&config))); |
+ } |
} |
void DaemonControllerWin::SetPin(const std::string& pin) { |
@@ -49,7 +235,161 @@ void DaemonControllerWin::SetPin(const std::string& pin) { |
} |
void DaemonControllerWin::Stop() { |
- NOTIMPLEMENTED(); |
+ base::AutoLock lock(lock_); |
+ |
+ if (state_ == STATE_STARTING || |
+ state_ == STATE_STARTED || |
+ state_ == STATE_STOPPING) { |
+ last_error_ = S_OK; |
+ forbidden_state_ = STATE_STARTED; |
+ state_ = STATE_STOPPING; |
+ worker_thread_.message_loop_proxy()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&DaemonControllerWin::DoStop, base::Unretained(this))); |
+ } |
+} |
+ |
+DWORD DaemonControllerWin::OpenService(ScopedScHandle* service_out) { |
+ // Open the service and query its current state. |
+ ScopedScHandle scmanager( |
+ ::OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, |
+ SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE)); |
+ if (!scmanager.IsValid()) { |
+ DWORD error = GetLastError(); |
+ LOG_GETLASTERROR(ERROR) |
+ << "Failed to connect to the service control manager"; |
+ return error; |
+ } |
+ |
+ ScopedScHandle service( |
+ ::OpenServiceW(scmanager, UTF8ToUTF16(kWindowsServiceName).c_str(), |
+ SERVICE_QUERY_STATUS)); |
+ if (!service.IsValid()) { |
+ DWORD error = GetLastError(); |
+ if (error != ERROR_SERVICE_DOES_NOT_EXIST) { |
+ LOG_GETLASTERROR(ERROR) |
+ << "Failed to open to the '" << kWindowsServiceName << "' service"; |
+ } |
+ return error; |
+ } |
+ |
+ service_out->Set(service.Take()); |
+ return ERROR_SUCCESS; |
+} |
+ |
+void DaemonControllerWin::DoGetConfig(const GetConfigCallback& callback) { |
+ IDaemonControl* control = NULL; |
+ HRESULT hr = worker_thread_.ActivateElevatedController(&control); |
+ if (FAILED(hr)) { |
+ callback.Run(scoped_ptr<base::DictionaryValue>()); |
+ return; |
+ } |
+ |
+ // Get the host configuration. |
+ BSTR host_config = NULL; |
+ hr = control->GetConfig(&host_config); |
+ if (FAILED(hr)) { |
+ callback.Run(scoped_ptr<base::DictionaryValue>()); |
+ return; |
+ } |
+ |
+ string16 file_content(static_cast<char16*>(host_config), |
+ ::SysStringLen(host_config)); |
+ SysFreeString(host_config); |
+ |
+ // Parse the string into a dictionary. |
+ scoped_ptr<base::Value> config( |
+ base::JSONReader::Read(UTF16ToUTF8(file_content), true)); |
+ |
+ base::DictionaryValue* dictionary; |
+ if (config.get() == NULL || !config->GetAsDictionary(&dictionary)) { |
+ callback.Run(scoped_ptr<base::DictionaryValue>()); |
+ return; |
+ } |
+ |
+ config.release(); |
+ callback.Run(scoped_ptr<base::DictionaryValue>(dictionary)); |
+} |
+ |
+void DaemonControllerWin::DoSetConfigAndStart( |
+ scoped_ptr<base::DictionaryValue> config) { |
+ IDaemonControl* control = NULL; |
+ HRESULT hr = worker_thread_.ActivateElevatedController(&control); |
+ if (FAILED(hr)) { |
+ base::AutoLock lock(lock_); |
+ last_error_ = hr; |
+ return; |
+ } |
+ |
+ // Store the configuration. |
+ std::string file_content; |
+ base::JSONWriter::Write(config.get(), &file_content); |
+ |
+ BSTR host_config = ::SysAllocString(UTF8ToUTF16(file_content).c_str()); |
+ if (host_config == NULL) { |
+ base::AutoLock lock(lock_); |
+ last_error_ = E_OUTOFMEMORY; |
+ return; |
+ } |
+ |
+ hr = control->SetConfig(host_config); |
+ ::SysFreeString(host_config); |
+ if (FAILED(hr)) { |
+ base::AutoLock lock(lock_); |
+ last_error_ = hr; |
+ return; |
+ } |
+ |
+ // Start daemon. |
+ hr = control->StartDaemon(); |
+ if (FAILED(hr)) { |
+ base::AutoLock lock(lock_); |
+ last_error_ = hr; |
+ } |
+} |
+ |
+void DaemonControllerWin::DoStop() { |
+ IDaemonControl* control = NULL; |
+ HRESULT hr = worker_thread_.ActivateElevatedController(&control); |
+ if (FAILED(hr)) { |
+ base::AutoLock lock(lock_); |
+ last_error_ = hr; |
+ return; |
+ } |
+ |
+ hr = control->StopDaemon(); |
+ if (FAILED(hr)) { |
+ base::AutoLock lock(lock_); |
+ last_error_ = hr; |
+ } |
+} |
+ |
+// static |
+remoting::DaemonController::State DaemonControllerWin::ConvertToDaemonState( |
+ DWORD service_state) { |
+ switch (service_state) { |
+ case SERVICE_RUNNING: |
+ return STATE_STARTED; |
+ |
+ case SERVICE_CONTINUE_PENDING: |
+ case SERVICE_START_PENDING: |
+ return STATE_STARTING; |
+ break; |
+ |
+ case SERVICE_PAUSE_PENDING: |
+ case SERVICE_STOP_PENDING: |
+ return STATE_STOPPING; |
+ break; |
+ |
+ case SERVICE_PAUSED: |
+ case SERVICE_STOPPED: |
+ return STATE_STOPPED; |
+ break; |
+ |
+ default: |
+ NOTREACHED(); |
+ return STATE_UNKNOWN; |
+ } |
} |
} // namespace |