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); |
Lei Zhang
2014/04/25 06:45:23
Nobody checks the return result.
|
+ // 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,25 +110,21 @@ |
// 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) |
Lei Zhang
2014/04/25 06:45:23
Always called with |watch| = -1
|
- : watch_(watch), |
+ explicit WatchEntry(const FilePath::StringType& subdir) |
+ : watch_(InotifyReader::kInvalidWatch), |
subdir_(subdir) {} |
InotifyReader::Watch watch_; |
@@ -137,6 +137,13 @@ |
// that exists. Updates |watched_path_|. Returns true on success. |
bool UpdateWatches() WARN_UNUSED_RESULT; |
+ // If |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_; |
@@ -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)) |
Lei Zhang
2014/04/25 06:45:23
Slightly easier to check for invalid |watch| here
|
+ return; |
AutoLock auto_lock(lock_); |
@@ -278,17 +284,15 @@ |
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) { |
if (event->mask & IN_IGNORED) |
return; |
- FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); |
+ FilePath::StringType child(event->len ? event->name : ""); |
AutoLock auto_lock(lock_); |
for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); |
@@ -307,61 +311,68 @@ |
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. |
+ // Happens when code flow reaches here from the PostTask() above. |
+ if (watches_.empty()) { |
Lei Zhang
2014/04/25 06:45:23
Only needed because we call DCHECK(HasValidWatchVe
|
+ 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() || |
Lei Zhang
2014/04/25 06:45:23
No longer needed since we already validated |watch
|
- (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_) || |
Lei Zhang
2014/04/25 06:45:23
I reordered the checks to avoid checking for linkn
|
+ (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_direct_child = watch_entry.subdir_.empty(); |
+ bool target_changed = |
+ (is_direct_child && (child == watch_entry.linkname_)) || |
+ (is_direct_child && 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 +395,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 +437,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("/")); |
+ FilePath path("/"); |
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(); |
Lei Zhang
2014/04/25 06:45:23
This is one actual change to the functionality. Wh
|
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) { |
+ path_valid = AddWatchForBrokenSymlink(path, &watch_entry); |
} |
- 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_); |
+ if (!IsLink(path)) |
+ return false; |
+ |
+ 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() { |