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