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..d2fb109ab3fe360ce715e5982e55b7466faa5554 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> |
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.
|
+#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 worker thread. |
+const char kWorkerThreadName[] = "Daemon Controller thread"; |
+ |
+// 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.
|
+// the owner thread. |
+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.
|
+ public: |
+ explicit CoThread(const char* name); |
+ |
+ // Activates an elevated instance of elevated controller and returns |
+ // the pointer to the control interface in |control_out|. This routine keeps |
+ // the ownership of the pointer so the caller should not call call AddRef() or |
+ // Release(). |
+ HRESULT ActivateElevatedController(IDaemonControl** control_out); |
+ |
+ protected: |
+ virtual void Init() OVERRIDE; |
+ virtual void CleanUp() OVERRIDE; |
+ |
+ IDaemonControl* control_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(CoThread); |
+}; |
+ |
class DaemonControllerWin : public remoting::DaemonController { |
public: |
DaemonControllerWin(); |
+ virtual ~DaemonControllerWin(); |
virtual State GetState() OVERRIDE; |
virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE; |
@@ -25,23 +70,171 @@ class DaemonControllerWin : public remoting::DaemonController { |
virtual void Stop() OVERRIDE; |
private: |
+ // Activates an elevated instance of elevated controller and returns |
+ // the pointer to the control interface in |control_out|. |
+ HRESULT ActivateElevatedController(IDaemonControl** control_out); |
+ |
+ // Opens the controlled service handle. |
+ DWORD OpenService(ScopedScHandle* service_out); |
+ |
+ // Worker functions called in the context of the worker thread. |
+ void DoGetConfig(const GetConfigCallback& callback); |
+ void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config); |
+ void DoStop(); |
+ |
+ // Converts SERVICE_XXX contants representing the service state (i.e. |
+ // SERVICE_RUNNING, SERVICE_STOPPED) to the daemon state. |
+ static State ConvertToDaemonState(DWORD service_state); |
+ |
+ // Service status change notification callback. |
+ static VOID CALLBACK OnServiceStatusChange(PVOID context); |
+ |
+ // The worker thread used for servicing long running operations. |
+ CoThread worker_thread_; |
+ |
+ // The lock protecting access to all data members below. |
+ base::Lock lock_; |
+ |
+ // The error occurred during the last transition. |
+ HRESULT last_error_; |
+ |
+ // Cached daemon state. |
+ 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. |
+ 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.
|
+ |
DISALLOW_COPY_AND_ASSIGN(DaemonControllerWin); |
}; |
-DaemonControllerWin::DaemonControllerWin() { |
+CoThread::CoThread(const char* name) : base::Thread(name), control_(NULL) { |
} |
-DaemonController::State DaemonControllerWin::GetState() { |
- return DaemonController::STATE_NOT_IMPLEMENTED; |
+void CoThread::Init() { |
+ CoInitialize(NULL); |
+} |
+ |
+void CoThread::CleanUp() { |
+ if (control_ != NULL) { |
+ control_->Release(); |
+ } |
+ |
+ CoUninitialize(); |
+} |
+ |
+HRESULT CoThread::ActivateElevatedController( |
+ IDaemonControl** control_out) { |
+ 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.
|
+ |
+ 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; |
+ |
+ IDaemonControl* control = NULL; |
+ 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
|
+ &bind_options, |
+ IID_IDaemonControl, |
+ (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.
|
+ } else { |
+ hr = S_OK; |
+ } |
+ |
+ if (SUCCEEDED(hr)) { |
+ *control_out = control_; |
+ } else { |
+ LOG(ERROR) << "Failed to create the elevated controller (error: 0x" |
+ << 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
|
+ } |
+ |
+ return hr; |
+} |
+ |
+DaemonControllerWin::DaemonControllerWin() |
+ : last_error_(S_OK), |
+ state_(STATE_UNKNOWN), |
+ forbidden_state_(STATE_UNKNOWN), |
+ worker_thread_(kWorkerThreadName) { |
+ |
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
|
+ base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); |
+ if (!worker_thread_.StartWithOptions(thread_options)) { |
+ // N.B. StartWithOptions() does not report the error code returned by |
+ // the system. |
+ last_error_ = E_FAIL; |
+ } |
+} |
+ |
+DaemonControllerWin::~DaemonControllerWin() { |
+ worker_thread_.Stop(); |
+} |
+ |
+remoting::DaemonController::State DaemonControllerWin::GetState() { |
+ // TODO(alexeypa): convert polling to async callbacks once there is a thread |
+ // that can receive APC callbacks. |
+ 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_); |
+ 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
|
+ 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_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.
|
+ } 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 +242,166 @@ 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, ASCIIToUTF16(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)); |
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.
|
+ |
+ 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.
|
+ callback.Run(scoped_ptr<base::DictionaryValue>()); |
+ return; |
+ } |
+ |
+ callback.Run(scoped_ptr<base::DictionaryValue>( |
+ static_cast<base::DictionaryValue*>(config.release()))); |
+} |
+ |
+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; |
+ } |
+ |
+ // Set the configuration file |
+ 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( |
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
|
+ 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 |