123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- // 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 <windows.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/memory/ptr_util.h"
- #include "base/memory/raw_ptr.h"
- #include "base/strings/string_util.h"
- #include "base/threading/scoped_blocking_call.h"
- #include "base/threading/sequenced_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:
- FilePathWatcherImpl() = default;
- FilePathWatcherImpl(const FilePathWatcherImpl&) = delete;
- FilePathWatcherImpl& operator=(const FilePathWatcherImpl&) = delete;
- ~FilePathWatcherImpl() override;
- // FilePathWatcher::PlatformDelegate:
- bool Watch(const FilePath& path,
- Type type,
- const FilePathWatcher::Callback& callback) override;
- void Cancel() override;
- // base::win::ObjectWatcher::Delegate:
- void OnObjectSignaled(HANDLE object) override;
- private:
- // 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.
- [[nodiscard]] static bool SetupWatchHandle(const FilePath& dir,
- bool recursive,
- HANDLE* handle);
- // (Re-)Initialize the watch handle.
- [[nodiscard]] bool UpdateWatch();
- // Destroy the watch handle.
- void DestroyWatch();
- // Callback to notify upon changes.
- FilePathWatcher::Callback callback_;
- // Path we're supposed to watch (passed to callback).
- FilePath target_;
- // Set to true in the destructor.
- raw_ptr<bool> was_deleted_ptr_ = nullptr;
- // Handle for FindFirstChangeNotification.
- HANDLE handle_ = INVALID_HANDLE_VALUE;
- // ObjectWatcher to watch handle_ for events.
- base::win::ObjectWatcher watcher_;
- // The type of watch requested.
- Type type_ = Type::kNonRecursive;
- // 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_;
- };
- FilePathWatcherImpl::~FilePathWatcherImpl() {
- DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
- if (was_deleted_ptr_)
- *was_deleted_ptr_ = true;
- }
- bool FilePathWatcherImpl::Watch(const FilePath& path,
- Type type,
- const FilePathWatcher::Callback& callback) {
- DCHECK(target_.value().empty()); // Can only watch one path.
- set_task_runner(SequencedTaskRunnerHandle::Get());
- callback_ = callback;
- target_ = path;
- type_ = type;
- File::Info file_info;
- ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
- 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;
- }
- DCHECK(task_runner()->RunsTasksInCurrentSequence());
- set_cancelled();
- if (handle_ != INVALID_HANDLE_VALUE)
- DestroyWatch();
- callback_.Reset();
- }
- void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
- DCHECK(task_runner()->RunsTasksInCurrentSequence());
- DCHECK_EQ(object, handle_);
- DCHECK(!was_deleted_ptr_);
- bool was_deleted = false;
- was_deleted_ptr_ = &was_deleted;
- 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 = false;
- {
- ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
- file_exists = GetFileInfo(target_, &file_info);
- }
- if (type_ == Type::kRecursive) {
- // 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_ > Seconds(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 (!was_deleted) {
- watcher_.StartWatchingOnce(handle_, this);
- was_deleted_ptr_ = nullptr;
- }
- }
- // static
- bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
- bool recursive,
- HANDLE* handle) {
- ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
- *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();
- ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
- // 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, type_ == Type::kRecursive, &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, type_ == Type::kRecursive,
- &temp_handle)) {
- return false;
- }
- if (temp_handle == INVALID_HANDLE_VALUE)
- break;
- FindCloseChangeNotification(handle_);
- handle_ = temp_handle;
- }
- return true;
- }
- void FilePathWatcherImpl::DestroyWatch() {
- watcher_.StopWatching();
- ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
- FindCloseChangeNotification(handle_);
- handle_ = INVALID_HANDLE_VALUE;
- }
- } // namespace
- FilePathWatcher::FilePathWatcher() {
- sequence_checker_.DetachFromSequence();
- impl_ = std::make_unique<FilePathWatcherImpl>();
- }
- } // namespace base
|