Home | History | Annotate | Download | only in setup
      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
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/host/setup/daemon_controller.h"
      6 
      7 #include <launch.h>
      8 #include <stdio.h>
      9 #include <sys/types.h>
     10 
     11 #include "base/basictypes.h"
     12 #include "base/bind.h"
     13 #include "base/compiler_specific.h"
     14 #include "base/file_util.h"
     15 #include "base/files/file_path.h"
     16 #include "base/json/json_writer.h"
     17 #include "base/logging.h"
     18 #include "base/mac/foundation_util.h"
     19 #include "base/mac/launchd.h"
     20 #include "base/mac/mac_logging.h"
     21 #include "base/mac/mac_util.h"
     22 #include "base/mac/scoped_launch_data.h"
     23 #include "base/threading/thread.h"
     24 #include "base/time/time.h"
     25 #include "base/values.h"
     26 #include "remoting/host/constants_mac.h"
     27 #include "remoting/host/json_host_config.h"
     28 #include "remoting/host/usage_stats_consent.h"
     29 
     30 namespace remoting {
     31 
     32 namespace {
     33 
     34 class DaemonControllerMac : public remoting::DaemonController {
     35  public:
     36   DaemonControllerMac();
     37   virtual ~DaemonControllerMac();
     38 
     39   virtual State GetState() OVERRIDE;
     40   virtual void GetConfig(const GetConfigCallback& callback) OVERRIDE;
     41   virtual void SetConfigAndStart(
     42       scoped_ptr<base::DictionaryValue> config,
     43       bool consent,
     44       const CompletionCallback& done) OVERRIDE;
     45   virtual void UpdateConfig(scoped_ptr<base::DictionaryValue> config,
     46                             const CompletionCallback& done_callback) OVERRIDE;
     47   virtual void Stop(const CompletionCallback& done_callback) OVERRIDE;
     48   virtual void SetWindow(void* window_handle) OVERRIDE;
     49   virtual void GetVersion(const GetVersionCallback& done_callback) OVERRIDE;
     50   virtual void GetUsageStatsConsent(
     51       const GetUsageStatsConsentCallback& callback) OVERRIDE;
     52 
     53  private:
     54   void DoGetConfig(const GetConfigCallback& callback);
     55   void DoGetVersion(const GetVersionCallback& callback);
     56   void DoSetConfigAndStart(scoped_ptr<base::DictionaryValue> config,
     57                            bool consent,
     58                            const CompletionCallback& done);
     59   void DoUpdateConfig(scoped_ptr<base::DictionaryValue> config,
     60                       const CompletionCallback& done_callback);
     61   void DoStop(const CompletionCallback& done_callback);
     62   void DoGetUsageStatsConsent(const GetUsageStatsConsentCallback& callback);
     63 
     64   void ShowPreferencePane(const std::string& config_data,
     65                           const CompletionCallback& done_callback);
     66   void RegisterForPreferencePaneNotifications(
     67       const CompletionCallback &done_callback);
     68   void DeregisterForPreferencePaneNotifications();
     69   void PreferencePaneCallbackDelegate(CFStringRef name);
     70   static bool DoShowPreferencePane(const std::string& config_data);
     71   static void PreferencePaneCallback(CFNotificationCenterRef center,
     72                                      void* observer,
     73                                      CFStringRef name,
     74                                      const void* object,
     75                                      CFDictionaryRef user_info);
     76 
     77   base::Thread auth_thread_;
     78   CompletionCallback current_callback_;
     79 
     80   DISALLOW_COPY_AND_ASSIGN(DaemonControllerMac);
     81 };
     82 
     83 DaemonControllerMac::DaemonControllerMac()
     84     : auth_thread_("Auth thread") {
     85   auth_thread_.Start();
     86 }
     87 
     88 DaemonControllerMac::~DaemonControllerMac() {
     89   auth_thread_.Stop();
     90   DeregisterForPreferencePaneNotifications();
     91 }
     92 
     93 void DaemonControllerMac::DeregisterForPreferencePaneNotifications() {
     94   CFNotificationCenterRemoveObserver(
     95       CFNotificationCenterGetDistributedCenter(),
     96       this,
     97       CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
     98       NULL);
     99   CFNotificationCenterRemoveObserver(
    100       CFNotificationCenterGetDistributedCenter(),
    101       this,
    102       CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
    103       NULL);
    104 }
    105 
    106 DaemonController::State DaemonControllerMac::GetState() {
    107   pid_t job_pid = base::mac::PIDForJob(kServiceName);
    108   if (job_pid < 0) {
    109     return DaemonController::STATE_NOT_INSTALLED;
    110   } else if (job_pid == 0) {
    111     // Service is stopped, or a start attempt failed.
    112     return DaemonController::STATE_STOPPED;
    113   } else {
    114     return DaemonController::STATE_STARTED;
    115   }
    116 }
    117 
    118 void DaemonControllerMac::GetConfig(const GetConfigCallback& callback) {
    119   // base::Unretained() is safe, since this object owns the thread and therefore
    120   // outlives it.
    121   auth_thread_.message_loop_proxy()->PostTask(
    122       FROM_HERE,
    123       base::Bind(&DaemonControllerMac::DoGetConfig, base::Unretained(this),
    124                  callback));
    125 }
    126 
    127 void DaemonControllerMac::SetConfigAndStart(
    128     scoped_ptr<base::DictionaryValue> config,
    129     bool consent,
    130     const CompletionCallback& done) {
    131   auth_thread_.message_loop_proxy()->PostTask(
    132       FROM_HERE, base::Bind(
    133           &DaemonControllerMac::DoSetConfigAndStart, base::Unretained(this),
    134           base::Passed(&config), consent, done));
    135 }
    136 
    137 void DaemonControllerMac::UpdateConfig(
    138     scoped_ptr<base::DictionaryValue> config,
    139     const CompletionCallback& done_callback) {
    140   auth_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
    141       &DaemonControllerMac::DoUpdateConfig, base::Unretained(this),
    142       base::Passed(&config), done_callback));
    143 }
    144 
    145 void DaemonControllerMac::Stop(const CompletionCallback& done_callback) {
    146   auth_thread_.message_loop_proxy()->PostTask(
    147       FROM_HERE, base::Bind(
    148           &DaemonControllerMac::DoStop, base::Unretained(this), done_callback));
    149 }
    150 
    151 void DaemonControllerMac::SetWindow(void* window_handle) {
    152   // noop
    153 }
    154 
    155 void DaemonControllerMac::GetVersion(const GetVersionCallback& callback) {
    156   auth_thread_.message_loop_proxy()->PostTask(
    157       FROM_HERE,
    158       base::Bind(&DaemonControllerMac::DoGetVersion, base::Unretained(this),
    159                  callback));
    160 }
    161 
    162 void DaemonControllerMac::GetUsageStatsConsent(
    163     const GetUsageStatsConsentCallback& callback) {
    164   auth_thread_.message_loop_proxy()->PostTask(
    165       FROM_HERE,
    166       base::Bind(&DaemonControllerMac::DoGetUsageStatsConsent,
    167                  base::Unretained(this), callback));
    168 }
    169 
    170 void DaemonControllerMac::DoGetConfig(const GetConfigCallback& callback) {
    171   base::FilePath config_path(kHostConfigFilePath);
    172   JsonHostConfig host_config(config_path);
    173   scoped_ptr<base::DictionaryValue> config;
    174 
    175   if (host_config.Read()) {
    176     config.reset(new base::DictionaryValue());
    177     std::string value;
    178     if (host_config.GetString(kHostIdConfigPath, &value))
    179       config.get()->SetString(kHostIdConfigPath, value);
    180     if (host_config.GetString(kXmppLoginConfigPath, &value))
    181       config.get()->SetString(kXmppLoginConfigPath, value);
    182   }
    183 
    184   callback.Run(config.Pass());
    185 }
    186 
    187 void DaemonControllerMac::DoGetVersion(const GetVersionCallback& callback) {
    188   std::string version = "";
    189   std::string command_line = remoting::kHostHelperScriptPath;
    190   command_line += " --host-version";
    191   FILE* script_output = popen(command_line.c_str(), "r");
    192   if (script_output) {
    193     char buffer[100];
    194     char* result = fgets(buffer, sizeof(buffer), script_output);
    195     pclose(script_output);
    196     if (result) {
    197       // The string is guaranteed to be null-terminated, but probably contains
    198       // a newline character, which we don't want.
    199       for (int i = 0; result[i]; ++i) {
    200         if (result[i] < ' ') {
    201           result[i] = 0;
    202           break;
    203         }
    204       }
    205       version = result;
    206     }
    207   }
    208   callback.Run(version);
    209 }
    210 
    211 void DaemonControllerMac::DoSetConfigAndStart(
    212     scoped_ptr<base::DictionaryValue> config,
    213     bool consent,
    214     const CompletionCallback& done) {
    215   config->SetBoolean(kUsageStatsConsentConfigPath, consent);
    216   std::string config_data;
    217   base::JSONWriter::Write(config.get(), &config_data);
    218   ShowPreferencePane(config_data, done);
    219 }
    220 
    221 void DaemonControllerMac::DoUpdateConfig(
    222     scoped_ptr<base::DictionaryValue> config,
    223     const CompletionCallback& done_callback) {
    224   base::FilePath config_file_path(kHostConfigFilePath);
    225   JsonHostConfig config_file(config_file_path);
    226   if (!config_file.Read()) {
    227     done_callback.Run(RESULT_FAILED);
    228     return;
    229   }
    230   if (!config_file.CopyFrom(config.get())) {
    231     LOG(ERROR) << "Failed to update configuration.";
    232     done_callback.Run(RESULT_FAILED);
    233     return;
    234   }
    235 
    236   std::string config_data = config_file.GetSerializedData();
    237   ShowPreferencePane(config_data, done_callback);
    238 }
    239 
    240 void DaemonControllerMac::DoGetUsageStatsConsent(
    241     const GetUsageStatsConsentCallback& callback) {
    242   bool allowed = false;
    243   base::FilePath config_file_path(kHostConfigFilePath);
    244   JsonHostConfig host_config(config_file_path);
    245   if (host_config.Read()) {
    246     host_config.GetBoolean(kUsageStatsConsentConfigPath, &allowed);
    247   }
    248   // set_by_policy is not yet supported.
    249   callback.Run(true, allowed, false /* set_by_policy */);
    250 }
    251 
    252 void DaemonControllerMac::ShowPreferencePane(
    253     const std::string& config_data, const CompletionCallback& done_callback) {
    254   if (DoShowPreferencePane(config_data)) {
    255     RegisterForPreferencePaneNotifications(done_callback);
    256   } else {
    257     done_callback.Run(RESULT_FAILED);
    258   }
    259 }
    260 
    261 bool DaemonControllerMac::DoShowPreferencePane(const std::string& config_data) {
    262   if (!config_data.empty()) {
    263     base::FilePath config_path;
    264     if (!file_util::GetTempDir(&config_path)) {
    265       LOG(ERROR) << "Failed to get filename for saving configuration data.";
    266       return false;
    267     }
    268     config_path = config_path.Append(kHostConfigFileName);
    269 
    270     int written = file_util::WriteFile(config_path, config_data.data(),
    271                                        config_data.size());
    272     if (written != static_cast<int>(config_data.size())) {
    273       LOG(ERROR) << "Failed to save configuration data to: "
    274                  << config_path.value();
    275       return false;
    276     }
    277   }
    278 
    279   base::FilePath pane_path;
    280   // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
    281   // building against SDK 10.6.
    282   if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
    283     LOG(ERROR) << "Failed to get directory for local preference panes.";
    284     return false;
    285   }
    286   pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName);
    287 
    288   FSRef pane_path_ref;
    289   if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
    290     LOG(ERROR) << "Failed to create FSRef";
    291     return false;
    292   }
    293   OSStatus status = LSOpenFSRef(&pane_path_ref, NULL);
    294   if (status != noErr) {
    295     OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
    296                                 << pane_path.value();
    297     return false;
    298   }
    299 
    300   CFNotificationCenterRef center =
    301       CFNotificationCenterGetDistributedCenter();
    302   base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString(
    303       kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8));
    304   CFNotificationCenterPostNotification(center, service_name, NULL, NULL,
    305                                        TRUE);
    306   return true;
    307 }
    308 
    309 void DaemonControllerMac::DoStop(const CompletionCallback& done_callback) {
    310   ShowPreferencePane("", done_callback);
    311 }
    312 
    313 // CFNotificationCenterAddObserver ties the thread on which distributed
    314 // notifications are received to the one on which it is first called.
    315 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback
    316 // bounces the invocation to the correct thread, so it doesn't matter
    317 // which thread CompletionCallbacks are called on.
    318 void DaemonControllerMac::RegisterForPreferencePaneNotifications(
    319     const CompletionCallback& done_callback) {
    320   // We can only have one callback registered at a time. This is enforced by the
    321   // UX flow of the web-app.
    322   DCHECK(current_callback_.is_null());
    323   current_callback_ = done_callback;
    324 
    325   CFNotificationCenterAddObserver(
    326       CFNotificationCenterGetDistributedCenter(),
    327       this,
    328       &DaemonControllerMac::PreferencePaneCallback,
    329       CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
    330       NULL,
    331       CFNotificationSuspensionBehaviorDeliverImmediately);
    332   CFNotificationCenterAddObserver(
    333       CFNotificationCenterGetDistributedCenter(),
    334       this,
    335       &DaemonControllerMac::PreferencePaneCallback,
    336       CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
    337       NULL,
    338       CFNotificationSuspensionBehaviorDeliverImmediately);
    339 }
    340 
    341 void DaemonControllerMac::PreferencePaneCallbackDelegate(CFStringRef name) {
    342   AsyncResult result = RESULT_FAILED;
    343   if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
    344           kCFCompareEqualTo) {
    345     result = RESULT_OK;
    346   } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
    347           kCFCompareEqualTo) {
    348     result = RESULT_FAILED;
    349   } else {
    350     LOG(WARNING) << "Ignoring unexpected notification: " << name;
    351     return;
    352   }
    353   DCHECK(!current_callback_.is_null());
    354   current_callback_.Run(result);
    355   current_callback_.Reset();
    356   DeregisterForPreferencePaneNotifications();
    357 }
    358 
    359 void DaemonControllerMac::PreferencePaneCallback(CFNotificationCenterRef center,
    360                                                  void* observer,
    361                                                  CFStringRef name,
    362                                                  const void* object,
    363                                                  CFDictionaryRef user_info) {
    364   DaemonControllerMac* self = reinterpret_cast<DaemonControllerMac*>(observer);
    365   if (self) {
    366     self->PreferencePaneCallbackDelegate(name);
    367   } else {
    368     LOG(WARNING) << "Ignoring notification with NULL observer: " << name;
    369   }
    370 }
    371 
    372 }  // namespace
    373 
    374 scoped_ptr<DaemonController> remoting::DaemonController::Create() {
    375   return scoped_ptr<DaemonController>(new DaemonControllerMac());
    376 }
    377 
    378 }  // namespace remoting
    379