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/files/file_path_watcher.h"
12 #include "base/files/file_util.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
18 namespace remoting {
19
20 // The name of the command-line switch used to specify the host configuration
21 // file to use.
22 const char kHostConfigSwitchName[] = "host-config";
23
24 const 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.
30 const int kMaxRetries = 3;
31 #endif // defined(OS_WIN)
32
33 class 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
ConfigFileWatcher(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,const base::FilePath & config_path)89 ConfigFileWatcher::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
~ConfigFileWatcher()97 ConfigFileWatcher::~ConfigFileWatcher() {
98 impl_->StopWatching();
99 impl_ = NULL;
100 }
101
Watch(ConfigWatcher::Delegate * delegate)102 void ConfigFileWatcher::Watch(ConfigWatcher::Delegate* delegate) {
103 impl_->Watch(delegate);
104 }
105
ConfigFileWatcherImpl(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,const base::FilePath & config_path)106 ConfigFileWatcherImpl::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
Watch(ConfigWatcher::Delegate * delegate)119 void 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
WatchOnIoThread()130 void 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
StopWatching()158 void 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
~ConfigFileWatcherImpl()166 ConfigFileWatcherImpl::~ConfigFileWatcherImpl() {
167 DCHECK(!config_updated_timer_);
168 DCHECK(!config_watcher_);
169 }
170
FinishStopping()171 void ConfigFileWatcherImpl::FinishStopping() {
172 DCHECK(io_task_runner_->BelongsToCurrentThread());
173
174 config_updated_timer_.reset();
175 config_watcher_.reset();
176 }
177
OnConfigUpdated(const base::FilePath & path,bool error)178 void 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
NotifyError()190 void ConfigFileWatcherImpl::NotifyError() {
191 DCHECK(main_task_runner_->BelongsToCurrentThread());
192
193 delegate_->OnConfigWatcherError();
194 }
195
NotifyUpdate(const std::string & config)196 void ConfigFileWatcherImpl::NotifyUpdate(const std::string& config) {
197 DCHECK(main_task_runner_->BelongsToCurrentThread());
198
199 delegate_->OnConfigUpdated(config_);
200 }
201
ReloadConfig()202 void 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