Index: base/files/file_path_watcher_linux.cc |
diff --git a/base/files/file_path_watcher_linux.cc b/base/files/file_path_watcher_linux.cc |
index 265366b144779530b89f9464d00e2cd6bc9d8e80..2dfd54bd9022b1ee6812a8a5463e8ed08c37cde0 100644 |
--- a/base/files/file_path_watcher_linux.cc |
+++ b/base/files/file_path_watcher_linux.cc |
@@ -119,7 +119,8 @@ class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, |
// 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. |
+ // 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), |
@@ -127,6 +128,7 @@ class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, |
InotifyReader::Watch watch_; |
FilePath::StringType subdir_; |
+ FilePath::StringType linkname_; |
}; |
typedef std::vector<WatchEntry> WatchVector; |
@@ -328,41 +330,47 @@ void FilePathWatcherImpl::OnFilePathChanged( |
// 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_) |
- break; |
- } |
- |
- // If this notification is from a previous generation of watches or the watch |
- // has been cancelled (|watches_| is empty then), bail out. |
- if (watch_entry == watches_.end()) |
- return; |
- |
- // Check whether a path component of |target_| changed. |
- bool change_on_target_path = child.empty() || child == watch_entry->subdir_; |
- |
- // 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() || |
- (watch_entry->subdir_ == child && (++watch_entry)->subdir_.empty()); |
+ 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_); |
+ |
+ // 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()); |
+ |
+ // Update watches if a directory component of the |target_| path |
+ // (dis)appears. Note that we don't add the additional restriction |
+ // of checking is_directory here as changes to symlinks on the |
+ // target path will not have is_directory set but as a result we |
+ // may sometimes call UpdateWatches unnecessarily. |
+ if (change_on_target_path && !UpdateWatches()) { |
+ delegate_->OnFilePathError(target_); |
+ return; |
+ } |
- // Update watches if a directory component of the |target_| path (dis)appears. |
- if (is_directory && change_on_target_path && !UpdateWatches()) { |
- delegate_->OnFilePathError(target_); |
- 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 && file_util::PathExists(target_))) { |
+ delegate_->OnFilePathChanged(target_); |
+ 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 && file_util::PathExists(target_))) { |
- delegate_->OnFilePathChanged(target_); |
- } |
} |
bool FilePathWatcherImpl::Watch(const FilePath& path, |
@@ -438,6 +446,28 @@ bool FilePathWatcherImpl::UpdateWatches() { |
InotifyReader::Watch old_watch = watch_entry->watch_; |
if (path_valid) { |
watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this); |
+ if ((watch_entry->watch_ == InotifyReader::kInvalidWatch) && |
+ file_util::IsLink(path)) { |
+ FilePath link; |
+ file_util::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. |
+ } |
+ } |
if (watch_entry->watch_ == InotifyReader::kInvalidWatch) { |
path_valid = false; |
} |