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