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