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/config_file_watcher.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/file_util.h"
12#include "base/files/file_path_watcher.h"
13#include "base/memory/scoped_ptr.h"
14#include "base/memory/weak_ptr.h"
15#include "base/single_thread_task_runner.h"
16#include "base/timer/timer.h"
17
18namespace remoting {
19
20// The name of the command-line switch used to specify the host configuration
21// file to use.
22const char kHostConfigSwitchName[] = "host-config";
23
24const base::FilePath::CharType kDefaultHostConfigFile[] =
25    FILE_PATH_LITERAL("host.json");
26
27#if defined(OS_WIN)
28// Maximum number of times to try reading the configuration file before
29// reporting an error.
30const int kMaxRetries = 3;
31#endif  // defined(OS_WIN)
32
33class ConfigFileWatcherImpl
34    : public base::RefCountedThreadSafe<ConfigFileWatcherImpl> {
35 public:
36  // Creates a configuration file watcher that lives on the |io_task_runner|
37  // thread but posts config file updates on on |main_task_runner|.
38  ConfigFileWatcherImpl(
39      scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
40      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
41      const base::FilePath& config_path);
42
43
44  // Notify |delegate| of config changes.
45  void Watch(ConfigWatcher::Delegate* delegate);
46
47  // Stops watching the configuration file.
48  void StopWatching();
49
50 private:
51  friend class base::RefCountedThreadSafe<ConfigFileWatcherImpl>;
52  virtual ~ConfigFileWatcherImpl();
53
54  void FinishStopping();
55
56  void WatchOnIoThread();
57
58  // Called every time the host configuration file is updated.
59  void OnConfigUpdated(const base::FilePath& path, bool error);
60
61  // Called to notify the delegate of updates/errors in the main thread.
62  void NotifyUpdate(const std::string& config);
63  void NotifyError();
64
65  // Reads the configuration file and passes it to the delegate.
66  void ReloadConfig();
67
68  std::string config_;
69  base::FilePath config_path_;
70
71  scoped_ptr<base::DelayTimer<ConfigFileWatcherImpl> > config_updated_timer_;
72
73  // Number of times an attempt to read the configuration file failed.
74  int retries_;
75
76  // Monitors the host configuration file.
77  scoped_ptr<base::FilePathWatcher> config_watcher_;
78
79  ConfigWatcher::Delegate* delegate_;
80
81  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
82  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
83
84  base::WeakPtrFactory<ConfigFileWatcherImpl> weak_factory_;
85
86  DISALLOW_COPY_AND_ASSIGN(ConfigFileWatcherImpl);
87};
88
89ConfigFileWatcher::ConfigFileWatcher(
90    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
91    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
92    const base::FilePath& config_path)
93    : impl_(new ConfigFileWatcherImpl(main_task_runner,
94                                      io_task_runner, config_path)) {
95}
96
97ConfigFileWatcher::~ConfigFileWatcher() {
98  impl_->StopWatching();
99  impl_ = NULL;
100}
101
102void ConfigFileWatcher::Watch(ConfigWatcher::Delegate* delegate) {
103  impl_->Watch(delegate);
104}
105
106ConfigFileWatcherImpl::ConfigFileWatcherImpl(
107    scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
108    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
109    const base::FilePath& config_path)
110    : config_path_(config_path),
111      retries_(0),
112      delegate_(NULL),
113      main_task_runner_(main_task_runner),
114      io_task_runner_(io_task_runner),
115      weak_factory_(this) {
116  DCHECK(main_task_runner_->BelongsToCurrentThread());
117}
118
119void ConfigFileWatcherImpl::Watch(ConfigWatcher::Delegate* delegate) {
120  DCHECK(main_task_runner_->BelongsToCurrentThread());
121  DCHECK(!delegate_);
122
123  delegate_ = delegate;
124
125  io_task_runner_->PostTask(
126      FROM_HERE,
127      base::Bind(&ConfigFileWatcherImpl::WatchOnIoThread, this));
128}
129
130void ConfigFileWatcherImpl::WatchOnIoThread() {
131  DCHECK(io_task_runner_->BelongsToCurrentThread());
132  DCHECK(!config_updated_timer_);
133  DCHECK(!config_watcher_);
134
135  // Create the timer that will be used for delayed-reading the configuration
136  // file.
137  config_updated_timer_.reset(new base::DelayTimer<ConfigFileWatcherImpl>(
138      FROM_HERE, base::TimeDelta::FromSeconds(2), this,
139      &ConfigFileWatcherImpl::ReloadConfig));
140
141  // Start watching the configuration file.
142  config_watcher_.reset(new base::FilePathWatcher());
143  if (!config_watcher_->Watch(
144          config_path_, false,
145          base::Bind(&ConfigFileWatcherImpl::OnConfigUpdated, this))) {
146    PLOG(ERROR) << "Couldn't watch file '" << config_path_.value() << "'";
147    main_task_runner_->PostTask(
148        FROM_HERE,
149        base::Bind(&ConfigFileWatcherImpl::NotifyError,
150            weak_factory_.GetWeakPtr()));
151    return;
152  }
153
154  // Force reloading of the configuration file at least once.
155  ReloadConfig();
156}
157
158void ConfigFileWatcherImpl::StopWatching() {
159  DCHECK(main_task_runner_->BelongsToCurrentThread());
160
161  weak_factory_.InvalidateWeakPtrs();
162  io_task_runner_->PostTask(
163      FROM_HERE, base::Bind(&ConfigFileWatcherImpl::FinishStopping, this));
164}
165
166ConfigFileWatcherImpl::~ConfigFileWatcherImpl() {
167  DCHECK(!config_updated_timer_);
168  DCHECK(!config_watcher_);
169}
170
171void ConfigFileWatcherImpl::FinishStopping() {
172  DCHECK(io_task_runner_->BelongsToCurrentThread());
173
174  config_updated_timer_.reset();
175  config_watcher_.reset();
176}
177
178void ConfigFileWatcherImpl::OnConfigUpdated(const base::FilePath& path,
179                                            bool error) {
180  DCHECK(io_task_runner_->BelongsToCurrentThread());
181
182  // Call ReloadConfig() after a short delay, so that we will not try to read
183  // the updated configuration file before it has been completely written.
184  // If the writer moves the new configuration file into place atomically,
185  // this delay may not be necessary.
186  if (!error && config_path_ == path)
187    config_updated_timer_->Reset();
188}
189
190void ConfigFileWatcherImpl::NotifyError() {
191  DCHECK(main_task_runner_->BelongsToCurrentThread());
192
193  delegate_->OnConfigWatcherError();
194}
195
196void ConfigFileWatcherImpl::NotifyUpdate(const std::string& config) {
197  DCHECK(main_task_runner_->BelongsToCurrentThread());
198
199  delegate_->OnConfigUpdated(config_);
200}
201
202void ConfigFileWatcherImpl::ReloadConfig() {
203  DCHECK(io_task_runner_->BelongsToCurrentThread());
204
205  std::string config;
206  if (!base::ReadFileToString(config_path_, &config)) {
207#if defined(OS_WIN)
208    // EACCESS may indicate a locking or sharing violation. Retry a few times
209    // before reporting an error.
210    if (errno == EACCES && retries_ < kMaxRetries) {
211      PLOG(WARNING) << "Failed to read '" << config_path_.value() << "'";
212
213      retries_ += 1;
214      config_updated_timer_->Reset();
215      return;
216    }
217#endif  // defined(OS_WIN)
218
219    PLOG(ERROR) << "Failed to read '" << config_path_.value() << "'";
220
221    main_task_runner_->PostTask(
222        FROM_HERE,
223        base::Bind(&ConfigFileWatcherImpl::NotifyError,
224            weak_factory_.GetWeakPtr()));
225    return;
226  }
227
228  retries_ = 0;
229
230  // Post an updated configuration only if it has actually changed.
231  if (config_ != config) {
232    config_ = config;
233    main_task_runner_->PostTask(
234        FROM_HERE,
235        base::Bind(&ConfigFileWatcherImpl::NotifyUpdate,
236            weak_factory_.GetWeakPtr(), config_));
237  }
238}
239
240}  // namespace remoting
241