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