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