| // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/files/file_path_watcher.h" | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/files/file.h" | 
 | #include "base/files/file_path.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/logging.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/ref_counted.h" | 
 | #include "base/message_loop/message_loop.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "base/time/time.h" | 
 | #include "base/win/object_watcher.h" | 
 |  | 
 | namespace base { | 
 |  | 
 | namespace { | 
 |  | 
 | class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, | 
 |                             public base::win::ObjectWatcher::Delegate, | 
 |                             public MessageLoop::DestructionObserver { | 
 |  public: | 
 |   FilePathWatcherImpl() | 
 |       : handle_(INVALID_HANDLE_VALUE), | 
 |         recursive_watch_(false) {} | 
 |  | 
 |   // FilePathWatcher::PlatformDelegate overrides. | 
 |   bool Watch(const FilePath& path, | 
 |              bool recursive, | 
 |              const FilePathWatcher::Callback& callback) override; | 
 |   void Cancel() override; | 
 |  | 
 |   // Deletion of the FilePathWatcher will call Cancel() to dispose of this | 
 |   // object in the right thread. This also observes destruction of the required | 
 |   // cleanup thread, in case it quits before Cancel() is called. | 
 |   void WillDestroyCurrentMessageLoop() override; | 
 |  | 
 |   // Callback from MessageLoopForIO. | 
 |   void OnObjectSignaled(HANDLE object) override; | 
 |  | 
 |  private: | 
 |   ~FilePathWatcherImpl() override {} | 
 |  | 
 |   // Setup a watch handle for directory |dir|. Set |recursive| to true to watch | 
 |   // the directory sub trees. Returns true if no fatal error occurs. |handle| | 
 |   // will receive the handle value if |dir| is watchable, otherwise | 
 |   // INVALID_HANDLE_VALUE. | 
 |   static bool SetupWatchHandle(const FilePath& dir, | 
 |                                bool recursive, | 
 |                                HANDLE* handle) WARN_UNUSED_RESULT; | 
 |  | 
 |   // (Re-)Initialize the watch handle. | 
 |   bool UpdateWatch() WARN_UNUSED_RESULT; | 
 |  | 
 |   // Destroy the watch handle. | 
 |   void DestroyWatch(); | 
 |  | 
 |   // Cleans up and stops observing the |task_runner_| thread. | 
 |   void CancelOnMessageLoopThread() override; | 
 |  | 
 |   // Callback to notify upon changes. | 
 |   FilePathWatcher::Callback callback_; | 
 |  | 
 |   // Path we're supposed to watch (passed to callback). | 
 |   FilePath target_; | 
 |  | 
 |   // Handle for FindFirstChangeNotification. | 
 |   HANDLE handle_; | 
 |  | 
 |   // ObjectWatcher to watch handle_ for events. | 
 |   base::win::ObjectWatcher watcher_; | 
 |  | 
 |   // Set to true to watch the sub trees of the specified directory file path. | 
 |   bool recursive_watch_; | 
 |  | 
 |   // Keep track of the last modified time of the file.  We use nulltime | 
 |   // to represent the file not existing. | 
 |   Time last_modified_; | 
 |  | 
 |   // The time at which we processed the first notification with the | 
 |   // |last_modified_| time stamp. | 
 |   Time first_notification_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); | 
 | }; | 
 |  | 
 | bool FilePathWatcherImpl::Watch(const FilePath& path, | 
 |                                 bool recursive, | 
 |                                 const FilePathWatcher::Callback& callback) { | 
 |   DCHECK(target_.value().empty());  // Can only watch one path. | 
 |  | 
 |   set_task_runner(ThreadTaskRunnerHandle::Get()); | 
 |   callback_ = callback; | 
 |   target_ = path; | 
 |   recursive_watch_ = recursive; | 
 |   MessageLoop::current()->AddDestructionObserver(this); | 
 |  | 
 |   File::Info file_info; | 
 |   if (GetFileInfo(target_, &file_info)) { | 
 |     last_modified_ = file_info.last_modified; | 
 |     first_notification_ = Time::Now(); | 
 |   } | 
 |  | 
 |   if (!UpdateWatch()) | 
 |     return false; | 
 |  | 
 |   watcher_.StartWatchingOnce(handle_, this); | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void FilePathWatcherImpl::Cancel() { | 
 |   if (callback_.is_null()) { | 
 |     // Watch was never called, or the |task_runner_| has already quit. | 
 |     set_cancelled(); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Switch to the file thread if necessary so we can stop |watcher_|. | 
 |   if (!task_runner()->BelongsToCurrentThread()) { | 
 |     task_runner()->PostTask(FROM_HERE, Bind(&FilePathWatcher::CancelWatch, | 
 |                                             make_scoped_refptr(this))); | 
 |   } else { | 
 |     CancelOnMessageLoopThread(); | 
 |   } | 
 | } | 
 |  | 
 | void FilePathWatcherImpl::CancelOnMessageLoopThread() { | 
 |   DCHECK(task_runner()->BelongsToCurrentThread()); | 
 |   set_cancelled(); | 
 |  | 
 |   if (handle_ != INVALID_HANDLE_VALUE) | 
 |     DestroyWatch(); | 
 |  | 
 |   if (!callback_.is_null()) { | 
 |     MessageLoop::current()->RemoveDestructionObserver(this); | 
 |     callback_.Reset(); | 
 |   } | 
 | } | 
 |  | 
 | void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { | 
 |   CancelOnMessageLoopThread(); | 
 | } | 
 |  | 
 | void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) { | 
 |   DCHECK(object == handle_); | 
 |   // Make sure we stay alive through the body of this function. | 
 |   scoped_refptr<FilePathWatcherImpl> keep_alive(this); | 
 |  | 
 |   if (!UpdateWatch()) { | 
 |     callback_.Run(target_, true /* error */); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Check whether the event applies to |target_| and notify the callback. | 
 |   File::Info file_info; | 
 |   bool file_exists = GetFileInfo(target_, &file_info); | 
 |   if (recursive_watch_) { | 
 |     // Only the mtime of |target_| is tracked but in a recursive watch, | 
 |     // some other file or directory may have changed so all notifications | 
 |     // are passed through. It is possible to figure out which file changed | 
 |     // using ReadDirectoryChangesW() instead of FindFirstChangeNotification(), | 
 |     // but that function is quite complicated: | 
 |     // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html | 
 |     callback_.Run(target_, false); | 
 |   } else if (file_exists && (last_modified_.is_null() || | 
 |              last_modified_ != file_info.last_modified)) { | 
 |     last_modified_ = file_info.last_modified; | 
 |     first_notification_ = Time::Now(); | 
 |     callback_.Run(target_, false); | 
 |   } else if (file_exists && last_modified_ == file_info.last_modified && | 
 |              !first_notification_.is_null()) { | 
 |     // The target's last modification time is equal to what's on record. This | 
 |     // means that either an unrelated event occurred, or the target changed | 
 |     // again (file modification times only have a resolution of 1s). Comparing | 
 |     // file modification times against the wall clock is not reliable to find | 
 |     // out whether the change is recent, since this code might just run too | 
 |     // late. Moreover, there's no guarantee that file modification time and wall | 
 |     // clock times come from the same source. | 
 |     // | 
 |     // Instead, the time at which the first notification carrying the current | 
 |     // |last_notified_| time stamp is recorded. Later notifications that find | 
 |     // the same file modification time only need to be forwarded until wall | 
 |     // clock has advanced one second from the initial notification. After that | 
 |     // interval, client code is guaranteed to having seen the current revision | 
 |     // of the file. | 
 |     if (Time::Now() - first_notification_ > TimeDelta::FromSeconds(1)) { | 
 |       // Stop further notifications for this |last_modification_| time stamp. | 
 |       first_notification_ = Time(); | 
 |     } | 
 |     callback_.Run(target_, false); | 
 |   } else if (!file_exists && !last_modified_.is_null()) { | 
 |     last_modified_ = Time(); | 
 |     callback_.Run(target_, false); | 
 |   } | 
 |  | 
 |   // The watch may have been cancelled by the callback. | 
 |   if (handle_ != INVALID_HANDLE_VALUE) | 
 |     watcher_.StartWatchingOnce(handle_, this); | 
 | } | 
 |  | 
 | // static | 
 | bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir, | 
 |                                            bool recursive, | 
 |                                            HANDLE* handle) { | 
 |   *handle = FindFirstChangeNotification( | 
 |       dir.value().c_str(), | 
 |       recursive, | 
 |       FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE | | 
 |       FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | | 
 |       FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY); | 
 |   if (*handle != INVALID_HANDLE_VALUE) { | 
 |     // Make sure the handle we got points to an existing directory. It seems | 
 |     // that windows sometimes hands out watches to directories that are | 
 |     // about to go away, but doesn't sent notifications if that happens. | 
 |     if (!DirectoryExists(dir)) { | 
 |       FindCloseChangeNotification(*handle); | 
 |       *handle = INVALID_HANDLE_VALUE; | 
 |     } | 
 |     return true; | 
 |   } | 
 |  | 
 |   // If FindFirstChangeNotification failed because the target directory | 
 |   // doesn't exist, access is denied (happens if the file is already gone but | 
 |   // there are still handles open), or the target is not a directory, try the | 
 |   // immediate parent directory instead. | 
 |   DWORD error_code = GetLastError(); | 
 |   if (error_code != ERROR_FILE_NOT_FOUND && | 
 |       error_code != ERROR_PATH_NOT_FOUND && | 
 |       error_code != ERROR_ACCESS_DENIED && | 
 |       error_code != ERROR_SHARING_VIOLATION && | 
 |       error_code != ERROR_DIRECTORY) { | 
 |     DPLOG(ERROR) << "FindFirstChangeNotification failed for " | 
 |                  << dir.value(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | bool FilePathWatcherImpl::UpdateWatch() { | 
 |   if (handle_ != INVALID_HANDLE_VALUE) | 
 |     DestroyWatch(); | 
 |  | 
 |   // Start at the target and walk up the directory chain until we succesfully | 
 |   // create a watch handle in |handle_|. |child_dirs| keeps a stack of child | 
 |   // directories stripped from target, in reverse order. | 
 |   std::vector<FilePath> child_dirs; | 
 |   FilePath watched_path(target_); | 
 |   while (true) { | 
 |     if (!SetupWatchHandle(watched_path, recursive_watch_, &handle_)) | 
 |       return false; | 
 |  | 
 |     // Break if a valid handle is returned. Try the parent directory otherwise. | 
 |     if (handle_ != INVALID_HANDLE_VALUE) | 
 |       break; | 
 |  | 
 |     // Abort if we hit the root directory. | 
 |     child_dirs.push_back(watched_path.BaseName()); | 
 |     FilePath parent(watched_path.DirName()); | 
 |     if (parent == watched_path) { | 
 |       DLOG(ERROR) << "Reached the root directory"; | 
 |       return false; | 
 |     } | 
 |     watched_path = parent; | 
 |   } | 
 |  | 
 |   // At this point, handle_ is valid. However, the bottom-up search that the | 
 |   // above code performs races against directory creation. So try to walk back | 
 |   // down and see whether any children appeared in the mean time. | 
 |   while (!child_dirs.empty()) { | 
 |     watched_path = watched_path.Append(child_dirs.back()); | 
 |     child_dirs.pop_back(); | 
 |     HANDLE temp_handle = INVALID_HANDLE_VALUE; | 
 |     if (!SetupWatchHandle(watched_path, recursive_watch_, &temp_handle)) | 
 |       return false; | 
 |     if (temp_handle == INVALID_HANDLE_VALUE) | 
 |       break; | 
 |     FindCloseChangeNotification(handle_); | 
 |     handle_ = temp_handle; | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void FilePathWatcherImpl::DestroyWatch() { | 
 |   watcher_.StopWatching(); | 
 |   FindCloseChangeNotification(handle_); | 
 |   handle_ = INVALID_HANDLE_VALUE; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | FilePathWatcher::FilePathWatcher() { | 
 |   impl_ = new FilePathWatcherImpl(); | 
 | } | 
 |  | 
 | }  // namespace base |