Chromium Code Reviews| Index: remoting/host/plugin/daemon_controller_mac.cc |
| diff --git a/remoting/host/plugin/daemon_controller_mac.cc b/remoting/host/plugin/daemon_controller_mac.cc |
| index 1a8a2e0f8d6815f49146d2e012f416a00b0a0055..a130fdef4353c9da23c8e85c774d49765284fb55 100644 |
| --- a/remoting/host/plugin/daemon_controller_mac.cc |
| +++ b/remoting/host/plugin/daemon_controller_mac.cc |
| @@ -4,17 +4,40 @@ |
| #include "remoting/host/plugin/daemon_controller.h" |
| +#include <launch.h> |
| +#include <ServiceManagement/ServiceManagement.h> |
| + |
| #include "base/basictypes.h" |
| +#include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| +#include "base/mac/authorization_util.h" |
| +#include "base/mac/foundation_util.h" |
| +#include "base/mac/mac_logging.h" |
| +#include "base/mac/scoped_authorizationref.h" |
| +#include "base/mac/scoped_cftyperef.h" |
| +#include "base/threading/thread.h" |
| namespace remoting { |
| namespace { |
| +// The name of the Remoting Host service that is registered with launchd. |
| +#define kServiceName "com.google.chrome_remote_desktop" |
|
Jamie
2012/03/23 03:24:05
Any reason not to make this a const char[]?
Lambros
2012/03/26 19:15:26
The CFSTR() macro seems to need a literal string.
|
| + |
| +// This helper script is executed as root. It is passed a command-line option |
| +// (--enable or --disable), which causes it to create or remove a trigger file. |
| +// The trigger file (defined in the service's plist file) informs launchd |
| +// whether the Host service should be running. Creating the trigger file causes |
| +// launchd to immediately start the service. Deleting the trigger file has no |
| +// immediate effect, but it prevents the service from being restarted if it |
| +// becomes stopped. |
| +const char kStartStopTool[] = "/Library/PrivilegedHelperTools/me2me.sh"; |
|
garykac
2012/03/23 16:28:29
Since this is a system directory that includes fil
Lambros
2012/03/26 19:15:26
Done.
|
| + |
| class DaemonControllerMac : public remoting::DaemonController { |
| public: |
| DaemonControllerMac(); |
| + virtual ~DaemonControllerMac(); |
| virtual State GetState() OVERRIDE; |
| virtual bool SetPin(const std::string& pin) OVERRIDE; |
| @@ -22,14 +45,53 @@ class DaemonControllerMac : public remoting::DaemonController { |
| virtual bool Stop() OVERRIDE; |
| private: |
| + void DoStart(); |
| + void DoStop(); |
| + |
| + bool RunToolScriptAsRoot(const char* command); |
| + bool StopService(); |
| + |
| + // The API for gaining root privileges is blocking (it prompts the user for |
| + // a password). Since Start() and Stop() must not block the main thread, they |
| + // need to post their tasks to a separate thread. |
| + base::Thread root_thread_; |
|
Jamie
2012/03/23 03:24:05
I think auth_thread might be a better name, since
Sergey Ulanov
2012/03/23 17:56:48
+1
Lambros
2012/03/26 19:15:26
Done.
|
| + |
| + // This distinguishes between STATE_START_FAILED and STATE_STOPPED in |
| + // GetState(). |
| + bool start_failed_; |
| + |
| DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac); |
| }; |
| -DaemonControllerMac::DaemonControllerMac() { |
| +DaemonControllerMac::DaemonControllerMac() |
| + : root_thread_("Root thread"), |
| + start_failed_(false) { |
| + root_thread_.Start(); |
| +} |
| + |
| +DaemonControllerMac::~DaemonControllerMac() { |
| + // This will block if the thread is waiting on a root password prompt. There |
| + // doesn't seem to be an easy solution for this, other than to spawn a |
| + // separate process to do the root elevation. |
| + |
| + // TODO(lambroslambrou): Improve this, either by finding a way to terminate |
| + // the thread, or by moving to a separate process. |
| + root_thread_.Stop(); |
| } |
| remoting::DaemonController::State DaemonControllerMac::GetState() { |
| - return remoting::DaemonController::STATE_NOT_IMPLEMENTED; |
| + base::mac::ScopedCFTypeRef<CFDictionaryRef> job( |
| + SMJobCopyDictionary(kSMDomainUserLaunchd, CFSTR(kServiceName))); |
| + if (!job) |
| + return remoting::DaemonController::STATE_NOT_INSTALLED; |
|
Jamie
2012/03/23 03:24:05
Until we have an installation story, I think you s
Lambros
2012/03/26 19:15:26
Done.
|
| + |
| + if (CFDictionaryGetValue(job.get(), CFSTR("PID"))) { |
| + return remoting::DaemonController::STATE_STARTED; |
| + } else { |
| + // Service is stopped, or a start attempt failed. |
| + return start_failed_ ? remoting::DaemonController::STATE_START_FAILED : |
| + remoting::DaemonController::STATE_STOPPED; |
| + } |
| } |
| bool DaemonControllerMac::SetPin(const std::string& pin) { |
| @@ -38,13 +100,93 @@ bool DaemonControllerMac::SetPin(const std::string& pin) { |
| } |
| bool DaemonControllerMac::Start() { |
| - NOTIMPLEMENTED(); |
| - return false; |
| + start_failed_ = false; |
| + root_thread_.message_loop_proxy()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&DaemonControllerMac::DoStart, base::Unretained(this))); |
|
Sergey Ulanov
2012/03/23 17:56:48
Add comment that it is safe to use base::Unretaine
Lambros
2012/03/26 19:15:26
Done.
|
| + return true; |
| } |
| bool DaemonControllerMac::Stop() { |
| - NOTIMPLEMENTED(); |
| - return false; |
| + root_thread_.message_loop_proxy()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&DaemonControllerMac::DoStop, base::Unretained(this))); |
| + return true; |
| +} |
| + |
| +void DaemonControllerMac::DoStart() { |
| + // Creating the trigger file causes launchd to start the service, so the |
| + // extra step performed in DoStop() is not necessary here. |
| + if (!RunToolScriptAsRoot("--enable")) |
| + start_failed_ = true; |
| +} |
| + |
| +void DaemonControllerMac::DoStop() { |
| + if (!RunToolScriptAsRoot("--disable")) |
| + return; |
| + |
| + // Deleting the trigger file does not cause launchd to stop the service. |
| + // Since the service is running for the local user's desktop (not as root), |
| + // it has to be stopped for that user. This cannot easily be done in the |
| + // shell-script running as root, so it is done here instead. |
| + StopService(); |
| +} |
| + |
| +bool DaemonControllerMac::RunToolScriptAsRoot(const char* command) { |
| + base::mac::ScopedAuthorizationRef authorization( |
| + base::mac::AuthorizationCreateToRunAsRoot(CFSTR(""))); |
| + if (!authorization) { |
| + LOG(ERROR) << "Failed to get root privileges."; |
| + return false; |
| + } |
| + |
| + const char* arguments[] = {command, NULL}; |
|
Sergey Ulanov
2012/03/23 17:56:48
nit: add spaces after { and before }.
Lambros
2012/03/26 19:15:26
Done.
|
| + int exit_status; |
| + OSStatus status = base::mac::ExecuteWithPrivilegesAndWait( |
| + authorization.get(), |
| + kStartStopTool, |
| + kAuthorizationFlagDefaults, |
| + arguments, |
| + NULL, |
| + &exit_status); |
| + if (status != errAuthorizationSuccess) { |
| + OSSTATUS_LOG(ERROR, status) << "AuthorizationExecuteWithPrivileges"; |
| + return false; |
| + } |
| + if (exit_status != 0) { |
| + LOG(ERROR) << kStartStopTool << " failed with exit status " << exit_status; |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool DaemonControllerMac::StopService() { |
| + launch_data_t message = launch_data_alloc(LAUNCH_DATA_DICTIONARY); |
|
Sergey Ulanov
2012/03/23 17:56:48
Maybe move ScopedLaunchData from chrome/browser/ma
|
| + launch_data_dict_insert(message, launch_data_new_string(kServiceName), |
| + LAUNCH_KEY_STOPJOB); |
| + launch_data_t response = launch_msg(message); |
| + launch_data_free(message); |
| + |
| + if (!response) { |
| + LOG(ERROR) << "Failed to send message to launchd"; |
| + return false; |
| + } |
| + |
| + bool success = false; |
| + if (launch_data_get_type(response) == LAUNCH_DATA_ERRNO) { |
| + int error = launch_data_get_errno(response); |
| + if (error) { |
| + LOG(ERROR) << "launchd returned error " << error; |
| + } else { |
| + success = true; |
| + } |
| + } else { |
| + LOG(ERROR) << "launchd returned unexpected response"; |
| + } |
| + |
| + launch_data_free(response); |
| + return success; |
| } |
| } // namespace |