• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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