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