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::InstallHost(
     70     const DaemonController::CompletionCallback& done) {
     71   NOTREACHED();
     72 }
     73 
     74 void DaemonControllerDelegateMac::SetConfigAndStart(
     75     scoped_ptr<base::DictionaryValue> config,
     76     bool consent,
     77     const DaemonController::CompletionCallback& done) {
     78   config->SetBoolean(kUsageStatsConsentConfigPath, consent);
     79   std::string config_data;
     80   base::JSONWriter::Write(config.get(), &config_data);
     81   ShowPreferencePane(config_data, done);
     82 }
     83 
     84 void DaemonControllerDelegateMac::UpdateConfig(
     85     scoped_ptr<base::DictionaryValue> config,
     86     const DaemonController::CompletionCallback& done) {
     87   base::FilePath config_file_path(kHostConfigFilePath);
     88   JsonHostConfig config_file(config_file_path);
     89   if (!config_file.Read()) {
     90     done.Run(DaemonController::RESULT_FAILED);
     91     return;
     92   }
     93   if (!config_file.CopyFrom(config.get())) {
     94     LOG(ERROR) << "Failed to update configuration.";
     95     done.Run(DaemonController::RESULT_FAILED);
     96     return;
     97   }
     98 
     99   std::string config_data = config_file.GetSerializedData();
    100   ShowPreferencePane(config_data, done);
    101 }
    102 
    103 void DaemonControllerDelegateMac::Stop(
    104     const DaemonController::CompletionCallback& done) {
    105   ShowPreferencePane("", done);
    106 }
    107 
    108 void DaemonControllerDelegateMac::SetWindow(void* window_handle) {
    109   // noop
    110 }
    111 
    112 std::string DaemonControllerDelegateMac::GetVersion() {
    113   std::string version = "";
    114   std::string command_line = remoting::kHostHelperScriptPath;
    115   command_line += " --host-version";
    116   FILE* script_output = popen(command_line.c_str(), "r");
    117   if (script_output) {
    118     char buffer[100];
    119     char* result = fgets(buffer, sizeof(buffer), script_output);
    120     pclose(script_output);
    121     if (result) {
    122       // The string is guaranteed to be null-terminated, but probably contains
    123       // a newline character, which we don't want.
    124       for (int i = 0; result[i]; ++i) {
    125         if (result[i] < ' ') {
    126           result[i] = 0;
    127           break;
    128         }
    129       }
    130       version = result;
    131     }
    132   }
    133 
    134   return version;
    135 }
    136 
    137 DaemonController::UsageStatsConsent
    138 DaemonControllerDelegateMac::GetUsageStatsConsent() {
    139   DaemonController::UsageStatsConsent consent;
    140   consent.supported = true;
    141   consent.allowed = false;
    142   // set_by_policy is not yet supported.
    143   consent.set_by_policy = false;
    144 
    145   base::FilePath config_file_path(kHostConfigFilePath);
    146   JsonHostConfig host_config(config_file_path);
    147   if (host_config.Read()) {
    148     host_config.GetBoolean(kUsageStatsConsentConfigPath, &consent.allowed);
    149   }
    150 
    151   return consent;
    152 }
    153 
    154 void DaemonControllerDelegateMac::ShowPreferencePane(
    155     const std::string& config_data,
    156     const DaemonController::CompletionCallback& done) {
    157   if (DoShowPreferencePane(config_data)) {
    158     RegisterForPreferencePaneNotifications(done);
    159   } else {
    160     done.Run(DaemonController::RESULT_FAILED);
    161   }
    162 }
    163 
    164 // CFNotificationCenterAddObserver ties the thread on which distributed
    165 // notifications are received to the one on which it is first called.
    166 // This is safe because HostNPScriptObject::InvokeAsyncResultCallback
    167 // bounces the invocation to the correct thread, so it doesn't matter
    168 // which thread CompletionCallbacks are called on.
    169 void DaemonControllerDelegateMac::RegisterForPreferencePaneNotifications(
    170     const DaemonController::CompletionCallback& done) {
    171   // We can only have one callback registered at a time. This is enforced by the
    172   // UX flow of the web-app.
    173   DCHECK(current_callback_.is_null());
    174   current_callback_ = done;
    175 
    176   CFNotificationCenterAddObserver(
    177       CFNotificationCenterGetDistributedCenter(),
    178       this,
    179       &DaemonControllerDelegateMac::PreferencePaneCallback,
    180       CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
    181       NULL,
    182       CFNotificationSuspensionBehaviorDeliverImmediately);
    183   CFNotificationCenterAddObserver(
    184       CFNotificationCenterGetDistributedCenter(),
    185       this,
    186       &DaemonControllerDelegateMac::PreferencePaneCallback,
    187       CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
    188       NULL,
    189       CFNotificationSuspensionBehaviorDeliverImmediately);
    190 }
    191 
    192 void DaemonControllerDelegateMac::DeregisterForPreferencePaneNotifications() {
    193   CFNotificationCenterRemoveObserver(
    194       CFNotificationCenterGetDistributedCenter(),
    195       this,
    196       CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME),
    197       NULL);
    198   CFNotificationCenterRemoveObserver(
    199       CFNotificationCenterGetDistributedCenter(),
    200       this,
    201       CFSTR(UPDATE_FAILED_NOTIFICATION_NAME),
    202       NULL);
    203 }
    204 
    205 void DaemonControllerDelegateMac::PreferencePaneCallbackDelegate(
    206     CFStringRef name) {
    207   DaemonController::AsyncResult result = DaemonController::RESULT_FAILED;
    208   if (CFStringCompare(name, CFSTR(UPDATE_SUCCEEDED_NOTIFICATION_NAME), 0) ==
    209           kCFCompareEqualTo) {
    210     result = DaemonController::RESULT_OK;
    211   } else if (CFStringCompare(name, CFSTR(UPDATE_FAILED_NOTIFICATION_NAME), 0) ==
    212           kCFCompareEqualTo) {
    213     result = DaemonController::RESULT_FAILED;
    214   } else {
    215     LOG(WARNING) << "Ignoring unexpected notification: " << name;
    216     return;
    217   }
    218 
    219   DCHECK(!current_callback_.is_null());
    220   DaemonController::CompletionCallback done = current_callback_;
    221   current_callback_.Reset();
    222   done.Run(result);
    223 
    224   DeregisterForPreferencePaneNotifications();
    225 }
    226 
    227 // static
    228 bool DaemonControllerDelegateMac::DoShowPreferencePane(
    229     const std::string& config_data) {
    230   if (!config_data.empty()) {
    231     base::FilePath config_path;
    232     if (!base::GetTempDir(&config_path)) {
    233       LOG(ERROR) << "Failed to get filename for saving configuration data.";
    234       return false;
    235     }
    236     config_path = config_path.Append(kHostConfigFileName);
    237 
    238     int written = base::WriteFile(config_path, config_data.data(),
    239                                        config_data.size());
    240     if (written != static_cast<int>(config_data.size())) {
    241       LOG(ERROR) << "Failed to save configuration data to: "
    242                  << config_path.value();
    243       return false;
    244     }
    245   }
    246 
    247   base::FilePath pane_path;
    248   // TODO(lambroslambrou): Use NSPreferencePanesDirectory once we start
    249   // building against SDK 10.6.
    250   if (!base::mac::GetLocalDirectory(NSLibraryDirectory, &pane_path)) {
    251     LOG(ERROR) << "Failed to get directory for local preference panes.";
    252     return false;
    253   }
    254   pane_path = pane_path.Append("PreferencePanes").Append(kPrefPaneFileName);
    255 
    256   FSRef pane_path_ref;
    257   if (!base::mac::FSRefFromPath(pane_path.value(), &pane_path_ref)) {
    258     LOG(ERROR) << "Failed to create FSRef";
    259     return false;
    260   }
    261   OSStatus status = LSOpenFSRef(&pane_path_ref, NULL);
    262   if (status != noErr) {
    263     OSSTATUS_LOG(ERROR, status) << "LSOpenFSRef failed for path: "
    264                                 << pane_path.value();
    265     return false;
    266   }
    267 
    268   CFNotificationCenterRef center =
    269       CFNotificationCenterGetDistributedCenter();
    270   base::ScopedCFTypeRef<CFStringRef> service_name(CFStringCreateWithCString(
    271       kCFAllocatorDefault, remoting::kServiceName, kCFStringEncodingUTF8));
    272   CFNotificationCenterPostNotification(center, service_name, NULL, NULL,
    273                                        TRUE);
    274   return true;
    275 }
    276 
    277 // static
    278 void DaemonControllerDelegateMac::PreferencePaneCallback(
    279     CFNotificationCenterRef center,
    280     void* observer,
    281     CFStringRef name,
    282     const void* object,
    283     CFDictionaryRef user_info) {
    284   DaemonControllerDelegateMac* self =
    285       reinterpret_cast<DaemonControllerDelegateMac*>(observer);
    286   if (!self) {
    287     LOG(WARNING) << "Ignoring notification with NULL observer: " << name;
    288     return;
    289   }
    290 
    291   self->PreferencePaneCallbackDelegate(name);
    292 }
    293 
    294 scoped_refptr<DaemonController> DaemonController::Create() {
    295   scoped_ptr<DaemonController::Delegate> delegate(
    296       new DaemonControllerDelegateMac());
    297   return new DaemonController(delegate.Pass());
    298 }
    299 
    300 }  // namespace remoting
    301