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/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 
     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 
     97 ConfigFileWatcher::~ConfigFileWatcher() {
     98   impl_->StopWatching();
     99   impl_ = NULL;
    100 }
    101 
    102 void ConfigFileWatcher::Watch(ConfigWatcher::Delegate* delegate) {
    103   impl_->Watch(delegate);
    104 }
    105 
    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 
    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 
    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 
    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 
    166 ConfigFileWatcherImpl::~ConfigFileWatcherImpl() {
    167   DCHECK(!config_updated_timer_);
    168   DCHECK(!config_watcher_);
    169 }
    170 
    171 void ConfigFileWatcherImpl::FinishStopping() {
    172   DCHECK(io_task_runner_->BelongsToCurrentThread());
    173 
    174   config_updated_timer_.reset();
    175   config_watcher_.reset();
    176 }
    177 
    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 
    190 void ConfigFileWatcherImpl::NotifyError() {
    191   DCHECK(main_task_runner_->BelongsToCurrentThread());
    192 
    193   delegate_->OnConfigWatcherError();
    194 }
    195 
    196 void ConfigFileWatcherImpl::NotifyUpdate(const std::string& config) {
    197   DCHECK(main_task_runner_->BelongsToCurrentThread());
    198 
    199   delegate_->OnConfigUpdated(config_);
    200 }
    201 
    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