• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 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 <errno.h>
8 #include <poll.h>
9 #include <stddef.h>
10 #include <string.h>
11 #include <sys/inotify.h>
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <unistd.h>
15 
16 #include <algorithm>
17 #include <array>
18 #include <fstream>
19 #include <map>
20 #include <memory>
21 #include <set>
22 #include <unordered_map>
23 #include <utility>
24 #include <vector>
25 
26 #include "base/containers/contains.h"
27 #include "base/files/file_enumerator.h"
28 #include "base/files/file_path.h"
29 #include "base/files/file_path_watcher_inotify.h"
30 #include "base/files/file_util.h"
31 #include "base/functional/bind.h"
32 #include "base/lazy_instance.h"
33 #include "base/location.h"
34 #include "base/logging.h"
35 #include "base/memory/ptr_util.h"
36 #include "base/memory/scoped_refptr.h"
37 #include "base/memory/weak_ptr.h"
38 #include "base/posix/eintr_wrapper.h"
39 #include "base/synchronization/lock.h"
40 #include "base/task/sequenced_task_runner.h"
41 #include "base/task/single_thread_task_runner.h"
42 #include "base/threading/platform_thread.h"
43 #include "base/threading/scoped_blocking_call.h"
44 #include "base/trace_event/base_tracing.h"
45 #include "build/build_config.h"
46 
47 namespace base {
48 
49 namespace {
50 
51 #if !BUILDFLAG(IS_FUCHSIA)
52 
53 // The /proc path to max_user_watches.
54 constexpr char kInotifyMaxUserWatchesPath[] =
55     "/proc/sys/fs/inotify/max_user_watches";
56 
57 // This is a soft limit. If there are more than |kExpectedFilePathWatches|
58 // FilePathWatchers for a user, than they might affect each other's inotify
59 // watchers limit.
60 constexpr size_t kExpectedFilePathWatchers = 16u;
61 
62 // The default max inotify watchers limit per user, if reading
63 // /proc/sys/fs/inotify/max_user_watches fails.
64 constexpr size_t kDefaultInotifyMaxUserWatches = 8192u;
65 
66 #endif  // !BUILDFLAG(IS_FUCHSIA)
67 
68 class FilePathWatcherImpl;
69 class InotifyReader;
70 
71 // Used by test to override inotify watcher limit.
72 size_t g_override_max_inotify_watches = 0u;
73 
74 class InotifyReaderThreadDelegate final : public PlatformThread::Delegate {
75  public:
InotifyReaderThreadDelegate(int inotify_fd)76   explicit InotifyReaderThreadDelegate(int inotify_fd)
77       : inotify_fd_(inotify_fd) {}
78   InotifyReaderThreadDelegate(const InotifyReaderThreadDelegate&) = delete;
79   InotifyReaderThreadDelegate& operator=(const InotifyReaderThreadDelegate&) =
80       delete;
81   ~InotifyReaderThreadDelegate() override = default;
82 
83  private:
84   void ThreadMain() override;
85 
86   const int inotify_fd_;
87 };
88 
89 // Singleton to manage all inotify watches.
90 // TODO(tony): It would be nice if this wasn't a singleton.
91 // http://crbug.com/38174
92 class InotifyReader {
93  public:
94   // Watch descriptor used by AddWatch() and RemoveWatch().
95 #if BUILDFLAG(IS_ANDROID)
96   using Watch = uint32_t;
97 #else
98   using Watch = int;
99 #endif
100 
101   // Record of watchers tracked for watch descriptors.
102   struct WatcherEntry {
103     scoped_refptr<SequencedTaskRunner> task_runner;
104     WeakPtr<FilePathWatcherImpl> watcher;
105   };
106 
107   static constexpr Watch kInvalidWatch = static_cast<Watch>(-1);
108   static constexpr Watch kWatchLimitExceeded = static_cast<Watch>(-2);
109 
110   InotifyReader(const InotifyReader&) = delete;
111   InotifyReader& operator=(const InotifyReader&) = delete;
112 
113   // Watch directory |path| for changes. |watcher| will be notified on each
114   // change. Returns |kInvalidWatch| on failure.
115   Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher);
116 
117   // Remove |watch| if it's valid.
118   void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher);
119 
120   // Invoked on "inotify_reader" thread to notify relevant watchers.
121   void OnInotifyEvent(const inotify_event* event);
122 
123   // Returns true if any paths are actively being watched.
124   bool HasWatches();
125 
126  private:
127   friend struct LazyInstanceTraitsBase<InotifyReader>;
128 
129   InotifyReader();
130   // There is no destructor because |g_inotify_reader| is a
131   // base::LazyInstace::Leaky object. Having a destructor causes build
132   // issues with GCC 6 (http://crbug.com/636346).
133 
134   // Returns true on successful thread creation.
135   bool StartThread();
136 
137   Lock lock_;
138 
139   // Tracks which FilePathWatcherImpls to be notified on which watches.
140   // The tracked FilePathWatcherImpl is keyed by raw pointers for fast look up
141   // and mapped to a WatchEntry that is used to safely post a notification.
142   std::unordered_map<Watch, std::map<FilePathWatcherImpl*, WatcherEntry>>
143       watchers_ GUARDED_BY(lock_);
144 
145   // File descriptor returned by inotify_init.
146   const int inotify_fd_;
147 
148   // Thread delegate for the Inotify thread.
149   InotifyReaderThreadDelegate thread_delegate_;
150 
151   // Flag set to true when startup was successful.
152   bool valid_ = false;
153 };
154 
155 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
156  public:
157   FilePathWatcherImpl();
158   FilePathWatcherImpl(const FilePathWatcherImpl&) = delete;
159   FilePathWatcherImpl& operator=(const FilePathWatcherImpl&) = delete;
160   ~FilePathWatcherImpl() override;
161 
162   // Called for each event coming from the watch on the original thread.
163   // |fired_watch| identifies the watch that fired, |child| indicates what has
164   // changed, and is relative to the currently watched path for |fired_watch|.
165   //
166   // |created| is true if the object appears.
167   // |deleted| is true if the object disappears.
168   // |is_dir| is true if the object is a directory.
169   void OnFilePathChanged(InotifyReader::Watch fired_watch,
170                          const FilePath::StringType& child,
171                          bool created,
172                          bool deleted,
173                          bool is_dir);
174 
175   // Returns whether the number of inotify watches of this FilePathWatcherImpl
176   // would exceed the limit if adding one more.
177   bool WouldExceedWatchLimit() const;
178 
179   // Returns a WatcherEntry for this, must be called on the original sequence.
180   InotifyReader::WatcherEntry GetWatcherEntry();
181 
182  private:
183   // Start watching |path| for changes and notify |delegate| on each change.
184   // Returns true if watch for |path| has been added successfully.
185   bool Watch(const FilePath& path,
186              Type type,
187              const FilePathWatcher::Callback& callback) override;
188 
189   // A generalized version. It extends |Type|.
190   bool WatchWithOptions(const FilePath& path,
191                         const WatchOptions& flags,
192                         const FilePathWatcher::Callback& callback) override;
193 
194   // Cancel the watch. This unregisters the instance with InotifyReader.
195   void Cancel() override;
196 
197   // Inotify watches are installed for all directory components of |target_|.
198   // A WatchEntry instance holds:
199   // - |watch|: the watch descriptor for a component.
200   // - |subdir|: the subdirectory that identifies the next component.
201   //   - For the last component, there is no next component, so it is empty.
202   // - |linkname|: the target of the symlink.
203   //   - Only if the target being watched is a symbolic link.
204   struct WatchEntry {
WatchEntrybase::__anona8b77b2a0111::FilePathWatcherImpl::WatchEntry205     explicit WatchEntry(const FilePath::StringType& dirname)
206         : watch(InotifyReader::kInvalidWatch), subdir(dirname) {}
207 
208     InotifyReader::Watch watch;
209     FilePath::StringType subdir;
210     FilePath::StringType linkname;
211   };
212 
213   // Reconfigure to watch for the most specific parent directory of |target_|
214   // that exists. Also calls UpdateRecursiveWatches() below. Returns true if
215   // watch limit is not hit. Otherwise, returns false.
216   [[nodiscard]] bool UpdateWatches();
217 
218   // Reconfigure to recursively watch |target_| and all its sub-directories.
219   // - This is a no-op if the watch is not recursive.
220   // - If |target_| does not exist, then clear all the recursive watches.
221   // - Assuming |target_| exists, passing kInvalidWatch as |fired_watch| forces
222   //   addition of recursive watches for |target_|.
223   // - Otherwise, only the directory associated with |fired_watch| and its
224   //   sub-directories will be reconfigured.
225   // Returns true if watch limit is not hit. Otherwise, returns false.
226   [[nodiscard]] bool UpdateRecursiveWatches(InotifyReader::Watch fired_watch,
227                                             bool is_dir);
228 
229   // Enumerate recursively through |path| and add / update watches.
230   // Returns true if watch limit is not hit. Otherwise, returns false.
231   [[nodiscard]] bool UpdateRecursiveWatchesForPath(const FilePath& path);
232 
233   // Do internal bookkeeping to update mappings between |watch| and its
234   // associated full path |path|.
235   void TrackWatchForRecursion(InotifyReader::Watch watch, const FilePath& path);
236 
237   // Remove all the recursive watches.
238   void RemoveRecursiveWatches();
239 
240   // |path| is a symlink to a non-existent target. Attempt to add a watch to
241   // the link target's parent directory. Update |watch_entry| on success.
242   // Returns true if watch limit is not hit. Otherwise, returns false.
243   [[nodiscard]] bool AddWatchForBrokenSymlink(const FilePath& path,
244                                               WatchEntry* watch_entry);
245 
246   bool HasValidWatchVector() const;
247 
248   // Callback to notify upon changes.
249   FilePathWatcher::Callback callback_;
250 
251   // The file or directory we're supposed to watch.
252   FilePath target_;
253 
254   Type type_ = Type::kNonRecursive;
255   bool report_modified_path_ = false;
256 
257   // The vector of watches and next component names for all path components,
258   // starting at the root directory. The last entry corresponds to the watch for
259   // |target_| and always stores an empty next component name in |subdir|.
260   std::vector<WatchEntry> watches_;
261 
262   std::unordered_map<InotifyReader::Watch, FilePath> recursive_paths_by_watch_;
263   std::map<FilePath, InotifyReader::Watch> recursive_watches_by_path_;
264 
265   WeakPtrFactory<FilePathWatcherImpl> weak_factory_{this};
266 };
267 
268 LazyInstance<InotifyReader>::Leaky g_inotify_reader = LAZY_INSTANCE_INITIALIZER;
269 
ThreadMain()270 void InotifyReaderThreadDelegate::ThreadMain() {
271   PlatformThread::SetName("inotify_reader");
272 
273   std::array<pollfd, 1> fdarray{{{inotify_fd_, POLLIN, 0}}};
274 
275   while (true) {
276     // Wait until some inotify events are available.
277     int poll_result = HANDLE_EINTR(poll(fdarray.data(), fdarray.size(), -1));
278     if (poll_result < 0) {
279       DPLOG(WARNING) << "poll failed";
280       return;
281     }
282 
283     // Adjust buffer size to current event queue size.
284     int buffer_size;
285     int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size));
286 
287     if (ioctl_result != 0 || buffer_size < 0) {
288       DPLOG(WARNING) << "ioctl failed";
289       return;
290     }
291 
292     std::vector<char> buffer(static_cast<size_t>(buffer_size));
293 
294     ssize_t bytes_read = HANDLE_EINTR(
295         read(inotify_fd_, buffer.data(), static_cast<size_t>(buffer_size)));
296 
297     if (bytes_read < 0) {
298       DPLOG(WARNING) << "read from inotify fd failed";
299       return;
300     }
301 
302     for (size_t i = 0; i < static_cast<size_t>(bytes_read);) {
303       inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
304       size_t event_size = sizeof(inotify_event) + event->len;
305       DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
306       g_inotify_reader.Get().OnInotifyEvent(event);
307       i += event_size;
308     }
309   }
310 }
311 
InotifyReader()312 InotifyReader::InotifyReader()
313     : inotify_fd_(inotify_init()), thread_delegate_(inotify_fd_) {
314   if (inotify_fd_ < 0) {
315     PLOG(ERROR) << "inotify_init() failed";
316     return;
317   }
318 
319   if (!StartThread())
320     return;
321 
322   valid_ = true;
323 }
324 
StartThread()325 bool InotifyReader::StartThread() {
326   // This object is LazyInstance::Leaky, so thread_delegate_ will outlive the
327   // thread.
328   return PlatformThread::CreateNonJoinable(0, &thread_delegate_);
329 }
330 
AddWatch(const FilePath & path,FilePathWatcherImpl * watcher)331 InotifyReader::Watch InotifyReader::AddWatch(const FilePath& path,
332                                              FilePathWatcherImpl* watcher) {
333   if (!valid_)
334     return kInvalidWatch;
335 
336   if (watcher->WouldExceedWatchLimit())
337     return kWatchLimitExceeded;
338 
339   AutoLock auto_lock(lock_);
340 
341   ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::WILL_BLOCK);
342   const int watch_int =
343       inotify_add_watch(inotify_fd_, path.value().c_str(),
344                         IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE |
345                             IN_MOVE | IN_ONLYDIR);
346   if (watch_int == -1)
347     return kInvalidWatch;
348   const Watch watch = static_cast<Watch>(watch_int);
349 
350   watchers_[watch].emplace(std::make_pair(watcher, watcher->GetWatcherEntry()));
351 
352   return watch;
353 }
354 
RemoveWatch(Watch watch,FilePathWatcherImpl * watcher)355 void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) {
356   if (!valid_ || (watch == kInvalidWatch))
357     return;
358 
359   AutoLock auto_lock(lock_);
360 
361   auto watchers_it = watchers_.find(watch);
362   if (watchers_it == watchers_.end())
363     return;
364 
365   auto& watcher_map = watchers_it->second;
366   watcher_map.erase(watcher);
367 
368   if (watcher_map.empty()) {
369     watchers_.erase(watchers_it);
370 
371     ScopedBlockingCall scoped_blocking_call(FROM_HERE,
372                                             BlockingType::WILL_BLOCK);
373     inotify_rm_watch(inotify_fd_, watch);
374   }
375 }
376 
OnInotifyEvent(const inotify_event * event)377 void InotifyReader::OnInotifyEvent(const inotify_event* event) {
378   if (event->mask & IN_IGNORED)
379     return;
380 
381   FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL(""));
382   AutoLock auto_lock(lock_);
383 
384   // In racing conditions, RemoveWatch() could grab `lock_` first and remove
385   // the entry for `event->wd`.
386   auto watchers_it = watchers_.find(static_cast<Watch>(event->wd));
387   if (watchers_it == watchers_.end())
388     return;
389 
390   auto& watcher_map = watchers_it->second;
391   for (const auto& entry : watcher_map) {
392     auto& watcher_entry = entry.second;
393     watcher_entry.task_runner->PostTask(
394         FROM_HERE,
395         BindOnce(&FilePathWatcherImpl::OnFilePathChanged, watcher_entry.watcher,
396                  static_cast<Watch>(event->wd), child,
397                  event->mask & (IN_CREATE | IN_MOVED_TO),
398                  event->mask & (IN_DELETE | IN_MOVED_FROM),
399                  event->mask & IN_ISDIR));
400   }
401 }
402 
HasWatches()403 bool InotifyReader::HasWatches() {
404   AutoLock auto_lock(lock_);
405 
406   return !watchers_.empty();
407 }
408 
409 FilePathWatcherImpl::FilePathWatcherImpl() = default;
410 
~FilePathWatcherImpl()411 FilePathWatcherImpl::~FilePathWatcherImpl() {
412   DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
413 }
414 
OnFilePathChanged(InotifyReader::Watch fired_watch,const FilePath::StringType & child,bool created,bool deleted,bool is_dir)415 void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch,
416                                             const FilePath::StringType& child,
417                                             bool created,
418                                             bool deleted,
419                                             bool is_dir) {
420   DCHECK(task_runner()->RunsTasksInCurrentSequence());
421   DCHECK(!watches_.empty());
422   DCHECK(HasValidWatchVector());
423 
424   // Used below to avoid multiple recursive updates.
425   bool did_update = false;
426 
427   // Whether kWatchLimitExceeded is encountered during update.
428   bool exceeded_limit = false;
429 
430   // Find the entries in |watches_| that correspond to |fired_watch|.
431   for (size_t i = 0; i < watches_.size(); ++i) {
432     const WatchEntry& watch_entry = watches_[i];
433     if (fired_watch != watch_entry.watch)
434       continue;
435 
436     // Check whether a path component of |target_| changed.
437     bool change_on_target_path = child.empty() ||
438                                  (child == watch_entry.linkname) ||
439                                  (child == watch_entry.subdir);
440 
441     // Check if the change references |target_| or a direct child of |target_|.
442     bool target_changed;
443     if (watch_entry.subdir.empty()) {
444       // The fired watch is for a WatchEntry without a subdir. Thus for a given
445       // |target_| = "/path/to/foo", this is for "foo". Here, check either:
446       // - the target has no symlink: it is the target and it changed.
447       // - the target has a symlink, and it matches |child|.
448       target_changed =
449           (watch_entry.linkname.empty() || child == watch_entry.linkname);
450     } else {
451       // The fired watch is for a WatchEntry with a subdir. Thus for a given
452       // |target_| = "/path/to/foo", this is for {"/", "/path", "/path/to"}.
453       // So we can safely access the next WatchEntry since we have not reached
454       // the end yet. Check |watch_entry| is for "/path/to", i.e. the next
455       // element is "foo".
456       bool next_watch_may_be_for_target = watches_[i + 1].subdir.empty();
457       if (next_watch_may_be_for_target) {
458         // The current |watch_entry| is for "/path/to", so check if the |child|
459         // that changed is "foo".
460         target_changed = watch_entry.subdir == child;
461       } else {
462         // The current |watch_entry| is not for "/path/to", so the next entry
463         // cannot be "foo". Thus |target_| has not changed.
464         target_changed = false;
465       }
466     }
467 
468     // Update watches if a directory component of the |target_| path
469     // (dis)appears. Note that we don't add the additional restriction of
470     // checking the event mask to see if it is for a directory here as changes
471     // to symlinks on the target path will not have IN_ISDIR set in the event
472     // masks. As a result we may sometimes call UpdateWatches() unnecessarily.
473     if (change_on_target_path && (created || deleted) && !did_update) {
474       if (!UpdateWatches()) {
475         exceeded_limit = true;
476         break;
477       }
478       did_update = true;
479     }
480 
481     // Report the following events:
482     //  - The target or a direct child of the target got changed (in case the
483     //    watched path refers to a directory).
484     //  - One of the parent directories got moved or deleted, since the target
485     //    disappears in this case.
486     //  - One of the parent directories appears. The event corresponding to
487     //    the target appearing might have been missed in this case, so recheck.
488     if (target_changed || (change_on_target_path && deleted) ||
489         (change_on_target_path && created && PathExists(target_))) {
490       if (!did_update) {
491         if (!UpdateRecursiveWatches(fired_watch, is_dir)) {
492           exceeded_limit = true;
493           break;
494         }
495         did_update = true;
496       }
497       if (report_modified_path_ && !change_on_target_path) {
498         callback_.Run(target_.Append(child),
499                       /*error=*/false);  // `this` may be deleted.
500       } else {
501         callback_.Run(target_, /*error=*/false);  // `this` may be deleted.
502       }
503       return;
504     }
505   }
506 
507   if (!exceeded_limit && Contains(recursive_paths_by_watch_, fired_watch)) {
508     if (!did_update) {
509       if (!UpdateRecursiveWatches(fired_watch, is_dir))
510         exceeded_limit = true;
511     }
512     if (!exceeded_limit) {
513       if (report_modified_path_) {
514         callback_.Run(recursive_paths_by_watch_[fired_watch].Append(child),
515                       /*error=*/false);  // `this` may be deleted.
516       } else {
517         callback_.Run(target_, /*error=*/false);  // `this` may be deleted.
518       }
519       return;
520     }
521   }
522 
523   if (exceeded_limit) {
524     // Cancels all in-flight events from inotify thread.
525     weak_factory_.InvalidateWeakPtrs();
526 
527     // Reset states and cancels all watches.
528     auto callback = callback_;
529     Cancel();
530 
531     // Fires the "error=true" callback.
532     callback.Run(target_, /*error=*/true);  // `this` may be deleted.
533   }
534 }
535 
WouldExceedWatchLimit() const536 bool FilePathWatcherImpl::WouldExceedWatchLimit() const {
537   DCHECK(task_runner()->RunsTasksInCurrentSequence());
538 
539   // `watches_` contains inotify watches of all dir components of `target_`.
540   // `recursive_paths_by_watch_` contains inotify watches for sub dirs under
541   // `target_` of a Type::kRecursive watcher and keyed by inotify watches.
542   // All inotify watches used by this FilePathWatcherImpl are either in
543   // `watches_` or as a key in `recursive_paths_by_watch_`. As a result, the
544   // two provide a good estimate on the number of inofiy watches used by this
545   // FilePathWatcherImpl.
546   const size_t number_of_inotify_watches =
547       watches_.size() + recursive_paths_by_watch_.size();
548   return number_of_inotify_watches >= GetMaxNumberOfInotifyWatches();
549 }
550 
GetWatcherEntry()551 InotifyReader::WatcherEntry FilePathWatcherImpl::GetWatcherEntry() {
552   DCHECK(task_runner()->RunsTasksInCurrentSequence());
553   return {task_runner(), weak_factory_.GetWeakPtr()};
554 }
555 
Watch(const FilePath & path,Type type,const FilePathWatcher::Callback & callback)556 bool FilePathWatcherImpl::Watch(const FilePath& path,
557                                 Type type,
558                                 const FilePathWatcher::Callback& callback) {
559   DCHECK(target_.empty());
560 
561   set_task_runner(SequencedTaskRunner::GetCurrentDefault());
562   callback_ = callback;
563   target_ = path;
564   type_ = type;
565 
566   std::vector<FilePath::StringType> comps = target_.GetComponents();
567   DCHECK(!comps.empty());
568   for (size_t i = 1; i < comps.size(); ++i)
569     watches_.emplace_back(comps[i]);
570   watches_.emplace_back(FilePath::StringType());
571 
572   if (!UpdateWatches()) {
573     Cancel();
574     // Note `callback` is not invoked since false is returned.
575     return false;
576   }
577 
578   return true;
579 }
580 
WatchWithOptions(const FilePath & path,const WatchOptions & options,const FilePathWatcher::Callback & callback)581 bool FilePathWatcherImpl::WatchWithOptions(
582     const FilePath& path,
583     const WatchOptions& options,
584     const FilePathWatcher::Callback& callback) {
585   report_modified_path_ = options.report_modified_path;
586   return Watch(path, options.type, callback);
587 }
588 
Cancel()589 void FilePathWatcherImpl::Cancel() {
590   if (!callback_) {
591     // Watch() was never called.
592     set_cancelled();
593     return;
594   }
595 
596   DCHECK(task_runner()->RunsTasksInCurrentSequence());
597   DCHECK(!is_cancelled());
598 
599   set_cancelled();
600   callback_.Reset();
601 
602   for (const auto& watch : watches_)
603     g_inotify_reader.Get().RemoveWatch(watch.watch, this);
604   watches_.clear();
605   target_.clear();
606   RemoveRecursiveWatches();
607 }
608 
UpdateWatches()609 bool FilePathWatcherImpl::UpdateWatches() {
610   // Ensure this runs on the task_runner() exclusively in order to avoid
611   // concurrency issues.
612   DCHECK(task_runner()->RunsTasksInCurrentSequence());
613   DCHECK(HasValidWatchVector());
614 
615   // Walk the list of watches and update them as we go.
616   FilePath path(FILE_PATH_LITERAL("/"));
617   for (WatchEntry& watch_entry : watches_) {
618     InotifyReader::Watch old_watch = watch_entry.watch;
619     watch_entry.watch = InotifyReader::kInvalidWatch;
620     watch_entry.linkname.clear();
621     watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this);
622     if (watch_entry.watch == InotifyReader::kWatchLimitExceeded)
623       return false;
624     if (watch_entry.watch == InotifyReader::kInvalidWatch) {
625       // Ignore the error code (beyond symlink handling) to attempt to add
626       // watches on accessible children of unreadable directories. Note that
627       // this is a best-effort attempt; we may not catch events in this
628       // scenario.
629       if (IsLink(path)) {
630         if (!AddWatchForBrokenSymlink(path, &watch_entry))
631           return false;
632       }
633     }
634     if (old_watch != watch_entry.watch)
635       g_inotify_reader.Get().RemoveWatch(old_watch, this);
636     path = path.Append(watch_entry.subdir);
637   }
638 
639   return UpdateRecursiveWatches(InotifyReader::kInvalidWatch, /*is_dir=*/false);
640 }
641 
UpdateRecursiveWatches(InotifyReader::Watch fired_watch,bool is_dir)642 bool FilePathWatcherImpl::UpdateRecursiveWatches(
643     InotifyReader::Watch fired_watch,
644     bool is_dir) {
645   DCHECK(HasValidWatchVector());
646 
647   if (type_ != Type::kRecursive)
648     return true;
649 
650   if (!DirectoryExists(target_)) {
651     RemoveRecursiveWatches();
652     return true;
653   }
654 
655   // Check to see if this is a forced update or if some component of |target_|
656   // has changed. For these cases, redo the watches for |target_| and below.
657   if (!Contains(recursive_paths_by_watch_, fired_watch) &&
658       fired_watch != watches_.back().watch) {
659     return UpdateRecursiveWatchesForPath(target_);
660   }
661 
662   // Underneath |target_|, only directory changes trigger watch updates.
663   if (!is_dir)
664     return true;
665 
666   const FilePath& changed_dir = Contains(recursive_paths_by_watch_, fired_watch)
667                                     ? recursive_paths_by_watch_[fired_watch]
668                                     : target_;
669 
670   auto start_it = recursive_watches_by_path_.upper_bound(changed_dir);
671   auto end_it = start_it;
672   for (; end_it != recursive_watches_by_path_.end(); ++end_it) {
673     const FilePath& cur_path = end_it->first;
674     if (!changed_dir.IsParent(cur_path))
675       break;
676 
677     // There could be a race when another process is changing contents under
678     // `changed_dir` while chrome is watching (e.g. an Android app updating
679     // a dir with Chrome OS file manager open for the dir). In such case,
680     // `cur_dir` under `changed_dir` could exist in this loop but not in
681     // the FileEnumerator loop in the upcoming UpdateRecursiveWatchesForPath(),
682     // As a result, `g_inotify_reader` would have an entry in its `watchers_`
683     // pointing to `this` but `this` is no longer aware of that. Crash in
684     // http://crbug/990004 could happen later.
685     //
686     // Remove the watcher of `cur_path` regardless of whether it exists
687     // or not to keep `this` and `g_inotify_reader` consistent even when the
688     // race happens. The watcher will be added back if `cur_path` exists in
689     // the FileEnumerator loop in UpdateRecursiveWatchesForPath().
690     g_inotify_reader.Get().RemoveWatch(end_it->second, this);
691 
692     // Keep it in sync with |recursive_watches_by_path_| crbug.com/995196.
693     recursive_paths_by_watch_.erase(end_it->second);
694   }
695   recursive_watches_by_path_.erase(start_it, end_it);
696   return UpdateRecursiveWatchesForPath(changed_dir);
697 }
698 
UpdateRecursiveWatchesForPath(const FilePath & path)699 bool FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) {
700   DCHECK_EQ(type_, Type::kRecursive);
701   DCHECK(!path.empty());
702   DCHECK(DirectoryExists(path));
703 
704   // Note: SHOW_SYM_LINKS exposes symlinks as symlinks, so they are ignored
705   // rather than followed. Following symlinks can easily lead to the undesirable
706   // situation where the entire file system is being watched.
707   FileEnumerator enumerator(
708       path, true /* recursive enumeration */,
709       FileEnumerator::DIRECTORIES | FileEnumerator::SHOW_SYM_LINKS);
710   for (FilePath current = enumerator.Next(); !current.empty();
711        current = enumerator.Next()) {
712     DCHECK(enumerator.GetInfo().IsDirectory());
713 
714     if (!Contains(recursive_watches_by_path_, current)) {
715       // Add new watches.
716       InotifyReader::Watch watch =
717           g_inotify_reader.Get().AddWatch(current, this);
718       if (watch == InotifyReader::kWatchLimitExceeded)
719         return false;
720       TrackWatchForRecursion(watch, current);
721     } else {
722       // Update existing watches.
723       InotifyReader::Watch old_watch = recursive_watches_by_path_[current];
724       DCHECK_NE(InotifyReader::kInvalidWatch, old_watch);
725       InotifyReader::Watch watch =
726           g_inotify_reader.Get().AddWatch(current, this);
727       if (watch == InotifyReader::kWatchLimitExceeded)
728         return false;
729       if (watch != old_watch) {
730         g_inotify_reader.Get().RemoveWatch(old_watch, this);
731         recursive_paths_by_watch_.erase(old_watch);
732         recursive_watches_by_path_.erase(current);
733         TrackWatchForRecursion(watch, current);
734       }
735     }
736   }
737   return true;
738 }
739 
TrackWatchForRecursion(InotifyReader::Watch watch,const FilePath & path)740 void FilePathWatcherImpl::TrackWatchForRecursion(InotifyReader::Watch watch,
741                                                  const FilePath& path) {
742   DCHECK_EQ(type_, Type::kRecursive);
743   DCHECK(!path.empty());
744   DCHECK(target_.IsParent(path));
745 
746   if (watch == InotifyReader::kInvalidWatch)
747     return;
748 
749   DCHECK(!Contains(recursive_paths_by_watch_, watch));
750   DCHECK(!Contains(recursive_watches_by_path_, path));
751   recursive_paths_by_watch_[watch] = path;
752   recursive_watches_by_path_[path] = watch;
753 }
754 
RemoveRecursiveWatches()755 void FilePathWatcherImpl::RemoveRecursiveWatches() {
756   if (type_ != Type::kRecursive)
757     return;
758 
759   for (const auto& it : recursive_paths_by_watch_)
760     g_inotify_reader.Get().RemoveWatch(it.first, this);
761 
762   recursive_paths_by_watch_.clear();
763   recursive_watches_by_path_.clear();
764 }
765 
AddWatchForBrokenSymlink(const FilePath & path,WatchEntry * watch_entry)766 bool FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path,
767                                                    WatchEntry* watch_entry) {
768 #if BUILDFLAG(IS_FUCHSIA)
769   // Fuchsia does not support symbolic links.
770   return false;
771 #else   // BUILDFLAG(IS_FUCHSIA)
772   DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch);
773   absl::optional<FilePath> link = ReadSymbolicLinkAbsolute(path);
774   if (!link) {
775     return true;
776   }
777   DCHECK(link->IsAbsolute());
778 
779   // Try watching symlink target directory. If the link target is "/", then we
780   // shouldn't get here in normal situations and if we do, we'd watch "/" for
781   // changes to a component "/" which is harmless so no special treatment of
782   // this case is required.
783   InotifyReader::Watch watch =
784       g_inotify_reader.Get().AddWatch(link->DirName(), this);
785   if (watch == InotifyReader::kWatchLimitExceeded)
786     return false;
787   if (watch == InotifyReader::kInvalidWatch) {
788     // TODO(craig) Symlinks only work if the parent directory for the target
789     // exist. Ideally we should make sure we've watched all the components of
790     // the symlink path for changes. See crbug.com/91561 for details.
791     DPLOG(WARNING) << "Watch failed for " << link->DirName().value();
792     return true;
793   }
794   watch_entry->watch = watch;
795   watch_entry->linkname = link->BaseName().value();
796   return true;
797 #endif  // BUILDFLAG(IS_FUCHSIA)
798 }
799 
HasValidWatchVector() const800 bool FilePathWatcherImpl::HasValidWatchVector() const {
801   if (watches_.empty())
802     return false;
803   for (size_t i = 0; i < watches_.size() - 1; ++i) {
804     if (watches_[i].subdir.empty())
805       return false;
806   }
807   return watches_.back().subdir.empty();
808 }
809 
810 }  // namespace
811 
GetMaxNumberOfInotifyWatches()812 size_t GetMaxNumberOfInotifyWatches() {
813 #if BUILDFLAG(IS_FUCHSIA)
814   // Fuchsia has no limit on the number of watches.
815   return std::numeric_limits<int>::max();
816 #else
817   static const size_t max = []() {
818     size_t max_number_of_inotify_watches = 0u;
819 
820     std::ifstream in(kInotifyMaxUserWatchesPath);
821     if (!in.is_open() || !(in >> max_number_of_inotify_watches)) {
822       LOG(ERROR) << "Failed to read " << kInotifyMaxUserWatchesPath;
823       return kDefaultInotifyMaxUserWatches / kExpectedFilePathWatchers;
824     }
825 
826     return max_number_of_inotify_watches / kExpectedFilePathWatchers;
827   }();
828   return g_override_max_inotify_watches ? g_override_max_inotify_watches : max;
829 #endif  // if BUILDFLAG(IS_FUCHSIA)
830 }
831 
832 ScopedMaxNumberOfInotifyWatchesOverrideForTest::
ScopedMaxNumberOfInotifyWatchesOverrideForTest(size_t override_max)833     ScopedMaxNumberOfInotifyWatchesOverrideForTest(size_t override_max) {
834   DCHECK_EQ(g_override_max_inotify_watches, 0u);
835   g_override_max_inotify_watches = override_max;
836 }
837 
838 ScopedMaxNumberOfInotifyWatchesOverrideForTest::
~ScopedMaxNumberOfInotifyWatchesOverrideForTest()839     ~ScopedMaxNumberOfInotifyWatchesOverrideForTest() {
840   g_override_max_inotify_watches = 0u;
841 }
842 
FilePathWatcher()843 FilePathWatcher::FilePathWatcher() {
844   DETACH_FROM_SEQUENCE(sequence_checker_);
845   impl_ = std::make_unique<FilePathWatcherImpl>();
846 }
847 
848 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
849 // Put inside "BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)" because Android
850 // includes file_path_watcher_linux.cc.
851 
852 // static
HasWatchesForTest()853 bool FilePathWatcher::HasWatchesForTest() {
854   return g_inotify_reader.Get().HasWatches();
855 }
856 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
857 
858 }  // namespace base
859