1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/files/file_path_watcher.h"
6
7 #include <windows.h>
8
9 #include <winnt.h>
10
11 #include <cstdint>
12 #include <map>
13 #include <memory>
14 #include <tuple>
15 #include <utility>
16
17 #include "base/auto_reset.h"
18 #include "base/containers/heap_array.h"
19 #include "base/containers/span.h"
20 #include "base/files/file.h"
21 #include "base/files/file_path.h"
22 #include "base/files/file_util.h"
23 #include "base/functional/bind.h"
24 #include "base/functional/callback_helpers.h"
25 #include "base/logging.h"
26 #include "base/memory/ptr_util.h"
27 #include "base/memory/raw_ptr.h"
28 #include "base/memory/weak_ptr.h"
29 #include "base/no_destructor.h"
30 #include "base/strings/string_util.h"
31 #include "base/synchronization/lock.h"
32 #include "base/task/sequenced_task_runner.h"
33 #include "base/threading/platform_thread.h"
34 #include "base/threading/scoped_blocking_call.h"
35 #include "base/time/time.h"
36 #include "base/types/expected.h"
37 #include "base/types/id_type.h"
38 #include "base/win/object_watcher.h"
39 #include "base/win/scoped_handle.h"
40 #include "base/win/windows_types.h"
41
42 namespace base {
43 namespace {
44
45 enum class CreateFileHandleError {
46 // When watching a path, the path (or some of its ancestor directories) might
47 // not exist yet. Failure to create a watcher because the path doesn't exist
48 // (or is not a directory) should not be considered fatal, since the watcher
49 // implementation can simply try again one directory level above.
50 kNonFatal,
51 kFatal,
52 };
53
54 base::expected<base::win::ScopedHandle, CreateFileHandleError>
CreateDirectoryHandle(const FilePath & dir)55 CreateDirectoryHandle(const FilePath& dir) {
56 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
57
58 base::win::ScopedHandle handle(::CreateFileW(
59 dir.value().c_str(), FILE_LIST_DIRECTORY,
60 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
61 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
62 nullptr));
63
64 if (handle.is_valid()) {
65 File::Info file_info;
66 if (!GetFileInfo(dir, &file_info)) {
67 // Windows sometimes hands out handles to files that are about to go away.
68 return base::unexpected(CreateFileHandleError::kNonFatal);
69 }
70
71 // Only return the handle if its a directory.
72 if (!file_info.is_directory) {
73 return base::unexpected(CreateFileHandleError::kNonFatal);
74 }
75
76 return handle;
77 }
78
79 switch (::GetLastError()) {
80 case ERROR_FILE_NOT_FOUND:
81 case ERROR_PATH_NOT_FOUND:
82 case ERROR_ACCESS_DENIED:
83 case ERROR_SHARING_VIOLATION:
84 case ERROR_DIRECTORY:
85 // Failure to create the handle is ok if the target directory doesn't
86 // exist, access is denied (happens if the file is already gone but there
87 // are still handles open), or the target is not a directory.
88 return base::unexpected(CreateFileHandleError::kNonFatal);
89 default:
90 DPLOG(ERROR) << "CreateFileW failed for " << dir.value();
91 return base::unexpected(CreateFileHandleError::kFatal);
92 }
93 }
94
95 class FilePathWatcherImpl;
96
97 class CompletionIOPortThread final : public PlatformThread::Delegate {
98 public:
99 using WatcherEntryId = base::IdTypeU64<class WatcherEntryIdTag>;
100
101 CompletionIOPortThread(const CompletionIOPortThread&) = delete;
102 CompletionIOPortThread& operator=(const CompletionIOPortThread&) = delete;
103
Get()104 static CompletionIOPortThread* Get() {
105 static NoDestructor<CompletionIOPortThread> io_thread;
106 return io_thread.get();
107 }
108
109 // Thread safe.
110 std::optional<WatcherEntryId> AddWatcher(
111 FilePathWatcherImpl& watcher,
112 base::win::ScopedHandle watched_handle,
113 base::FilePath watched_path);
114
115 // Thread safe.
116 void RemoveWatcher(WatcherEntryId watcher_id);
117
118 Lock& GetLockForTest(); // IN-TEST
119
120 private:
121 friend NoDestructor<CompletionIOPortThread>;
122
123 // The max size of a file notification assuming that long paths aren't
124 // enabled.
125 static constexpr size_t kMaxFileNotifySize =
126 sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH;
127
128 // Choose a decent number of notifications to support that isn't too large.
129 // Whatever we choose will be doubled by the kernel's copy of the buffer.
130 static constexpr int kBufferNotificationCount = 20;
131 static constexpr size_t kWatchBufferSizeBytes =
132 kBufferNotificationCount * kMaxFileNotifySize;
133
134 // Must be DWORD aligned.
135 static_assert(kWatchBufferSizeBytes % sizeof(DWORD) == 0);
136 // Must be less than the max network packet size for network drives. See
137 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks.
138 static_assert(kWatchBufferSizeBytes <= 64 * 1024);
139
140 struct WatcherEntry {
WatcherEntrybase::__anon1ff05bd10111::CompletionIOPortThread::WatcherEntry141 WatcherEntry(base::WeakPtr<FilePathWatcherImpl> watcher_weak_ptr,
142 scoped_refptr<SequencedTaskRunner> task_runner,
143 base::win::ScopedHandle watched_handle,
144 base::FilePath watched_path)
145 : watcher_weak_ptr(std::move(watcher_weak_ptr)),
146 task_runner(std::move(task_runner)),
147 watched_handle(std::move(watched_handle)),
148 watched_path(std::move(watched_path)) {}
149 ~WatcherEntry() = default;
150
151 // Delete copy and move constructors since `buffer` should not be copied or
152 // moved.
153 WatcherEntry(const WatcherEntry&) = delete;
154 WatcherEntry& operator=(const WatcherEntry&) = delete;
155 WatcherEntry(WatcherEntry&&) = delete;
156 WatcherEntry& operator=(WatcherEntry&&) = delete;
157
158 base::WeakPtr<FilePathWatcherImpl> watcher_weak_ptr;
159 scoped_refptr<SequencedTaskRunner> task_runner;
160
161 base::win::ScopedHandle watched_handle;
162 base::FilePath watched_path;
163
164 alignas(DWORD) uint8_t buffer[kWatchBufferSizeBytes];
165 };
166
167 OVERLAPPED overlapped = {};
168
169 CompletionIOPortThread();
170
171 ~CompletionIOPortThread() override = default;
172
173 void ThreadMain() override;
174
175 [[nodiscard]] DWORD SetupWatch(WatcherEntry& watcher_entry);
176
177 Lock watchers_lock_;
178
179 WatcherEntryId::Generator watcher_id_generator_ GUARDED_BY(watchers_lock_);
180
181 std::map<WatcherEntryId, WatcherEntry> watcher_entries_
182 GUARDED_BY(watchers_lock_);
183
184 // It is safe to access `io_completion_port_` on any thread without locks
185 // since:
186 // - Windows Handles are thread safe
187 // - `io_completion_port_` is set once in the constructor of this class
188 // - This class is never destroyed.
189 win::ScopedHandle io_completion_port_{
190 ::CreateIoCompletionPort(INVALID_HANDLE_VALUE,
191 nullptr,
192 reinterpret_cast<ULONG_PTR>(nullptr),
193 1)};
194 };
195
196 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
197 public:
198 FilePathWatcherImpl() = default;
199 FilePathWatcherImpl(const FilePathWatcherImpl&) = delete;
200 FilePathWatcherImpl& operator=(const FilePathWatcherImpl&) = delete;
201 ~FilePathWatcherImpl() override;
202
203 // FilePathWatcher::PlatformDelegate implementation:
204 bool Watch(const FilePath& path,
205 Type type,
206 const FilePathWatcher::Callback& callback) override;
207
208 // FilePathWatcher::PlatformDelegate implementation:
209 bool WatchWithOptions(const FilePath& path,
210 const WatchOptions& flags,
211 const FilePathWatcher::Callback& callback) override;
212
213 // FilePathWatcher::PlatformDelegate implementation:
214 bool WatchWithChangeInfo(
215 const FilePath& path,
216 const WatchOptions& options,
217 const FilePathWatcher::CallbackWithChangeInfo& callback) override;
218
219 void Cancel() override;
220
221 Lock& GetWatchThreadLockForTest() override; // IN-TEST
222
223 private:
224 friend CompletionIOPortThread;
225
226 // Sets up a watch handle for either `target_` or one of its ancestors.
227 // Returns true on success.
228 [[nodiscard]] bool SetupWatchHandleForTarget();
229
230 void CloseWatchHandle();
231
232 void BufferOverflowed();
233
234 void WatchedDirectoryDeleted(base::FilePath watched_path,
235 base::HeapArray<uint8_t> notification_batch);
236
237 void ProcessNotificationBatch(base::FilePath watched_path,
238 base::HeapArray<uint8_t> notification_batch);
239
240 // Callback to notify upon changes.
241 FilePathWatcher::CallbackWithChangeInfo callback_;
242
243 // Path we're supposed to watch (passed to callback).
244 FilePath target_;
245
246 std::optional<CompletionIOPortThread::WatcherEntryId> watcher_id_;
247
248 // The type of watch requested.
249 Type type_ = Type::kNonRecursive;
250
251 bool target_exists_ = false;
252
253 WeakPtrFactory<FilePathWatcherImpl> weak_factory_{this};
254 };
255
CompletionIOPortThread()256 CompletionIOPortThread::CompletionIOPortThread() {
257 PlatformThread::CreateNonJoinable(0, this);
258 }
259
SetupWatch(WatcherEntry & watcher_entry)260 DWORD CompletionIOPortThread::SetupWatch(WatcherEntry& watcher_entry) {
261 bool success = ReadDirectoryChangesW(
262 watcher_entry.watched_handle.get(), &watcher_entry.buffer,
263 kWatchBufferSizeBytes, /*bWatchSubtree=*/true,
264 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE |
265 FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME |
266 FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SECURITY,
267 nullptr, &overlapped, nullptr);
268 if (!success) {
269 return ::GetLastError();
270 }
271 return ERROR_SUCCESS;
272 }
273
274 std::optional<CompletionIOPortThread::WatcherEntryId>
AddWatcher(FilePathWatcherImpl & watcher,base::win::ScopedHandle watched_handle,base::FilePath watched_path)275 CompletionIOPortThread::AddWatcher(FilePathWatcherImpl& watcher,
276 base::win::ScopedHandle watched_handle,
277 base::FilePath watched_path) {
278 AutoLock auto_lock(watchers_lock_);
279
280 WatcherEntryId watcher_id = watcher_id_generator_.GenerateNextId();
281 HANDLE port = ::CreateIoCompletionPort(
282 watched_handle.get(), io_completion_port_.get(),
283 static_cast<ULONG_PTR>(watcher_id.GetUnsafeValue()), 1);
284 if (port == nullptr) {
285 return std::nullopt;
286 }
287
288 auto [it, inserted] = watcher_entries_.emplace(
289 std::piecewise_construct, std::forward_as_tuple(watcher_id),
290 std::forward_as_tuple(watcher.weak_factory_.GetWeakPtr(),
291 watcher.task_runner(), std::move(watched_handle),
292 std::move(watched_path)));
293
294 CHECK(inserted);
295
296 DWORD result = SetupWatch(it->second);
297
298 if (result != ERROR_SUCCESS) {
299 watcher_entries_.erase(it);
300 return std::nullopt;
301 }
302
303 return watcher_id;
304 }
305
RemoveWatcher(WatcherEntryId watcher_id)306 void CompletionIOPortThread::RemoveWatcher(WatcherEntryId watcher_id) {
307 HANDLE raw_watched_handle;
308 {
309 AutoLock auto_lock(watchers_lock_);
310
311 auto it = watcher_entries_.find(watcher_id);
312 CHECK(it != watcher_entries_.end());
313
314 auto& watched_handle = it->second.watched_handle;
315 CHECK(watched_handle.is_valid());
316 raw_watched_handle = watched_handle.release();
317 }
318
319 {
320 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
321
322 // `raw_watched_handle` being closed indicates to `ThreadMain` that this
323 // entry needs to be removed from `watcher_entries_` once the kernel
324 // indicates it is safe too.
325 ::CloseHandle(raw_watched_handle);
326 }
327 }
328
GetLockForTest()329 Lock& CompletionIOPortThread::GetLockForTest() {
330 return watchers_lock_;
331 }
332
ThreadMain()333 void CompletionIOPortThread::ThreadMain() {
334 while (true) {
335 DWORD bytes_transferred;
336 ULONG_PTR key = reinterpret_cast<ULONG_PTR>(nullptr);
337 OVERLAPPED* overlapped_out = nullptr;
338
339 BOOL io_port_result = ::GetQueuedCompletionStatus(
340 io_completion_port_.get(), &bytes_transferred, &key, &overlapped_out,
341 INFINITE);
342 CHECK(&overlapped == overlapped_out);
343
344 DWORD io_port_error = ERROR_SUCCESS;
345 if (io_port_result == FALSE) {
346 io_port_error = ::GetLastError();
347 // `ERROR_ACCESS_DENIED` should be the only error we can receive.
348 CHECK_EQ(io_port_error, static_cast<DWORD>(ERROR_ACCESS_DENIED));
349 }
350
351 AutoLock auto_lock(watchers_lock_);
352
353 WatcherEntryId watcher_id = WatcherEntryId::FromUnsafeValue(key);
354
355 auto watcher_entry_it = watcher_entries_.find(watcher_id);
356
357 CHECK(watcher_entry_it != watcher_entries_.end())
358 << "WatcherEntryId not in map";
359
360 auto& watcher_entry = watcher_entry_it->second;
361 auto& [watcher_weak_ptr, task_runner, watched_handle, watched_path,
362 buffer] = watcher_entry;
363
364 if (!watched_handle.is_valid()) {
365 // After the handle has been closed, a final notification will be sent
366 // with `bytes_transferred` equal to 0. It is safe to destroy the watcher
367 // now.
368 if (bytes_transferred == 0) {
369 // `watcher_entry` and all the local refs to its members will be
370 // dangling after this call.
371 watcher_entries_.erase(watcher_entry_it);
372 }
373 continue;
374 }
375
376 // `GetQueuedCompletionStatus` can fail with `ERROR_ACCESS_DENIED` when the
377 // watched directory is deleted.
378 if (io_port_result == FALSE) {
379 CHECK(bytes_transferred == 0);
380
381 task_runner->PostTask(
382 FROM_HERE,
383 base::BindOnce(&FilePathWatcherImpl::WatchedDirectoryDeleted,
384 watcher_weak_ptr, watched_path,
385 base::HeapArray<uint8_t>()));
386 continue;
387 }
388
389 base::HeapArray<uint8_t> notification_batch;
390 if (bytes_transferred > 0) {
391 notification_batch = base::HeapArray<uint8_t>::CopiedFrom(
392 base::span<uint8_t>(buffer).first(bytes_transferred));
393 }
394
395 // Let the kernel know that we're ready to receive change events again in
396 // the `watcher_entry`'s `buffer`.
397 //
398 // We do this as soon as possible, so that not too many events are received
399 // in the next batch. Too many events can cause a buffer overflow.
400 DWORD result = SetupWatch(watcher_entry);
401
402 // `SetupWatch` can fail if the watched directory was deleted before
403 // `SetupWatch` was called but after `GetQueuedCompletionStatus` returned.
404 if (result != ERROR_SUCCESS) {
405 CHECK_EQ(result, static_cast<DWORD>(ERROR_ACCESS_DENIED));
406 task_runner->PostTask(
407 FROM_HERE,
408 base::BindOnce(&FilePathWatcherImpl::WatchedDirectoryDeleted,
409 watcher_weak_ptr, watched_path,
410 std::move(notification_batch)));
411 continue;
412 }
413
414 // `GetQueuedCompletionStatus` succeeds with zero bytes transferred if there
415 // is a buffer overflow.
416 if (bytes_transferred == 0) {
417 task_runner->PostTask(
418 FROM_HERE, base::BindOnce(&FilePathWatcherImpl::BufferOverflowed,
419 watcher_weak_ptr));
420 continue;
421 }
422
423 task_runner->PostTask(
424 FROM_HERE,
425 base::BindOnce(&FilePathWatcherImpl::ProcessNotificationBatch,
426 watcher_weak_ptr, watched_path,
427 std::move(notification_batch)));
428 }
429 }
430
~FilePathWatcherImpl()431 FilePathWatcherImpl::~FilePathWatcherImpl() {
432 DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
433 }
434
Watch(const FilePath & path,Type type,const FilePathWatcher::Callback & callback)435 bool FilePathWatcherImpl::Watch(const FilePath& path,
436 Type type,
437 const FilePathWatcher::Callback& callback) {
438 return WatchWithChangeInfo(
439 path, WatchOptions{.type = type},
440 base::IgnoreArgs<const FilePathWatcher::ChangeInfo&>(
441 base::BindRepeating(std::move(callback))));
442 }
443
WatchWithOptions(const FilePath & path,const WatchOptions & options,const FilePathWatcher::Callback & callback)444 bool FilePathWatcherImpl::WatchWithOptions(
445 const FilePath& path,
446 const WatchOptions& options,
447 const FilePathWatcher::Callback& callback) {
448 return WatchWithChangeInfo(
449 path, options,
450 base::IgnoreArgs<const FilePathWatcher::ChangeInfo&>(
451 base::BindRepeating(std::move(callback))));
452 }
453
WatchWithChangeInfo(const FilePath & path,const WatchOptions & options,const FilePathWatcher::CallbackWithChangeInfo & callback)454 bool FilePathWatcherImpl::WatchWithChangeInfo(
455 const FilePath& path,
456 const WatchOptions& options,
457 const FilePathWatcher::CallbackWithChangeInfo& callback) {
458 DCHECK(target_.empty()); // Can only watch one path.
459
460 set_task_runner(SequencedTaskRunner::GetCurrentDefault());
461 callback_ = callback;
462 target_ = path;
463 type_ = options.type;
464
465 File::Info file_info;
466 target_exists_ = GetFileInfo(target_, &file_info);
467
468 return SetupWatchHandleForTarget();
469 }
470
Cancel()471 void FilePathWatcherImpl::Cancel() {
472 set_cancelled();
473
474 if (callback_.is_null()) {
475 // Watch was never called, or the `task_runner_` has already quit.
476 return;
477 }
478
479 DCHECK(task_runner()->RunsTasksInCurrentSequence());
480
481 CloseWatchHandle();
482
483 callback_.Reset();
484 }
485
GetWatchThreadLockForTest()486 Lock& FilePathWatcherImpl::GetWatchThreadLockForTest() {
487 return CompletionIOPortThread::Get()->GetLockForTest(); // IN-TEST
488 }
489
BufferOverflowed()490 void FilePathWatcherImpl::BufferOverflowed() {
491 // `this` may be deleted after `callback_` is run.
492 callback_.Run(FilePathWatcher::ChangeInfo(), target_, /*error=*/false);
493 }
494
WatchedDirectoryDeleted(base::FilePath watched_path,base::HeapArray<uint8_t> notification_batch)495 void FilePathWatcherImpl::WatchedDirectoryDeleted(
496 base::FilePath watched_path,
497 base::HeapArray<uint8_t> notification_batch) {
498 if (!SetupWatchHandleForTarget()) {
499 // `this` may be deleted after `callback_` is run.
500 callback_.Run(FilePathWatcher::ChangeInfo(), target_, /*error=*/true);
501 return;
502 }
503
504 if (!notification_batch.empty()) {
505 auto self = weak_factory_.GetWeakPtr();
506 // `ProcessNotificationBatch` may delete `this`.
507 ProcessNotificationBatch(std::move(watched_path),
508 std::move(notification_batch));
509 if (!self) {
510 return;
511 }
512 }
513
514 bool target_was_deleted = target_exists_ || watched_path == target_;
515 if (target_was_deleted) {
516 // `this` may be deleted after `callback_` is run.
517 callback_.Run(FilePathWatcher::ChangeInfo(), target_, /*error=*/false);
518 }
519 }
520
ProcessNotificationBatch(base::FilePath watched_path,base::HeapArray<uint8_t> notification_batch)521 void FilePathWatcherImpl::ProcessNotificationBatch(
522 base::FilePath watched_path,
523 base::HeapArray<uint8_t> notification_batch) {
524 DCHECK(task_runner()->RunsTasksInCurrentSequence());
525 CHECK(!notification_batch.empty());
526
527 auto self = weak_factory_.GetWeakPtr();
528
529 // Check whether the event applies to `target_` and notify the callback.
530 File::Info target_info;
531 bool target_exists_after_batch = GetFileInfo(target_, &target_info);
532
533 bool target_created_or_deleted = target_exists_after_batch != target_exists_;
534 target_exists_ = target_exists_after_batch;
535
536 // This keeps track of whether we just notified for a
537 // `FILE_ACTION_RENAMED_OLD_NAME`.
538 bool last_event_notified_for_old_name = false;
539
540 auto sub_span = notification_batch.as_span();
541 bool has_next_entry = true;
542
543 while (has_next_entry) {
544 const auto& file_notify_info =
545 *reinterpret_cast<FILE_NOTIFY_INFORMATION*>(sub_span.data());
546
547 has_next_entry = file_notify_info.NextEntryOffset != 0;
548 if (has_next_entry) {
549 sub_span = sub_span.subspan(file_notify_info.NextEntryOffset);
550 }
551
552 DWORD change_type = file_notify_info.Action;
553
554 // A rename will generate two move events, but we only report it as one move
555 // event. So continue if we just reported a `FILE_ACTION_RENAMED_OLD_NAME`.
556 if (last_event_notified_for_old_name &&
557 change_type == FILE_ACTION_RENAMED_NEW_NAME) {
558 last_event_notified_for_old_name = false;
559 continue;
560 }
561 last_event_notified_for_old_name = false;
562
563 FilePath change_path = watched_path.Append(std::basic_string_view<wchar_t>(
564 file_notify_info.FileName,
565 file_notify_info.FileNameLength / sizeof(wchar_t)));
566
567 // Ancestors of the `target_` are outside the watch scope.
568 if (change_path.IsParent(target_)) {
569 // Only report move events where the target was created or deleted.
570 if ((change_type != FILE_ACTION_RENAMED_NEW_NAME &&
571 change_type != FILE_ACTION_RENAMED_OLD_NAME) ||
572 !target_created_or_deleted) {
573 continue;
574 }
575 } else if (type_ == FilePathWatcher::Type::kNonRecursive &&
576 change_path != target_ && change_path.DirName() != target_) {
577 // For non recursive watches, only report events for the target or its
578 // direct children.
579 continue;
580 }
581
582 if (change_type == FILE_ACTION_MODIFIED) {
583 // Don't report modified events for directories.
584 File::Info file_info;
585 if (GetFileInfo(change_path, &file_info) && file_info.is_directory) {
586 continue;
587 }
588 }
589
590 last_event_notified_for_old_name =
591 change_type == FILE_ACTION_RENAMED_OLD_NAME;
592
593 // `this` may be deleted after `callback_` is run.
594 callback_.Run(FilePathWatcher::ChangeInfo(), target_, /*error=*/false);
595 if (!self) {
596 return;
597 }
598 }
599 }
600
SetupWatchHandleForTarget()601 bool FilePathWatcherImpl::SetupWatchHandleForTarget() {
602 CloseWatchHandle();
603
604 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
605
606 // Start at the target and walk up the directory chain until we successfully
607 // create a file handle in `watched_handle_`. `child_dirs` keeps a stack of
608 // child directories stripped from target, in reverse order.
609 std::vector<FilePath> child_dirs;
610 FilePath path_to_watch(target_);
611
612 base::win::ScopedHandle watched_handle;
613 FilePath watched_path;
614 while (true) {
615 auto result = CreateDirectoryHandle(path_to_watch);
616
617 // Break if a valid handle is returned.
618 if (result.has_value()) {
619 watched_handle = std::move(result.value());
620 watched_path = path_to_watch;
621 break;
622 }
623
624 // We're in an unknown state if `CreateDirectoryHandle` returns an `kFatal`
625 // error, so return failure.
626 if (result.error() == CreateFileHandleError::kFatal) {
627 return false;
628 }
629
630 // Abort if we hit the root directory.
631 child_dirs.push_back(path_to_watch.BaseName());
632 FilePath parent(path_to_watch.DirName());
633 if (parent == path_to_watch) {
634 DLOG(ERROR) << "Reached the root directory";
635 return false;
636 }
637 path_to_watch = std::move(parent);
638 }
639
640 // At this point, `watched_handle` is valid. However, the bottom-up search
641 // that the above code performs races against directory creation. So try to
642 // walk back down and see whether any children appeared in the mean time.
643 while (!child_dirs.empty()) {
644 path_to_watch = path_to_watch.Append(child_dirs.back());
645 child_dirs.pop_back();
646 auto result = CreateDirectoryHandle(path_to_watch);
647 if (!result.has_value()) {
648 // We're in an unknown state if `CreateDirectoryHandle` returns an
649 // `kFatal` error, so return failure.
650 if (result.error() == CreateFileHandleError::kFatal) {
651 return false;
652 }
653 // Otherwise go with the current `watched_handle`.
654 break;
655 }
656 watched_handle = std::move(result.value());
657 watched_path = path_to_watch;
658 }
659
660 watcher_id_ = CompletionIOPortThread::Get()->AddWatcher(
661 *this, std::move(watched_handle), std::move(watched_path));
662
663 return watcher_id_.has_value();
664 }
665
CloseWatchHandle()666 void FilePathWatcherImpl::CloseWatchHandle() {
667 if (watcher_id_.has_value()) {
668 CompletionIOPortThread::Get()->RemoveWatcher(watcher_id_.value());
669 watcher_id_.reset();
670 }
671 }
672
673 } // namespace
674
FilePathWatcher()675 FilePathWatcher::FilePathWatcher()
676 : FilePathWatcher(std::make_unique<FilePathWatcherImpl>()) {}
677
678 } // namespace base
679