| Index: base/files/file_path_watcher_linux.cc
|
| ===================================================================
|
| --- base/files/file_path_watcher_linux.cc (revision 265970)
|
| +++ base/files/file_path_watcher_linux.cc (working copy)
|
| @@ -49,8 +49,8 @@
|
| // change. Returns kInvalidWatch on failure.
|
| Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher);
|
|
|
| - // Remove |watch|. Returns true on success.
|
| - bool RemoveWatch(Watch watch, FilePathWatcherImpl* watcher);
|
| + // Remove |watch| if it's valid.
|
| + void RemoveWatch(Watch watch, FilePathWatcherImpl* watcher);
|
|
|
| // Callback for InotifyReaderTask.
|
| void OnInotifyEvent(const inotify_event* event);
|
| @@ -97,6 +97,10 @@
|
| const FilePath::StringType& child,
|
| bool created);
|
|
|
| + protected:
|
| + virtual ~FilePathWatcherImpl() {}
|
| +
|
| + private:
|
| // Start watching |path| for changes and notify |delegate| on each change.
|
| // Returns true if watch for |path| has been added successfully.
|
| virtual bool Watch(const FilePath& path,
|
| @@ -106,30 +110,26 @@
|
| // Cancel the watch. This unregisters the instance with InotifyReader.
|
| virtual void Cancel() OVERRIDE;
|
|
|
| + // Cleans up and stops observing the message_loop() thread.
|
| + virtual void CancelOnMessageLoopThread() OVERRIDE;
|
| +
|
| // Deletion of the FilePathWatcher will call Cancel() to dispose of this
|
| // object in the right thread. This also observes destruction of the required
|
| // cleanup thread, in case it quits before Cancel() is called.
|
| virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
|
|
|
| - protected:
|
| - virtual ~FilePathWatcherImpl() {}
|
| -
|
| - private:
|
| - // Cleans up and stops observing the |message_loop_| thread.
|
| - virtual void CancelOnMessageLoopThread() OVERRIDE;
|
| -
|
| // Inotify watches are installed for all directory components of |target_|. A
|
| // WatchEntry instance holds the watch descriptor for a component and the
|
| // subdirectory for that identifies the next component. If a symbolic link
|
| // is being watched, the target of the link is also kept.
|
| struct WatchEntry {
|
| - WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir)
|
| - : watch_(watch),
|
| - subdir_(subdir) {}
|
| + explicit WatchEntry(const FilePath::StringType& dirname)
|
| + : watch(InotifyReader::kInvalidWatch),
|
| + subdir(dirname) {}
|
|
|
| - InotifyReader::Watch watch_;
|
| - FilePath::StringType subdir_;
|
| - FilePath::StringType linkname_;
|
| + InotifyReader::Watch watch;
|
| + FilePath::StringType subdir;
|
| + FilePath::StringType linkname;
|
| };
|
| typedef std::vector<WatchEntry> WatchVector;
|
|
|
| @@ -137,6 +137,13 @@
|
| // that exists. Updates |watched_path_|. Returns true on success.
|
| bool UpdateWatches() WARN_UNUSED_RESULT;
|
|
|
| + // |path| is a symlink to a non-existent target. Attempt to add a watch to
|
| + // the link target's parent directory. Returns true and update |watch_entry|
|
| + // on success.
|
| + bool AddWatchForBrokenSymlink(const FilePath& path, WatchEntry* watch_entry);
|
| +
|
| + bool HasValidWatchVector() const;
|
| +
|
| // Callback to notify upon changes.
|
| FilePathWatcher::Callback callback_;
|
|
|
| @@ -145,7 +152,7 @@
|
|
|
| // 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_|.
|
| + // |target_| and always stores an empty next component name in |subdir|.
|
| WatchVector watches_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
|
| @@ -224,8 +231,8 @@
|
| shutdown_pipe_[1] = -1;
|
| if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
|
| thread_.message_loop()->PostTask(
|
| - FROM_HERE, Bind(&InotifyReaderCallback, this, inotify_fd_,
|
| - shutdown_pipe_[0]));
|
| + FROM_HERE,
|
| + Bind(&InotifyReaderCallback, this, inotify_fd_, shutdown_pipe_[0]));
|
| valid_ = true;
|
| }
|
| }
|
| @@ -267,10 +274,9 @@
|
| return watch;
|
| }
|
|
|
| -bool InotifyReader::RemoveWatch(Watch watch,
|
| - FilePathWatcherImpl* watcher) {
|
| - if (!valid_)
|
| - return false;
|
| +void InotifyReader::RemoveWatch(Watch watch, FilePathWatcherImpl* watcher) {
|
| + if (!valid_ || (watch == kInvalidWatch))
|
| + return;
|
|
|
| AutoLock auto_lock(lock_);
|
|
|
| @@ -278,10 +284,8 @@
|
|
|
| if (watchers_[watch].empty()) {
|
| watchers_.erase(watch);
|
| - return (inotify_rm_watch(inotify_fd_, watch) == 0);
|
| + inotify_rm_watch(inotify_fd_, watch);
|
| }
|
| -
|
| - return true;
|
| }
|
|
|
| void InotifyReader::OnInotifyEvent(const inotify_event* event) {
|
| @@ -307,61 +311,66 @@
|
| const FilePath::StringType& child,
|
| bool created) {
|
| if (!message_loop()->BelongsToCurrentThread()) {
|
| - // Switch to message_loop_ to access watches_ safely.
|
| - message_loop()->PostTask(FROM_HERE,
|
| - Bind(&FilePathWatcherImpl::OnFilePathChanged,
|
| - this,
|
| - fired_watch,
|
| - child,
|
| - created));
|
| + // Switch to message_loop() to access |watches_| safely.
|
| + message_loop()->PostTask(
|
| + FROM_HERE,
|
| + Bind(&FilePathWatcherImpl::OnFilePathChanged, this,
|
| + fired_watch, child, created));
|
| return;
|
| }
|
|
|
| + // Check to see if CancelOnMessageLoopThread() has already been called.
|
| + // May happen when code flow reaches here from the PostTask() above.
|
| + if (watches_.empty()) {
|
| + DCHECK(target_.empty());
|
| + return;
|
| + }
|
| +
|
| DCHECK(MessageLoopForIO::current());
|
| + DCHECK(HasValidWatchVector());
|
|
|
| // Find the entry in |watches_| that corresponds to |fired_watch|.
|
| - WatchVector::const_iterator watch_entry(watches_.begin());
|
| - for ( ; watch_entry != watches_.end(); ++watch_entry) {
|
| - if (fired_watch == watch_entry->watch_) {
|
| - // Check whether a path component of |target_| changed.
|
| - bool change_on_target_path = child.empty() ||
|
| - ((child == watch_entry->subdir_) && watch_entry->linkname_.empty()) ||
|
| - (child == watch_entry->linkname_);
|
| + for (size_t i = 0; i < watches_.size(); ++i) {
|
| + const WatchEntry& watch_entry = watches_[i];
|
| + if (fired_watch != watch_entry.watch)
|
| + continue;
|
|
|
| - // Check whether the change references |target_| or a direct child.
|
| - DCHECK(watch_entry->subdir_.empty() ||
|
| - (watch_entry + 1) != watches_.end());
|
| - bool target_changed =
|
| - (watch_entry->subdir_.empty() && (child == watch_entry->linkname_)) ||
|
| - (watch_entry->subdir_.empty() && watch_entry->linkname_.empty()) ||
|
| - (watch_entry->subdir_ == child && (watch_entry + 1)->subdir_.empty());
|
| + // Check whether a path component of |target_| changed.
|
| + bool change_on_target_path =
|
| + child.empty() ||
|
| + (child == watch_entry.linkname) ||
|
| + (child == watch_entry.subdir);
|
|
|
| - // 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 && !UpdateWatches()) {
|
| - callback_.Run(target_, true /* error */);
|
| - return;
|
| - }
|
| + // Check if the change references |target_| or a direct child of |target_|.
|
| + bool is_watch_for_target = watch_entry.subdir.empty();
|
| + bool target_changed =
|
| + (is_watch_for_target && (child == watch_entry.linkname)) ||
|
| + (is_watch_for_target && watch_entry.linkname.empty()) ||
|
| + (watch_entry.subdir == child && watches_[i + 1].subdir.empty());
|
|
|
| - // 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 && !created) ||
|
| - (change_on_target_path && PathExists(target_))) {
|
| - callback_.Run(target_, false);
|
| - return;
|
| - }
|
| + // 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 && !UpdateWatches()) {
|
| + callback_.Run(target_, true /* error */);
|
| + return;
|
| }
|
| +
|
| + // 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 && !created) ||
|
| + (change_on_target_path && PathExists(target_))) {
|
| + callback_.Run(target_, false /* error */);
|
| + return;
|
| + }
|
| }
|
| }
|
|
|
| @@ -384,46 +393,39 @@
|
| std::vector<FilePath::StringType> comps;
|
| target_.GetComponents(&comps);
|
| DCHECK(!comps.empty());
|
| - std::vector<FilePath::StringType>::const_iterator comp = comps.begin();
|
| - for (++comp; comp != comps.end(); ++comp)
|
| - watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, *comp));
|
| -
|
| - watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch,
|
| - FilePath::StringType()));
|
| + for (size_t i = 1; i < comps.size(); ++i)
|
| + watches_.push_back(WatchEntry(comps[i]));
|
| + watches_.push_back(WatchEntry(FilePath::StringType()));
|
| return UpdateWatches();
|
| }
|
|
|
| void FilePathWatcherImpl::Cancel() {
|
| if (callback_.is_null()) {
|
| - // Watch was never called, or the |message_loop_| thread is already gone.
|
| + // Watch was never called, or the message_loop() thread is already gone.
|
| set_cancelled();
|
| return;
|
| }
|
|
|
| - // Switch to the message_loop_ if necessary so we can access |watches_|.
|
| + // Switch to the message_loop() if necessary so we can access |watches_|.
|
| if (!message_loop()->BelongsToCurrentThread()) {
|
| message_loop()->PostTask(FROM_HERE,
|
| Bind(&FilePathWatcher::CancelWatch,
|
| - make_scoped_refptr(this)));
|
| + make_scoped_refptr(this)));
|
| } else {
|
| CancelOnMessageLoopThread();
|
| }
|
| }
|
|
|
| void FilePathWatcherImpl::CancelOnMessageLoopThread() {
|
| - if (!is_cancelled())
|
| - set_cancelled();
|
| + set_cancelled();
|
|
|
| if (!callback_.is_null()) {
|
| MessageLoop::current()->RemoveDestructionObserver(this);
|
| callback_.Reset();
|
| }
|
|
|
| - for (WatchVector::iterator watch_entry(watches_.begin());
|
| - watch_entry != watches_.end(); ++watch_entry) {
|
| - if (watch_entry->watch_ != InotifyReader::kInvalidWatch)
|
| - g_inotify_reader.Get().RemoveWatch(watch_entry->watch_, this);
|
| - }
|
| + for (size_t i = 0; i < watches_.size(); ++i)
|
| + g_inotify_reader.Get().RemoveWatch(watches_[i].watch, this);
|
| watches_.clear();
|
| target_.clear();
|
| }
|
| @@ -433,57 +435,75 @@
|
| }
|
|
|
| bool FilePathWatcherImpl::UpdateWatches() {
|
| - // Ensure this runs on the |message_loop_| exclusively in order to avoid
|
| + // Ensure this runs on the message_loop() exclusively in order to avoid
|
| // concurrency issues.
|
| DCHECK(message_loop()->BelongsToCurrentThread());
|
| + DCHECK(HasValidWatchVector());
|
|
|
| // Walk the list of watches and update them as we go.
|
| FilePath path(FILE_PATH_LITERAL("/"));
|
| bool path_valid = true;
|
| - for (WatchVector::iterator watch_entry(watches_.begin());
|
| - watch_entry != watches_.end(); ++watch_entry) {
|
| - InotifyReader::Watch old_watch = watch_entry->watch_;
|
| + for (size_t i = 0; i < watches_.size(); ++i) {
|
| + WatchEntry& watch_entry = watches_[i];
|
| + InotifyReader::Watch old_watch = watch_entry.watch;
|
| + watch_entry.watch = InotifyReader::kInvalidWatch;
|
| + watch_entry.linkname.clear();
|
| if (path_valid) {
|
| - watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this);
|
| - if ((watch_entry->watch_ == InotifyReader::kInvalidWatch) &&
|
| - base::IsLink(path)) {
|
| - FilePath link;
|
| - if (ReadSymbolicLink(path, &link)) {
|
| - if (!link.IsAbsolute())
|
| - link = path.DirName().Append(link);
|
| - // 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.
|
| - watch_entry->watch_ =
|
| - g_inotify_reader.Get().AddWatch(link.DirName(), this);
|
| - if (watch_entry->watch_ != InotifyReader::kInvalidWatch) {
|
| - watch_entry->linkname_ = link.BaseName().value();
|
| - } else {
|
| - DPLOG(WARNING) << "Watch failed for " << link.DirName().value();
|
| - // 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.
|
| - }
|
| + watch_entry.watch = g_inotify_reader.Get().AddWatch(path, this);
|
| + if (watch_entry.watch == InotifyReader::kInvalidWatch) {
|
| + if (IsLink(path)) {
|
| + path_valid = AddWatchForBrokenSymlink(path, &watch_entry);
|
| + } else {
|
| + path_valid = false;
|
| }
|
| }
|
| - if (watch_entry->watch_ == InotifyReader::kInvalidWatch) {
|
| - path_valid = false;
|
| - }
|
| - } else {
|
| - watch_entry->watch_ = InotifyReader::kInvalidWatch;
|
| }
|
| - if (old_watch != InotifyReader::kInvalidWatch &&
|
| - old_watch != watch_entry->watch_) {
|
| + if (old_watch != watch_entry.watch)
|
| g_inotify_reader.Get().RemoveWatch(old_watch, this);
|
| - }
|
| - path = path.Append(watch_entry->subdir_);
|
| + path = path.Append(watch_entry.subdir);
|
| }
|
|
|
| return true;
|
| }
|
|
|
| +bool FilePathWatcherImpl::AddWatchForBrokenSymlink(const FilePath& path,
|
| + WatchEntry* watch_entry) {
|
| + DCHECK_EQ(InotifyReader::kInvalidWatch, watch_entry->watch);
|
| + FilePath link;
|
| + if (!ReadSymbolicLink(path, &link))
|
| + return false;
|
| +
|
| + if (!link.IsAbsolute())
|
| + link = path.DirName().Append(link);
|
| +
|
| + // 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::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 false;
|
| + }
|
| + watch_entry->watch = watch;
|
| + watch_entry->linkname = link.BaseName().value();
|
| + return true;
|
| +}
|
| +
|
| +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_[watches_.size() - 1].subdir.empty();
|
| +}
|
| +
|
| } // namespace
|
|
|
| FilePathWatcher::FilePathWatcher() {
|
|
|