file_path_watcher_win.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. #include "base/files/file_path_watcher.h"
  5. #include <windows.h>
  6. #include "base/bind.h"
  7. #include "base/files/file.h"
  8. #include "base/files/file_path.h"
  9. #include "base/files/file_util.h"
  10. #include "base/logging.h"
  11. #include "base/memory/ptr_util.h"
  12. #include "base/memory/raw_ptr.h"
  13. #include "base/strings/string_util.h"
  14. #include "base/threading/scoped_blocking_call.h"
  15. #include "base/threading/sequenced_task_runner_handle.h"
  16. #include "base/time/time.h"
  17. #include "base/win/object_watcher.h"
  18. namespace base {
  19. namespace {
  20. class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
  21. public base::win::ObjectWatcher::Delegate {
  22. public:
  23. FilePathWatcherImpl() = default;
  24. FilePathWatcherImpl(const FilePathWatcherImpl&) = delete;
  25. FilePathWatcherImpl& operator=(const FilePathWatcherImpl&) = delete;
  26. ~FilePathWatcherImpl() override;
  27. // FilePathWatcher::PlatformDelegate:
  28. bool Watch(const FilePath& path,
  29. Type type,
  30. const FilePathWatcher::Callback& callback) override;
  31. void Cancel() override;
  32. // base::win::ObjectWatcher::Delegate:
  33. void OnObjectSignaled(HANDLE object) override;
  34. private:
  35. // Setup a watch handle for directory |dir|. Set |recursive| to true to watch
  36. // the directory sub trees. Returns true if no fatal error occurs. |handle|
  37. // will receive the handle value if |dir| is watchable, otherwise
  38. // INVALID_HANDLE_VALUE.
  39. [[nodiscard]] static bool SetupWatchHandle(const FilePath& dir,
  40. bool recursive,
  41. HANDLE* handle);
  42. // (Re-)Initialize the watch handle.
  43. [[nodiscard]] bool UpdateWatch();
  44. // Destroy the watch handle.
  45. void DestroyWatch();
  46. // Callback to notify upon changes.
  47. FilePathWatcher::Callback callback_;
  48. // Path we're supposed to watch (passed to callback).
  49. FilePath target_;
  50. // Set to true in the destructor.
  51. raw_ptr<bool> was_deleted_ptr_ = nullptr;
  52. // Handle for FindFirstChangeNotification.
  53. HANDLE handle_ = INVALID_HANDLE_VALUE;
  54. // ObjectWatcher to watch handle_ for events.
  55. base::win::ObjectWatcher watcher_;
  56. // The type of watch requested.
  57. Type type_ = Type::kNonRecursive;
  58. // Keep track of the last modified time of the file. We use nulltime
  59. // to represent the file not existing.
  60. Time last_modified_;
  61. // The time at which we processed the first notification with the
  62. // |last_modified_| time stamp.
  63. Time first_notification_;
  64. };
  65. FilePathWatcherImpl::~FilePathWatcherImpl() {
  66. DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
  67. if (was_deleted_ptr_)
  68. *was_deleted_ptr_ = true;
  69. }
  70. bool FilePathWatcherImpl::Watch(const FilePath& path,
  71. Type type,
  72. const FilePathWatcher::Callback& callback) {
  73. DCHECK(target_.value().empty()); // Can only watch one path.
  74. set_task_runner(SequencedTaskRunnerHandle::Get());
  75. callback_ = callback;
  76. target_ = path;
  77. type_ = type;
  78. File::Info file_info;
  79. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  80. if (GetFileInfo(target_, &file_info)) {
  81. last_modified_ = file_info.last_modified;
  82. first_notification_ = Time::Now();
  83. }
  84. if (!UpdateWatch())
  85. return false;
  86. watcher_.StartWatchingOnce(handle_, this);
  87. return true;
  88. }
  89. void FilePathWatcherImpl::Cancel() {
  90. if (callback_.is_null()) {
  91. // Watch was never called, or the |task_runner_| has already quit.
  92. set_cancelled();
  93. return;
  94. }
  95. DCHECK(task_runner()->RunsTasksInCurrentSequence());
  96. set_cancelled();
  97. if (handle_ != INVALID_HANDLE_VALUE)
  98. DestroyWatch();
  99. callback_.Reset();
  100. }
  101. void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) {
  102. DCHECK(task_runner()->RunsTasksInCurrentSequence());
  103. DCHECK_EQ(object, handle_);
  104. DCHECK(!was_deleted_ptr_);
  105. bool was_deleted = false;
  106. was_deleted_ptr_ = &was_deleted;
  107. if (!UpdateWatch()) {
  108. callback_.Run(target_, true /* error */);
  109. return;
  110. }
  111. // Check whether the event applies to |target_| and notify the callback.
  112. File::Info file_info;
  113. bool file_exists = false;
  114. {
  115. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  116. file_exists = GetFileInfo(target_, &file_info);
  117. }
  118. if (type_ == Type::kRecursive) {
  119. // Only the mtime of |target_| is tracked but in a recursive watch,
  120. // some other file or directory may have changed so all notifications
  121. // are passed through. It is possible to figure out which file changed
  122. // using ReadDirectoryChangesW() instead of FindFirstChangeNotification(),
  123. // but that function is quite complicated:
  124. // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html
  125. callback_.Run(target_, false);
  126. } else if (file_exists && (last_modified_.is_null() ||
  127. last_modified_ != file_info.last_modified)) {
  128. last_modified_ = file_info.last_modified;
  129. first_notification_ = Time::Now();
  130. callback_.Run(target_, false);
  131. } else if (file_exists && last_modified_ == file_info.last_modified &&
  132. !first_notification_.is_null()) {
  133. // The target's last modification time is equal to what's on record. This
  134. // means that either an unrelated event occurred, or the target changed
  135. // again (file modification times only have a resolution of 1s). Comparing
  136. // file modification times against the wall clock is not reliable to find
  137. // out whether the change is recent, since this code might just run too
  138. // late. Moreover, there's no guarantee that file modification time and wall
  139. // clock times come from the same source.
  140. //
  141. // Instead, the time at which the first notification carrying the current
  142. // |last_notified_| time stamp is recorded. Later notifications that find
  143. // the same file modification time only need to be forwarded until wall
  144. // clock has advanced one second from the initial notification. After that
  145. // interval, client code is guaranteed to having seen the current revision
  146. // of the file.
  147. if (Time::Now() - first_notification_ > Seconds(1)) {
  148. // Stop further notifications for this |last_modification_| time stamp.
  149. first_notification_ = Time();
  150. }
  151. callback_.Run(target_, false);
  152. } else if (!file_exists && !last_modified_.is_null()) {
  153. last_modified_ = Time();
  154. callback_.Run(target_, false);
  155. }
  156. // The watch may have been cancelled by the callback.
  157. if (!was_deleted) {
  158. watcher_.StartWatchingOnce(handle_, this);
  159. was_deleted_ptr_ = nullptr;
  160. }
  161. }
  162. // static
  163. bool FilePathWatcherImpl::SetupWatchHandle(const FilePath& dir,
  164. bool recursive,
  165. HANDLE* handle) {
  166. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  167. *handle = FindFirstChangeNotification(
  168. dir.value().c_str(), recursive,
  169. FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
  170. FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
  171. FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY);
  172. if (*handle != INVALID_HANDLE_VALUE) {
  173. // Make sure the handle we got points to an existing directory. It seems
  174. // that windows sometimes hands out watches to directories that are
  175. // about to go away, but doesn't sent notifications if that happens.
  176. if (!DirectoryExists(dir)) {
  177. FindCloseChangeNotification(*handle);
  178. *handle = INVALID_HANDLE_VALUE;
  179. }
  180. return true;
  181. }
  182. // If FindFirstChangeNotification failed because the target directory
  183. // doesn't exist, access is denied (happens if the file is already gone but
  184. // there are still handles open), or the target is not a directory, try the
  185. // immediate parent directory instead.
  186. DWORD error_code = GetLastError();
  187. if (error_code != ERROR_FILE_NOT_FOUND &&
  188. error_code != ERROR_PATH_NOT_FOUND &&
  189. error_code != ERROR_ACCESS_DENIED &&
  190. error_code != ERROR_SHARING_VIOLATION &&
  191. error_code != ERROR_DIRECTORY) {
  192. DPLOG(ERROR) << "FindFirstChangeNotification failed for "
  193. << dir.value();
  194. return false;
  195. }
  196. return true;
  197. }
  198. bool FilePathWatcherImpl::UpdateWatch() {
  199. if (handle_ != INVALID_HANDLE_VALUE)
  200. DestroyWatch();
  201. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  202. // Start at the target and walk up the directory chain until we succesfully
  203. // create a watch handle in |handle_|. |child_dirs| keeps a stack of child
  204. // directories stripped from target, in reverse order.
  205. std::vector<FilePath> child_dirs;
  206. FilePath watched_path(target_);
  207. while (true) {
  208. if (!SetupWatchHandle(watched_path, type_ == Type::kRecursive, &handle_))
  209. return false;
  210. // Break if a valid handle is returned. Try the parent directory otherwise.
  211. if (handle_ != INVALID_HANDLE_VALUE)
  212. break;
  213. // Abort if we hit the root directory.
  214. child_dirs.push_back(watched_path.BaseName());
  215. FilePath parent(watched_path.DirName());
  216. if (parent == watched_path) {
  217. DLOG(ERROR) << "Reached the root directory";
  218. return false;
  219. }
  220. watched_path = parent;
  221. }
  222. // At this point, handle_ is valid. However, the bottom-up search that the
  223. // above code performs races against directory creation. So try to walk back
  224. // down and see whether any children appeared in the mean time.
  225. while (!child_dirs.empty()) {
  226. watched_path = watched_path.Append(child_dirs.back());
  227. child_dirs.pop_back();
  228. HANDLE temp_handle = INVALID_HANDLE_VALUE;
  229. if (!SetupWatchHandle(watched_path, type_ == Type::kRecursive,
  230. &temp_handle)) {
  231. return false;
  232. }
  233. if (temp_handle == INVALID_HANDLE_VALUE)
  234. break;
  235. FindCloseChangeNotification(handle_);
  236. handle_ = temp_handle;
  237. }
  238. return true;
  239. }
  240. void FilePathWatcherImpl::DestroyWatch() {
  241. watcher_.StopWatching();
  242. ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
  243. FindCloseChangeNotification(handle_);
  244. handle_ = INVALID_HANDLE_VALUE;
  245. }
  246. } // namespace
  247. FilePathWatcher::FilePathWatcher() {
  248. sequence_checker_.DetachFromSequence();
  249. impl_ = std::make_unique<FilePathWatcherImpl>();
  250. }
  251. } // namespace base