Home | History | Annotate | Download | only in host
      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