1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
|
// 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/ptr_util.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()
: handle_(INVALID_HANDLE_VALUE),
recursive_watch_(false) {}
~FilePathWatcherImpl() override;
// FilePathWatcher::PlatformDelegate:
bool Watch(const FilePath& path,
bool recursive,
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.
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();
// 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.
bool* was_deleted_ptr_ = nullptr;
// 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);
};
FilePathWatcherImpl::~FilePathWatcherImpl() {
DCHECK(!task_runner() || task_runner()->RunsTasksOnCurrentThread());
if (was_deleted_ptr_)
*was_deleted_ptr_ = true;
}
bool FilePathWatcherImpl::Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) {
DCHECK(target_.value().empty()); // Can only watch one path.
set_task_runner(SequencedTaskRunnerHandle::Get());
callback_ = callback;
target_ = path;
recursive_watch_ = recursive;
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;
}
DCHECK(task_runner()->RunsTasksOnCurrentThread());
set_cancelled();
if (handle_ != INVALID_HANDLE_VALUE)
DestroyWatch();
callback_.Reset();
}
void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
DCHECK(task_runner()->RunsTasksOnCurrentThread());
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 = 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 (!was_deleted) {
watcher_.StartWatchingOnce(handle_, this);
was_deleted_ptr_ = nullptr;
}
}
// 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() {
sequence_checker_.DetachFromSequence();
impl_ = MakeUnique<FilePathWatcherImpl>();
}
} // namespace base
|