mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-28 08:16:09 +03:00
291 lines
9.6 KiB
C++
291 lines
9.6 KiB
C++
// 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"
|
|
|
|
#include <windows.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()->RunsTasksInCurrentSequence());
|
|
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()->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 = 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_ = std::make_unique<FilePathWatcherImpl>();
|
|
}
|
|
|
|
} // namespace base
|