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 31namespace remoting { 32 33DaemonControllerDelegateMac::DaemonControllerDelegateMac() { 34} 35 36DaemonControllerDelegateMac::~DaemonControllerDelegateMac() { 37 DeregisterForPreferencePaneNotifications(); 38} 39 40DaemonController::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 52scoped_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 69void 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 79void 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 98void DaemonControllerDelegateMac::Stop( 99 const DaemonController::CompletionCallback& done) { 100 ShowPreferencePane("", done); 101} 102 103void DaemonControllerDelegateMac::SetWindow(void* window_handle) { 104 // noop 105} 106 107std::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 132DaemonController::UsageStatsConsent 133DaemonControllerDelegateMac::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 149void 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. 164void 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 187void 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 200void 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 223bool 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 273void 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 289scoped_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