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