| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |