• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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