Home | History | Annotate | Download | only in files
      1 // Copyright (c) 2011 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 "base/files/file_path_watcher.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/file_util.h"
      9 #include "base/files/file_path.h"
     10 #include "base/logging.h"
     11 #include "base/memory/ref_counted.h"
     12 #include "base/message_loop/message_loop_proxy.h"
     13 #include "base/time/time.h"
     14 #include "base/win/object_watcher.h"
     15 
     16 namespace base {
     17 
     18 namespace {
     19 
     20 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
     21                             public base::win::ObjectWatcher::Delegate,
     22                             public MessageLoop::DestructionObserver {
     23  public:
     24   FilePathWatcherImpl()
     25       : handle_(INVALID_HANDLE_VALUE),
     26         recursive_watch_(false) {}
     27 
     28   // FilePathWatcher::PlatformDelegate overrides.
     29   virtual bool Watch(const FilePath& path,
     30                      bool recursive,
     31                      const FilePathWatcher::Callback& callback) OVERRIDE;
     32   virtual void Cancel() OVERRIDE;
     33 
     34   // Deletion of the FilePathWatcher will call Cancel() to dispose of this
     35   // object in the right thread. This also observes destruction of the required
     36   // cleanup thread, in case it quits before Cancel() is called.
     37   virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
     38 
     39   // Callback from MessageLoopForIO.
     40   virtual void OnObjectSignaled(HANDLE object);
     41 
     42  private:
     43   virtual ~FilePathWatcherImpl() {}
     44 
     45   // Setup a watch handle for directory |dir|. Set |recursive| to true to watch
     46   // the directory sub trees. Returns true if no fatal error occurs. |handle|
     47   // will receive the handle value if |dir| is watchable, otherwise
     48   // INVALID_HANDLE_VALUE.
     49   static bool SetupWatchHandle(const FilePath& dir,
     50                                bool recursive,
     51                                HANDLE* handle) WARN_UNUSED_RESULT;
     52 
     53   // (Re-)Initialize the watch handle.
     54   bool UpdateWatch() WARN_UNUSED_RESULT;
     55 
     56   // Destroy the watch handle.
     57   void DestroyWatch();
     58 
     59   // Cleans up and stops observing the |message_loop_| thread.
     60   void CancelOnMessageLoopThread() OVERRIDE;
     61 
     62   // Callback to notify upon changes.
     63   FilePathWatcher::Callback callback_;
     64 
     65   // Path we're supposed to watch (passed to callback).
     66   FilePath target_;
     67 
     68   // Handle for FindFirstChangeNotification.
     69   HANDLE handle_;
     70 
     71   // ObjectWatcher to watch handle_ for events.
     72   base::win::ObjectWatcher watcher_;
     73 
     74   // Set to true to watch the sub trees of the specified directory file path.
     75   bool recursive_watch_;
     76 
     77   // Keep track of the last modified time of the file.  We use nulltime
     78   // to represent the file not existing.
     79   base::Time last_modified_;
     80 
     81   // The time at which we processed the first notification with the
     82   // |last_modified_| time stamp.
     83   base::Time first_notification_;
     84 
     85   DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
     86 };
     87 
     88 bool FilePathWatcherImpl::Watch(const FilePath& path,
     89                                 bool recursive,
     90                                 const FilePathWatcher::Callback& callback) {
     91   DCHECK(target_.value().empty());  // Can only watch one path.
     92 
     93   set_message_loop(base::MessageLoopProxy::current());
     94   callback_ = callback;
     95   target_ = path;
     96   recursive_watch_ = recursive;
     97   MessageLoop::current()->AddDestructionObserver(this);
     98 
     99   if (!UpdateWatch())
    100     return false;
    101 
    102   watcher_.StartWatching(handle_, this);
    103 
    104   return true;
    105 }
    106 
    107 void FilePathWatcherImpl::Cancel() {
    108   if (callback_.is_null()) {
    109     // Watch was never called, or the |message_loop_| has already quit.
    110     set_cancelled();
    111     return;
    112   }
    113 
    114   // Switch to the file thread if necessary so we can stop |watcher_|.
    115   if (!message_loop()->BelongsToCurrentThread()) {
    116     message_loop()->PostTask(FROM_HERE,
    117                              base::Bind(&FilePathWatcher::CancelWatch,
    118                                         make_scoped_refptr(this)));
    119   } else {
    120     CancelOnMessageLoopThread();
    121   }
    122 }
    123 
    124 void FilePathWatcherImpl::CancelOnMessageLoopThread() {
    125   set_cancelled();
    126 
    127   if (handle_ != INVALID_HANDLE_VALUE)
    128     DestroyWatch();
    129 
    130   if (!callback_.is_null()) {
    131     MessageLoop::current()->RemoveDestructionObserver(this);
    132     callback_.Reset();
    133   }
    134 }
    135 
    136 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
    137   CancelOnMessageLoopThread();
    138 }
    139 
    140 void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
    141   DCHECK(object == handle_);
    142   // Make sure we stay alive through the body of this function.
    143   scoped_refptr<FilePathWatcherImpl> keep_alive(this);
    144 
    145   if (!UpdateWatch()) {
    146     callback_.Run(target_, true /* error */);
    147     return;
    148   }
    149 
    150   // Check whether the event applies to |target_| and notify the callback.
    151   base::PlatformFileInfo file_info;
    152   bool file_exists = file_util::GetFileInfo(target_, &file_info);
    153   if (file_exists && (last_modified_.is_null() ||
    154       last_modified_ != file_info.last_modified)) {
    155     last_modified_ = file_info.last_modified;
    156     first_notification_ = base::Time::Now();
    157     callback_.Run(target_, false);
    158   } else if (file_exists && !first_notification_.is_null()) {
    159     // The target's last modification time is equal to what's on record. This
    160     // means that either an unrelated event occurred, or the target changed
    161     // again (file modification times only have a resolution of 1s). Comparing
    162     // file modification times against the wall clock is not reliable to find
    163     // out whether the change is recent, since this code might just run too
    164     // late. Moreover, there's no guarantee that file modification time and wall
    165     // clock times come from the same source.
    166     //
    167     // Instead, the time at which the first notification carrying the current
    168     // |last_notified_| time stamp is recorded. Later notifications that find
    169     // the same file modification time only need to be forwarded until wall
    170     // clock has advanced one second from the initial notification. After that
    171     // interval, client code is guaranteed to having seen the current revision
    172     // of the file.
    173     if (base::Time::Now() - first_notification_ >
    174         base::TimeDelta::FromSeconds(1)) {
    175       // Stop further notifications for this |last_modification_| time stamp.
    176       first_notification_ = base::Time();
    177     }
    178     callback_.Run(target_, false);
    179   } else if (!file_exists && !last_modified_.is_null()) {
    180     last_modified_ = base::Time();
    181     callback_.Run(target_, false);
    182   }
    183 
    184   // The watch may have been cancelled by the callback.
    185   if (handle_ != INVALID_HANDLE_VALUE)
    186     watcher_.StartWatching(handle_, this);
    187 }
    188 
    189 // static
    190 bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
    191                                            bool recursive,
    192                                            HANDLE* handle) {
    193   *handle = FindFirstChangeNotification(
    194       dir.value().c_str(),
    195       recursive,
    196       FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
    197       FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
    198       FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
    199   if (*handle != INVALID_HANDLE_VALUE) {
    200     // Make sure the handle we got points to an existing directory. It seems
    201     // that windows sometimes hands out watches to directories that are
    202     // about to go away, but doesn't sent notifications if that happens.
    203     if (!DirectoryExists(dir)) {
    204       FindCloseChangeNotification(*handle);
    205       *handle = INVALID_HANDLE_VALUE;
    206     }
    207     return true;
    208   }
    209 
    210   // If FindFirstChangeNotification failed because the target directory
    211   // doesn't exist, access is denied (happens if the file is already gone but
    212   // there are still handles open), or the target is not a directory, try the
    213   // immediate parent directory instead.
    214   DWORD error_code = GetLastError();
    215   if (error_code != ERROR_FILE_NOT_FOUND &&
    216       error_code != ERROR_PATH_NOT_FOUND &&
    217       error_code != ERROR_ACCESS_DENIED &&
    218       error_code != ERROR_SHARING_VIOLATION &&
    219       error_code != ERROR_DIRECTORY) {
    220     using ::operator<<; // Pick the right operator<< below.
    221     DPLOG(ERROR) << "FindFirstChangeNotification failed for "
    222                  << dir.value();
    223     return false;
    224   }
    225 
    226   return true;
    227 }
    228 
    229 bool FilePathWatcherImpl::UpdateWatch() {
    230   if (handle_ != INVALID_HANDLE_VALUE)
    231     DestroyWatch();
    232 
    233   base::PlatformFileInfo file_info;
    234   if (file_util::GetFileInfo(target_, &file_info)) {
    235     last_modified_ = file_info.last_modified;
    236     first_notification_ = base::Time::Now();
    237   }
    238 
    239   // Start at the target and walk up the directory chain until we succesfully
    240   // create a watch handle in |handle_|. |child_dirs| keeps a stack of child
    241   // directories stripped from target, in reverse order.
    242   std::vector<FilePath> child_dirs;
    243   FilePath watched_path(target_);
    244   while (true) {
    245     if (!SetupWatchHandle(watched_path, recursive_watch_, &handle_))
    246       return false;
    247 
    248     // Break if a valid handle is returned. Try the parent directory otherwise.
    249     if (handle_ != INVALID_HANDLE_VALUE)
    250       break;
    251 
    252     // Abort if we hit the root directory.
    253     child_dirs.push_back(watched_path.BaseName());
    254     FilePath parent(watched_path.DirName());
    255     if (parent == watched_path) {
    256       DLOG(ERROR) << "Reached the root directory";
    257       return false;
    258     }
    259     watched_path = parent;
    260   }
    261 
    262   // At this point, handle_ is valid. However, the bottom-up search that the
    263   // above code performs races against directory creation. So try to walk back
    264   // down and see whether any children appeared in the mean time.
    265   while (!child_dirs.empty()) {
    266     watched_path = watched_path.Append(child_dirs.back());
    267     child_dirs.pop_back();
    268     HANDLE temp_handle = INVALID_HANDLE_VALUE;
    269     if (!SetupWatchHandle(watched_path, recursive_watch_, &temp_handle))
    270       return false;
    271     if (temp_handle == INVALID_HANDLE_VALUE)
    272       break;
    273     FindCloseChangeNotification(handle_);
    274     handle_ = temp_handle;
    275   }
    276 
    277   return true;
    278 }
    279 
    280 void FilePathWatcherImpl::DestroyWatch() {
    281   watcher_.StopWatching();
    282   FindCloseChangeNotification(handle_);
    283   handle_ = INVALID_HANDLE_VALUE;
    284 }
    285 
    286 }  // namespace
    287 
    288 FilePathWatcher::FilePathWatcher() {
    289   impl_ = new FilePathWatcherImpl();
    290 }
    291 
    292 }  // namespace base
    293