| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/files/file_path_watcher.h" |
| |
| #include <errno.h> |
| #include <poll.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <sys/inotify.h> |
| #include <sys/ioctl.h> |
| #include <sys/select.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <fstream> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_path_watcher_inotify.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/lazy_instance.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/trace_event/base_tracing.h" |
| #include "build/build_config.h" |
| |
| namespace base { |
| |
| namespace { |
| |
| #if !BUILDFLAG(IS_FUCHSIA) |
| |
| // The /proc path to max_user_watches. |
| constexpr char kInotifyMaxUserWatchesPath[] = |
| "/proc/sys/fs/inotify/max_user_watches"; |
| |
| // This is a soft limit. If there are more than |kExpectedFilePathWatches| |
| // FilePathWatchers for a user, than they might affect each other's inotify |
| // watchers limit. |
| constexpr size_t kExpectedFilePathWatchers = 16u; |
| |
| // The default max inotify watchers limit per user, if reading |
| // /proc/sys/fs/inotify/max_user_watches fails. |
| constexpr size_t kDefaultInotifyMaxUserWatches = 8192u; |
| |
| #endif // !BUILDFLAG(IS_FUCHSIA) |
| |
| class FilePathWatcherImpl; |
| class InotifyReader; |
| |
| // Used by test to override inotify watcher limit. |
| size_t g_override_max_inotify_watches = 0u; |
| |
| class InotifyReaderThreadDelegate final : public PlatformThread::Delegate { |
| public: |
| explicit InotifyReaderThreadDelegate(int inotify_fd) |
| : inotify_fd_(inotify_fd) {} |
| InotifyReaderThreadDelegate(const InotifyReaderThreadDelegate&) = delete; |
| InotifyReaderThreadDelegate& operator=(const InotifyReaderThreadDelegate&) = |
| delete; |
| ~InotifyReaderThreadDelegate() override = default; |
| |
| private: |
| void ThreadMain() override; |
| |
| const int inotify_fd_; |
| }; |
| |
| // Singleton to manage all inotify watches. |
| // TODO(tony): It would be nice if this wasn't a singleton. |
| // http://crbug.com/38174 |
| class InotifyReader { |
| public: |
| // Watch descriptor used by AddWatch() and RemoveWatch(). |
| #if BUILDFLAG(IS_ANDROID) |
| using Watch = uint32_t; |
| #else |
| using Watch = int; |
| #endif |
| |
| // Record of watchers tracked for watch descriptors. |
| struct WatcherEntry { |
| scoped_refptr<SequencedTaskRunner> task_runner; |
| WeakPtr<FilePathWatcherImpl> watcher; |
| }; |
| |
| static constexpr Watch kInvalidWatch = static_cast<Watch>(-1); |
| static constexpr Watch kWatchLimitExceeded = static_cast<Watch>(-2); |
| |
| InotifyReader(const InotifyReader&) = delete; |
| InotifyReader& operator=(const InotifyReader&) = delete; |
| |
| // Watch directory |path| for changes. |watcher| will be notified on each |
| // change. Returns |kInvalidWatch| on failure. |
| Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher); |
| |
| // Remove |watch| if it's valid. |
| void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher); |
| |
| // Invoked on "inotify_reader" thread to notify relevant watchers. |
| void OnInotifyEvent(const inotify_event* event); |
| |
| // Returns true if any paths are actively being watched. |
| bool HasWatches(); |
| |
| private: |
| friend struct LazyInstanceTraitsBase<InotifyReader>; |
| |
| InotifyReader(); |
| // There is no destructor because |g_inotify_reader| is a |
| // base::LazyInstace::Leaky object. Having a destructor causes build |
| // issues with GCC 6 (http://crbug.com/636346). |
| |
| // Returns true on successful thread creation. |
| bool StartThread(); |
| |
| Lock lock_; |
| |
| // Tracks which FilePathWatcherImpls to be notified on which watches. |
| // The tracked FilePathWatcherImpl is keyed by raw pointers for fast look up |
| // and mapped to a WatchEntry that is used to safely post a notification. |
| std::unordered_map<Watch, std::map<FilePathWatcherImpl*, WatcherEntry>> |
| watchers_ GUARDED_BY(lock_); |
| |
| // File descriptor returned by inotify_init. |
| const int inotify_fd_; |
| |
| // Thread delegate for the Inotify thread. |
| InotifyReaderThreadDelegate thread_delegate_; |
| |
| // Flag set to true when startup was successful. |
| bool valid_ = false; |
| }; |
| |
| class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { |
| public: |
| FilePathWatcherImpl(); |
| FilePathWatcherImpl(const FilePathWatcherImpl&) = delete; |
| FilePathWatcherImpl& operator=(const FilePathWatcherImpl&) = delete; |
| ~FilePathWatcherImpl() override; |
| |
| // Called for each event coming from the watch on the original thread. |
| // |fired_watch| identifies the watch that fired, |child| indicates what has |
| // changed, and is relative to the currently watched path for |fired_watch|. |
| // |
| // |created| is true if the object appears. |
| // |deleted| is true if the object disappears. |
| // |is_dir| is true if the object is a directory. |
| void OnFilePathChanged(InotifyReader::Watch fired_watch, |
| const FilePath::StringType& child, |
| bool created, |
| bool deleted, |
| bool is_dir); |
| |
| // Returns whether the number of inotify watches of this FilePathWatcherImpl |
| // would exceed the limit if adding one more. |
| bool WouldExceedWatchLimit() const; |
| |
| // Returns a WatcherEntry for this, must be called on the original sequence. |
| InotifyReader::WatcherEntry GetWatcherEntry(); |
| |
| private: |
| // Start watching |path| for changes and notify |delegate| on each change. |
| // Returns true if watch for |path| has been added successfully. |
| bool Watch(const FilePath& path, |
| Type type, |
| const FilePathWatcher::Callback& callback) override; |
| |
| // A generalized version. It extends |Type|. |
| bool WatchWithOptions(const FilePath& path, |
| const WatchOptions& flags, |
| const FilePathWatcher::Callback& callback) override; |
| |
| // Cancel the watch. This unregisters the instance with InotifyReader. |
| void Cancel() override; |
| |
| // Inotify watches are installed for all directory components of |target_|. |
| // A WatchEntry instance holds: |
| // - |watch|: the watch descriptor for a component. |
| // - |subdir|: the subdirectory that identifies the next component. |
| // - For the last component, there is no next component, so it is empty. |
| // - |linkname|: the target of the symlink. |
| // - Only if the target being watched is a symbolic link. |
| struct WatchEntry { |
| explicit WatchEntry(const FilePath::StringType& dirname) |
| : watch(InotifyReader::kInvalidWatch), subdir(dirname) {} |
| |
| InotifyReader::Watch watch; |
| FilePath::StringType subdir; |
| FilePath::StringType linkname; |
| }; |
| |
| // Reconfigure to watch for the most specific parent directory of |target_| |
| // that exists. Also calls UpdateRecursiveWatches() below. Returns true if |
| // watch limit is not hit. Otherwise, returns false. |
| [[nodiscard]] bool UpdateWatches(); |
| |
| // Reconfigure to recursively watch |target_| and all its sub-directories. |
| // - This is a no-op if the watch is not recursive. |
| // - If |target_| does not exist, then clear all the recursive watches. |
| // - Assuming |target_| exists, passing kInvalidWatch as |fired_watch| forces |
| // addition of recursive watches for |target_|. |
| // - Otherwise, only the directory associated with |fired_watch| and its |
| // sub-directories will be reconfigured. |
| // Returns true if watch limit is not hit. Otherwise, returns false. |
| [[nodiscard]] bool UpdateRecursiveWatches(InotifyReader::Watch fired_watch, |
| bool is_dir); |
| |
| // Enumerate recursively through |path| and add / update watches. |
| // Returns true if watch limit is not hit. Otherwise, returns false. |
| [[nodiscard]] bool UpdateRecursiveWatchesForPath(const FilePath& path); |
| |
| // Do internal bookkeeping to update mappings between |watch| and its |
| // associated full path |path|. |
| void TrackWatchForRecursion(InotifyReader::Watch watch, const FilePath& path); |
| |
| // Remove all the recursive watches. |
| void RemoveRecursiveWatches(); |
| |
| // |path| is a symlink to a non-existent target. Attempt to add a watch to |
| // the link target's parent directory. Update |watch_entry| on success. |
| // Returns true if watch limit is not hit. Otherwise, returns false. |
| [[nodiscard]] bool AddWatchForBrokenSymlink(const FilePath& path, |
| WatchEntry* watch_entry); |
| |
| bool HasValidWatchVector() const; |
| |
| // Callback to notify upon changes. |
| FilePathWatcher::Callback callback_; |
| |
| // The file or directory we're supposed to watch. |
| FilePath target_; |
| |
| Type type_ = Type::kNonRecursive; |
| bool report_modified_path_ = false; |
| |
| // The vector of watches and next component names for all path components, |
| // starting at the root directory. The last entry corresponds to the watch for |
| // |target_| and always stores an empty next component name in |subdir|. |
| std::vector<WatchEntry> watches_; |
| |
| std::unordered_map<InotifyReader::Watch, FilePath> recursive_paths_by_watch_; |
| std::map<FilePath, InotifyReader::Watch> recursive_watches_by_path_; |
| |
| WeakPtrFactory<FilePathWatcherImpl> weak_factory_{this}; |
| }; |
| |
| LazyInstance<InotifyReader>::Leaky g_inotify_reader = LAZY_INSTANCE_INITIALIZER; |
| |
| void InotifyReaderThreadDelegate::ThreadMain() { |
| PlatformThread::SetName("inotify_reader"); |
| |
| std::array<pollfd, 1> fdarray{{{inotify_fd_, POLLIN, 0}}}; |
| |
| while (true) { |
| // Wait until some inotify events are available. |
| int poll_result = HANDLE_EINTR(poll(fdarray.data(), fdarray.size(), -1)); |
| if (poll_result < 0) { |
| DPLOG(WARNING) << "poll failed"; |
| return; |
| } |
| |
| // Adjust buffer size to current event queue size. |
| int buffer_size; |
| int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size)); |
| |
| if (ioctl_result != 0 || buffer_size < 0) { |
| DPLOG(WARNING) << "ioctl failed"; |
| return; |
| } |
| |
| std::vector<char> buffer(static_cast<size_t>(buffer_size)); |
| |
| ssize_t bytes_read = HANDLE_EINTR( |
| read(inotify_fd_, buffer.data(), static_cast<size_t>(buffer_size))); |
| |
| if (bytes_read < 0) { |
| DPLOG(WARNING) << "read from inotify fd failed"; |
| return; |
| } |
| |
| for (size_t i = 0; i < static_cast<size_t>(bytes_read);) { |
| inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]); |
| size_t event_size = sizeof(inotify_event) + event->len; |
| DCHECK(i + event_size <= static_cast<size_t>(bytes_read)); |
| g_inotify_reader.Get().OnInotifyEvent(event); |
| i += event_size; |
| } |
| } |
| } |
| |
| InotifyReader::InotifyReader() |
| : inotify_fd_(inotify_init()), thread_delegate_(inotify_fd_) { |
| if (inotify_fd_ < 0) { |
| PLOG(ERROR) << "inotify_init() failed"; |
| return; |
| } |
| |
| if (!StartThread()) |
| return; |
| |
| valid_ = true; |
| } |
| |
| bool InotifyReader::StartThread() { |
| // This object is LazyInstance::Leaky, so thread_delegate_ will outlive the |
| // thread. |
| return PlatformThread::CreateNonJoinable(0, &thread_delegate_); |
| } |
| |
| InotifyReader::Watch InotifyReader::AddWatch(const FilePath& path, |
| FilePathWatcherImpl* watcher) { |
| if (!valid_) |
| return kInvalidWatch; |
| |
| if (watcher->WouldExceedWatchLimit()) |
| return kWatchLimitExceeded; |
| |
| AutoLock auto_lock(lock_); |
| |
| ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::WILL_BLOCK); |
| const int watch_int = |
| inotify_add_watch(inotify_fd_, path.value().c_str(), |
| IN_ATTRIB | IN_CREATE | IN_DELETE | IN_CLOSE_WRITE | |
| IN_MOVE | IN_ONLYDIR); |
| if (watch_int == -1) |
| return kInvalidWatch; |
| const Watch watch = static_cast<Watch>(watch_int); |
| |
| watchers_[watch].emplace(std::make_pair(watcher, watcher->GetWatcherEntry())); |
| |
| return watch; |
| } |
| |
| void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) { |
| if (!valid_ || (watch == kInvalidWatch)) |
| return; |
| |
| AutoLock auto_lock(lock_); |
| |
| auto watchers_it = watchers_.find(watch); |
| if (watchers_it == watchers_.end()) |
| return; |
| |
| auto& watcher_map = watchers_it->second; |
| watcher_map.erase(watcher); |
| |
| if (watcher_map.empty()) { |
| watchers_.erase(watchers_it); |
| |
| ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| BlockingType::WILL_BLOCK); |
| inotify_rm_watch(inotify_fd_, watch); |
| } |
| } |
| |
| void InotifyReader::OnInotifyEvent(const inotify_event* event) { |
| if (event->mask & IN_IGNORED) |
| return; |
| |
| FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); |
| AutoLock auto_lock(lock_); |
| |
| // In racing conditions, RemoveWatch() could grab `lock_` first and remove |
| // the entry for `event->wd`. |
| auto watchers_it = watchers_.find(static_cast<Watch>(event->wd)); |
| if (watchers_it == watchers_.end()) |
| return; |
| |
| auto& watcher_map = watchers_it->second; |
| for (const auto& entry : watcher_map) { |
| auto& watcher_entry = entry.second; |
| watcher_entry.task_runner->PostTask( |
| FROM_HERE, |
| BindOnce(&FilePathWatcherImpl::OnFilePathChanged, watcher_entry.watcher, |
| static_cast<Watch>(event->wd), child, |
| event->mask & (IN_CREATE | IN_MOVED_TO), |
| event->mask & (IN_DELETE | IN_MOVED_FROM), |
| event->mask & IN_ISDIR)); |
| } |
| } |
| |
| bool InotifyReader::HasWatches() { |
| AutoLock auto_lock(lock_); |
| |
| return !watchers_.empty(); |
| } |
| |
| FilePathWatcherImpl::FilePathWatcherImpl() = default; |
| |
| FilePathWatcherImpl::~FilePathWatcherImpl() { |
| DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence()); |
| } |
| |
| void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch, |
| const FilePath::StringType& child, |
| bool created, |
| bool deleted, |
| bool is_dir) { |
| DCHECK(task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(!watches_.empty()); |
| DCHECK(HasValidWatchVector()); |
| |
| // Used below to avoid multiple recursive updates. |
| bool did_update = false; |
| |
| // Whether kWatchLimitExceeded is encountered during update. |
| bool exceeded_limit = false; |
| |
| // Find the entries in |watches_| that correspond to |fired_watch|. |
| for (size_t i = 0; i < watches_.size(); ++i) { |
| const WatchEntry& watch_entry = watches_[i]; |
| if (fired_watch != watch_entry.watch) |
| continue; |
| |
| // Check whether a path component of |target_| changed. |
| bool change_on_target_path = child.empty() || |
| (child == watch_entry.linkname) || |
| (child == watch_entry.subdir); |
| |
| // Check if the change references |target_| or a direct child of |target_|. |
| bool target_changed; |
| if (watch_entry.subdir.empty()) { |
| // The fired watch is for a WatchEntry without a subdir. Thus for a given |
| // |target_| = "/path/to/foo", this is for "foo". Here, check either: |
| // - the target has no symlink: it is the target and it changed. |
| // - the target has a symlink, and it matches |child|. |
| target_changed = |
| (watch_entry.linkname.empty() || child == watch_entry.linkname); |
| } else { |
| // The fired watch is for a WatchEntry with a subdir. Thus for a given |
| // |target_| = "/path/to/foo", this is for {"/", "/path", "/path/to"}. |
| // So we can safely access the next WatchEntry since we have not reached |
| // the end yet. Check |watch_entry| is for "/path/to", i.e. the next |
| // element is "foo". |
| bool next_watch_may_be_for_target = watches_[i + 1].subdir.empty(); |
| if (next_watch_may_be_for_target) { |
| // The current |watch_entry| is for "/path/to", so check if the |child| |
| // that changed is "foo". |
| target_changed = watch_entry.subdir == child; |
| } else { |
| // The current |watch_entry| is not for "/path/to", so the next entry |
| // cannot be "foo". Thus |target_| has not changed. |
| target_changed = false; |
| } |
| } |
| |
| // Update watches if a directory component of the |target_| path |
| // (dis)appears. Note that we don't add the additional restriction of |
| // checking the event mask to see if it is for a directory here as changes |
| // to symlinks on the target path will not have IN_ISDIR set in the event |
| // masks. As a result we may sometimes call UpdateWatches() unnecessarily. |
| if (change_on_target_path && (created || deleted) && !did_update) { |
| if (!UpdateWatches()) { |
| exceeded_limit = true; |
| break; |
| } |
| did_update = true; |
| } |
| |
| // Report the following events: |
| // - The target or a direct child of the target got changed (in case the |
| // watched path refers to a directory). |
| // - One of the parent directories got moved or deleted, since the target |
| // disappears in this case. |
| // - One of the parent directories appears. The event corresponding to |
| // the target appearing might have been missed in this case, so recheck. |
| if (target_changed || (change_on_target_path && deleted) || |
| (change_on_target_path && created && PathExists(target_))) { |
| if (!did_update) { |
| if (!UpdateRecursiveWatches(fired_watch, is_dir)) { |
| exceeded_limit = true; |
| break; |
| } |
| did_update = true; |
| } |
| if (report_modified_path_ && !change_on_target_path) { |
| callback_.Run(target_.Append(child), |
| /*error=*/false); // `this` may be deleted. |
| } else { |
| callback_.Run(target_, /*error=*/false); // `this` may be deleted. |
| } |
| return; |
| } |
| } |
| |
| if (!exceeded_limit && Contains(recursive_paths_by_watch_, fired_watch)) { |
| if (!did_update) { |
| if (!UpdateRecursiveWatches(fired_watch, is_dir)) |
| exceeded_limit = true; |
| } |
| if (!exceeded_limit) { |
| if (report_modified_path_) { |
| callback_.Run(recursive_paths_by_watch_[fired_watch].Append(child), |
| /*error=*/false); // `this` may be deleted. |
| } else { |
| callback_.Run(target_, /*error=*/false); // `this` may be deleted. |
| } |
| return; |
| } |
| } |
| |
| if (exceeded_limit) { |
| // Cancels all in-flight events from inotify thread. |
| weak_factory_.InvalidateWeakPtrs(); |
| |
| // Reset states and cancels all watches. |
| auto callback = callback_; |
| Cancel(); |
| |
| // Fires the "error=true" callback. |
| callback.Run(target_, /*error=*/true); // `this` may be deleted. |
| } |
| } |
| |
| bool FilePathWatcherImpl::WouldExceedWatchLimit() const { |
| DCHECK(task_runner()->RunsTasksInCurrentSequence()); |
| |
| // `watches_` contains inotify watches of all dir components of `target_`. |
| // `recursive_paths_by_watch_` contains inotify watches for sub dirs under |
| // `target_` of a Type::kRecursive watcher and keyed by inotify watches. |
| // All inotify watches used by this FilePathWatcherImpl are either in |
| // `watches_` or as a key in `recursive_paths_by_watch_`. As a result, the |
| // two provide a good estimate on the number of inofiy watches used by this |
| // FilePathWatcherImpl. |
| const size_t number_of_inotify_watches = |
| watches_.size() + recursive_paths_by_watch_.size(); |
| return number_of_inotify_watches >= GetMaxNumberOfInotifyWatches(); |
| } |
| |
| InotifyReader::WatcherEntry FilePathWatcherImpl::GetWatcherEntry() { |
| DCHECK(task_runner()->RunsTasksInCurrentSequence()); |
| return {task_runner(), weak_factory_.GetWeakPtr()}; |
| } |
| |
| bool FilePathWatcherImpl::Watch(const FilePath& path, |
| Type type, |
| const FilePathWatcher::Callback& callback) { |
| DCHECK(target_.empty()); |
| |
| set_task_runner(SequencedTaskRunner::GetCurrentDefault()); |
| callback_ = callback; |
| target_ = path; |
| type_ = type; |
| |
| std::vector<FilePath::StringType> comps = target_.GetComponents(); |
| DCHECK(!comps.empty()); |
| for (size_t i = 1; i < comps.size(); ++i) |
| watches_.emplace_back(comps[i]); |
| watches_.emplace_back(FilePath::StringType()); |
| |
| if (!UpdateWatches()) { |
| Cancel(); |
| // Note `callback` is not invoked since false is returned. |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool FilePathWatcherImpl::WatchWithOptions( |
| const FilePath& path, |
| const WatchOptions& options, |
| const FilePathWatcher::Callback& callback) { |
| report_modified_path_ = options.report_modified_path; |
| return Watch(path, options.type, callback); |
| } |
| |
| void FilePathWatcherImpl::Cancel() { |
| if (!callback_) { |
| // Watch() was never called. |
| set_cancelled(); |
| return; |
| } |
| |
| DCHECK(task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(!is_cancelled()); |
| |
| set_cancelled(); |
| callback_.Reset(); |
| |
| for (const auto& watch : watches_) |
| g_inotify_reader.Get().RemoveWatch(watch.watch, this); |
| watches_.clear(); |
| target_.clear(); |
| RemoveRecursiveWatches(); |
| } |
| |
| bool FilePathWatcherImpl::UpdateWatches() { |
| // Ensure this runs on the task_runner() exclusively in order to avoid |
| // concurrency issues. |
| DCHECK(task_runner()->RunsTasksInCurrentSequence()); |
| DCHECK(HasValidWatchVector()); |
| |
| // Walk the list of watches and update them as we go. |
| FilePath path(FILE_PATH_LITERAL("/")); |
| for (WatchEntry& watch_entry : watches_) { |
| InotifyReader::Watch old_watch = watch_entry.watch; |
| watch_entry.watch = InotifyReader::kInvalidWatch; |
| watch_entry.linkname.clear(); |
| watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this); |
| if (watch_entry.watch == InotifyReader::kWatchLimitExceeded) |
| return false; |
| if (watch_entry.watch == InotifyReader::kInvalidWatch) { |
| // Ignore the error code (beyond symlink handling) to attempt to add |
| // watches on accessible children of unreadable directories. Note that |
| // this is a best-effort attempt; we may not catch events in this |
| // scenario. |
| if (IsLink(path)) { |
| if (!AddWatchForBrokenSymlink(path, &watch_entry)) |
| return false; |
| } |
| } |
| if (old_watch != watch_entry.watch) |
| g_inotify_reader.Get().RemoveWatch(old_watch, this); |
| path = path.Append(watch_entry.subdir); |
| } |
| |
| return UpdateRecursiveWatches(InotifyReader::kInvalidWatch, /*is_dir=*/false); |
| } |
| |
| bool FilePathWatcherImpl::UpdateRecursiveWatches( |
| InotifyReader::Watch fired_watch, |
| bool is_dir) { |
| DCHECK(HasValidWatchVector()); |
| |
| if (type_ != Type::kRecursive) |
| return true; |
| |
| if (!DirectoryExists(target_)) { |
| RemoveRecursiveWatches(); |
| return true; |
| } |
| |
| // Check to see if this is a forced update or if some component of |target_| |
| // has changed. For these cases, redo the watches for |target_| and below. |
| if (!Contains(recursive_paths_by_watch_, fired_watch) && |
| fired_watch != watches_.back().watch) { |
| return UpdateRecursiveWatchesForPath(target_); |
| } |
| |
| // Underneath |target_|, only directory changes trigger watch updates. |
| if (!is_dir) |
| return true; |
| |
| const FilePath& changed_dir = Contains(recursive_paths_by_watch_, fired_watch) |
| ? recursive_paths_by_watch_[fired_watch] |
| : target_; |
| |
| auto start_it = recursive_watches_by_path_.upper_bound(changed_dir); |
| auto end_it = start_it; |
| for (; end_it != recursive_watches_by_path_.end(); ++end_it) { |
| const FilePath& cur_path = end_it->first; |
| if (!changed_dir.IsParent(cur_path)) |
| break; |
| |
| // There could be a race when another process is changing contents under |
| // `changed_dir` while chrome is watching (e.g. an Android app updating |
| // a dir with Chrome OS file manager open for the dir). In such case, |
| // `cur_dir` under `changed_dir` could exist in this loop but not in |
| // the FileEnumerator loop in the upcoming UpdateRecursiveWatchesForPath(), |
| // As a result, `g_inotify_reader` would have an entry in its `watchers_` |
| // pointing to `this` but `this` is no longer aware of that. Crash in |
| // http://crbug/990004 could happen later. |
| // |
| // Remove the watcher of `cur_path` regardless of whether it exists |
| // or not to keep `this` and `g_inotify_reader` consistent even when the |
| // race happens. The watcher will be added back if `cur_path` exists in |
| // the FileEnumerator loop in UpdateRecursiveWatchesForPath(). |
| g_inotify_reader.Get().RemoveWatch(end_it->second, this); |
| |
| // Keep it in sync with |recursive_watches_by_path_| crbug.com/995196. |
| recursive_paths_by_watch_.erase(end_it->second); |
| } |
| recursive_watches_by_path_.erase(start_it, end_it); |
| return UpdateRecursiveWatchesForPath(changed_dir); |
| } |
| |
| bool FilePathWatcherImpl::UpdateRecursiveWatchesForPath(const FilePath& path) { |
| DCHECK_EQ(type_, Type::kRecursive); |
| DCHECK(!path.empty()); |
| DCHECK(DirectoryExists(path)); |
| |
| // Note: SHOW_SYM_LINKS exposes symlinks as symlinks, so they are ignored |
| // rather than followed. Following symlinks can easily lead to the undesirable |
| // situation where the entire file system is being watched. |
| FileEnumerator enumerator( |
| path, true /* recursive enumeration */, |
| FileEnumerator::DIRECTORIES | FileEnumerator::SHOW_SYM_LINKS); |
| for (FilePath current = enumerator.Next(); !current.empty(); |
| current = enumerator.Next()) { |
| DCHECK(enumerator.GetInfo().IsDirectory()); |
| |
| if (!Contains(recursive_watches_by_path_, current)) { |
| // Add new watches. |
| InotifyReader::Watch watch = |
| g_inotify_reader.Get().AddWatch(current, this); |
| if (watch == InotifyReader::kWatchLimitExceeded) |
| return false; |
| TrackWatchForRecursion(watch, current); |
| } else { |
| // Update existing watches. |
| InotifyReader::Watch old_watch = recursive_watches_by_path_[current]; |
| DCHECK_NE(InotifyReader::kInvalidWatch, old_watch); |
| InotifyReader::Watch watch = |
| g_inotify_reader.Get().AddWatch(current, this); |
| if (watch == InotifyReader::kWatchLimitExceeded) |
| return false; |
| if (watch != old_watch) { |
| g_inotify_reader.Get().RemoveWatch(old_watch, this); |
| recursive_paths_by_watch_.erase(old_watch); |
| recursive_watches_by_path_.erase(current); |
| TrackWatchForRecursion(watch, current); |
| } |
| } |
| } |
| return true; |
| } |
| |
| void FilePathWatcherImpl::TrackWatchForRecursion(InotifyReader::Watch watch, |
| const FilePath& path) { |
| DCHECK_EQ(type_, Type::kRecursive); |
| DCHECK(!path.empty()); |
| DCHECK(target_.IsParent(path)); |
| |
| if (watch == InotifyReader::kInvalidWatch) |
| return; |
| |
| DCHECK(!Contains(recursive_paths_by_watch_, watch)); |
| DCHECK(!Contains(recursive_watches_by_path_, path)); |
| recursive_paths_by_watch_[watch] = path; |
| recursive_watches_by_path_[path] = watch; |
| } |
| |
| void FilePathWatcherImpl::RemoveRecursiveWatches() { |
| if (type_ != Type::kRecursive) |
| return; |
| |
| for (const auto& it : recursive_paths_by_watch_) |
| g_inotify_reader.Get().RemoveWatch(it.first, this); |
| |
| recursive_paths_by_watch_.clear(); |
| recursive_watches_by_path_.clear(); |
| } |
| |
| bool FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path, |
| WatchEntry* watch_entry) { |
| #if BUILDFLAG(IS_FUCHSIA) |
| // Fuchsia does not support symbolic links. |
| return false; |
| #else // BUILDFLAG(IS_FUCHSIA) |
| DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch); |
| absl::optional<FilePath> link = ReadSymbolicLinkAbsolute(path); |
| if (!link) { |
| return true; |
| } |
| DCHECK(link->IsAbsolute()); |
| |
| // Try watching symlink target directory. If the link target is "/", then we |
| // shouldn't get here in normal situations and if we do, we'd watch "/" for |
| // changes to a component "/" which is harmless so no special treatment of |
| // this case is required. |
| InotifyReader::Watch watch = |
| g_inotify_reader.Get().AddWatch(link->DirName(), this); |
| if (watch == InotifyReader::kWatchLimitExceeded) |
| return false; |
| if (watch == InotifyReader::kInvalidWatch) { |
| // TODO(craig) Symlinks only work if the parent directory for the target |
| // exist. Ideally we should make sure we've watched all the components of |
| // the symlink path for changes. See crbug.com/91561 for details. |
| DPLOG(WARNING) << "Watch failed for " << link->DirName().value(); |
| return true; |
| } |
| watch_entry->watch = watch; |
| watch_entry->linkname = link->BaseName().value(); |
| return true; |
| #endif // BUILDFLAG(IS_FUCHSIA) |
| } |
| |
| bool FilePathWatcherImpl::HasValidWatchVector() const { |
| if (watches_.empty()) |
| return false; |
| for (size_t i = 0; i < watches_.size() - 1; ++i) { |
| if (watches_[i].subdir.empty()) |
| return false; |
| } |
| return watches_.back().subdir.empty(); |
| } |
| |
| } // namespace |
| |
| size_t GetMaxNumberOfInotifyWatches() { |
| #if BUILDFLAG(IS_FUCHSIA) |
| // Fuchsia has no limit on the number of watches. |
| return std::numeric_limits<int>::max(); |
| #else |
| static const size_t max = []() { |
| size_t max_number_of_inotify_watches = 0u; |
| |
| std::ifstream in(kInotifyMaxUserWatchesPath); |
| if (!in.is_open() || !(in >> max_number_of_inotify_watches)) { |
| LOG(ERROR) << "Failed to read " << kInotifyMaxUserWatchesPath; |
| return kDefaultInotifyMaxUserWatches / kExpectedFilePathWatchers; |
| } |
| |
| return max_number_of_inotify_watches / kExpectedFilePathWatchers; |
| }(); |
| return g_override_max_inotify_watches ? g_override_max_inotify_watches : max; |
| #endif // if BUILDFLAG(IS_FUCHSIA) |
| } |
| |
| ScopedMaxNumberOfInotifyWatchesOverrideForTest:: |
| ScopedMaxNumberOfInotifyWatchesOverrideForTest(size_t override_max) { |
| DCHECK_EQ(g_override_max_inotify_watches, 0u); |
| g_override_max_inotify_watches = override_max; |
| } |
| |
| ScopedMaxNumberOfInotifyWatchesOverrideForTest:: |
| ~ScopedMaxNumberOfInotifyWatchesOverrideForTest() { |
| g_override_max_inotify_watches = 0u; |
| } |
| |
| FilePathWatcher::FilePathWatcher() { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| impl_ = std::make_unique<FilePathWatcherImpl>(); |
| } |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // Put inside "BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)" because Android |
| // includes file_path_watcher_linux.cc. |
| |
| // static |
| bool FilePathWatcher::HasWatchesForTest() { |
| return g_inotify_reader.Get().HasWatches(); |
| } |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| } // namespace base |