Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(999)

Unified Diff: base/directory_watcher_inotify.cc

Issue 92032: Add support for almost-recursive watches in Linux DirectoryWatcher (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: '' Created 11 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | base/directory_watcher_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/directory_watcher_inotify.cc
===================================================================
--- base/directory_watcher_inotify.cc (revision 14201)
+++ base/directory_watcher_inotify.cc (working copy)
@@ -6,8 +6,8 @@
#include <errno.h>
#include <string.h>
+#include <sys/inotify.h>
#include <sys/ioctl.h>
-#include <sys/inotify.h>
#include <sys/select.h>
#include <unistd.h>
@@ -17,6 +17,7 @@
#include <vector>
#include "base/file_path.h"
+#include "base/file_util.h"
#include "base/hash_tables.h"
#include "base/lock.h"
#include "base/logging.h"
@@ -28,23 +29,20 @@
namespace {
+class DirectoryWatcherImpl;
+
// Singleton to manage all inotify watches.
class InotifyReader {
public:
typedef int Watch; // Watch descriptor used by AddWatch and RemoveWatch.
static const Watch kInvalidWatch = -1;
- // Watch |path| for changes. |delegate| will be notified on each change. Does
- // not check for duplicates. If you call it n times with same |path|
- // and |delegate|, it will receive n notifications for each change
- // in |path|. It makes implementation of DirectoryWatcher simple.
+ // Watch |path| for changes. |watcher| will be notified on each change.
// Returns kInvalidWatch on failure.
- Watch AddWatch(const FilePath& path, DirectoryWatcher::Delegate* delegate);
+ Watch AddWatch(const FilePath& path, DirectoryWatcherImpl* watcher);
- // Remove |watch| for |delegate|. If you had n watches for same |delegate|
- // and path, after calling this function you will have n - 1.
- // Returns true on success.
- bool RemoveWatch(Watch watch, DirectoryWatcher::Delegate* delegate);
+ // Remove |watch|. Returns true on success.
+ bool RemoveWatch(Watch watch, DirectoryWatcherImpl* watcher);
// Callback for InotifyReaderTask.
void OnInotifyEvent(inotify_event* event);
@@ -52,16 +50,13 @@
private:
friend struct DefaultSingletonTraits<InotifyReader>;
- typedef std::pair<DirectoryWatcher::Delegate*, MessageLoop*> DelegateInfo;
- typedef std::multiset<DelegateInfo> DelegateSet;
+ typedef std::set<DirectoryWatcherImpl*> WatcherSet;
InotifyReader();
~InotifyReader();
// We keep track of which delegates want to be notified on which watches.
- // Multiset is used because there may be many DirectoryWatchers for same path
- // and delegate.
- base::hash_map<Watch, DelegateSet> delegates_;
+ base::hash_map<Watch, WatcherSet> watchers_;
// For each watch we also want to know the path it's watching.
base::hash_map<Watch, FilePath> paths_;
@@ -84,6 +79,115 @@
DISALLOW_COPY_AND_ASSIGN(InotifyReader);
};
+class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
+ public:
+
+ typedef std::set<FilePath> FilePathSet;
+
+ DirectoryWatcherImpl();
+ ~DirectoryWatcherImpl();
+
+ // Called for each event coming from one of watches.
+ void HandleInotifyEvent(inotify_event* event);
+
+ // Callback for DirectoryWatcherImplEnumeratePathTask.
+ void OnEnumeratedPath(const std::set<FilePath>& subtree);
+
+ // Start watching |path| for changes and notify |delegate| on each change.
+ // Watch entire subtree, when |recursive| is true.
+ // Returns true if watch for |path| has been added successfuly. Watches
+ // required for |recursive| are added on a background thread and have no
+ // effect on the return value.
+ virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
+ bool recursive);
+
+ private:
+ typedef std::set<InotifyReader::Watch> WatchSet;
+
+ // Returns true if |inode| is watched by DirectoryWatcherImpl.
+ bool isPathWatched(ino_t inode) const;
+
+ // Return true if watching subtree is success.
+ bool watchSubtree(const FilePathSet& subtree);
+
+ // Lock to protect attributes.
+ Lock lock_;
+
+ // Delegate to notify upon changes.
+ DirectoryWatcher::Delegate* delegate_;
+
+ // Path we're watching (passed to delegate).
+ FilePath root_path_;
+
+ // Watch returned by InotifyReader.
+ InotifyReader::Watch root_watch_;
+
+ typedef std::set<ino_t> InodeSet;
+
+ // Hold inode for paths that are watched.
+ InodeSet path_watched_;
+
+ // We need to know who is watching what inode,
+ // for cleaning up and handling events.
+ base::hash_map<ino_t, DirectoryWatcherImpl*> path_watcher_;
+
+ // Flag set to true when recursively watching subtree.
+ bool recursive_;
+
+ // Flag set to true when thread startup was successful.
+ bool path_enumerator_thread_started_;
+
+ // Seperate thread on which we enumerate directory subtree.
+ base::Thread path_enumerator_thread_;
+
+ MessageLoop* loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
+};
+
+class DirectoryWatcherImplEnumeratePathTask : public Task {
+ public:
+ DirectoryWatcherImplEnumeratePathTask(DirectoryWatcherImpl* watcher,
+ const FilePath& path)
+ :watcher_(watcher), path_(path) {}
+
+ virtual void Run() {
+ file_util::FileEnumerator dir_list(path_, true,
+ file_util::FileEnumerator::DIRECTORIES);
+
+ DirectoryWatcherImpl::FilePathSet subtree;
+ for (FilePath subdirectory = dir_list.Next();
+ !subdirectory.empty();
+ subdirectory = dir_list.Next()) {
+ subtree.insert(subdirectory);
+ }
+ watcher_->OnEnumeratedPath(subtree);
+ }
+
+ private:
+ DirectoryWatcherImpl* watcher_;
+ FilePath path_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImplEnumeratePathTask);
+};
+
+class DirectoryWatcherImplNotifyTask : public Task {
+ public:
+ DirectoryWatcherImplNotifyTask(DirectoryWatcher::Delegate* delegate,
+ const FilePath& path)
+ :delegate_(delegate), path_(path) {}
+
+ virtual void Run() {
+ delegate_->OnDirectoryChanged(path_);
+ }
+
+ private:
+ DirectoryWatcher::Delegate* delegate_;
+ FilePath path_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImplNotifyTask);
+};
+
class InotifyReaderTask : public Task {
public:
InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
@@ -152,25 +256,6 @@
DISALLOW_COPY_AND_ASSIGN(InotifyReaderTask);
};
-class InotifyReaderNotifyTask : public Task {
- public:
- InotifyReaderNotifyTask(DirectoryWatcher::Delegate* delegate,
- const FilePath& path)
- : delegate_(delegate),
- path_(path) {
- }
-
- virtual void Run() {
- delegate_->OnDirectoryChanged(path_);
- }
-
- private:
- DirectoryWatcher::Delegate* delegate_;
- FilePath path_;
-
- DISALLOW_COPY_AND_ASSIGN(InotifyReaderNotifyTask);
-};
-
InotifyReader::InotifyReader()
: thread_("inotify_reader"),
inotify_fd_(inotify_init()),
@@ -205,7 +290,8 @@
}
InotifyReader::Watch InotifyReader::AddWatch(
- const FilePath& path, DirectoryWatcher::Delegate* delegate) {
+ const FilePath& path, DirectoryWatcherImpl* watcher) {
+
if (!valid_)
return kInvalidWatch;
@@ -214,19 +300,20 @@
Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(),
IN_CREATE | IN_DELETE |
IN_CLOSE_WRITE | IN_MOVE);
+
if (watch == kInvalidWatch)
return kInvalidWatch;
if (paths_[watch].empty())
paths_[watch] = path; // We don't yet watch this path.
- delegates_[watch].insert(std::make_pair(delegate, MessageLoop::current()));
+ watchers_[watch].insert(watcher);
return watch;
}
bool InotifyReader::RemoveWatch(Watch watch,
- DirectoryWatcher::Delegate* delegate) {
+ DirectoryWatcherImpl* watcher) {
if (!valid_)
return false;
@@ -235,14 +322,11 @@
if (paths_[watch].empty())
return false; // We don't recognize this watch.
- // Only erase one occurrence of delegate (there may be more).
- delegates_[watch].erase(
- delegates_[watch].find(std::make_pair(delegate, MessageLoop::current())));
+ watchers_[watch].erase(watcher);
- if (delegates_[watch].empty()) {
+ if (watchers_[watch].empty()) {
paths_.erase(watch);
- delegates_.erase(watch);
-
+ watchers_.erase(watch);
return (inotify_rm_watch(inotify_fd_, watch) == 0);
}
@@ -253,73 +337,138 @@
if (event->mask & IN_IGNORED)
return;
- DelegateSet delegates_to_notify;
+ WatcherSet watchers_to_notify;
FilePath changed_path;
{
AutoLock auto_lock(lock_);
changed_path = paths_[event->wd];
- delegates_to_notify.insert(delegates_[event->wd].begin(),
- delegates_[event->wd].end());
+ watchers_to_notify.insert(watchers_[event->wd].begin(),
+ watchers_[event->wd].end());
}
- DelegateSet::iterator i;
- for (i = delegates_to_notify.begin(); i != delegates_to_notify.end(); ++i) {
- DirectoryWatcher::Delegate* delegate = i->first;
- MessageLoop* loop = i->second;
- loop->PostTask(FROM_HERE,
- new InotifyReaderNotifyTask(delegate, changed_path));
+ for (WatcherSet::iterator watcher = watchers_to_notify.begin();
+ watcher != watchers_to_notify.end();
+ ++watcher) {
+ (*watcher)->HandleInotifyEvent(event);
}
}
-class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate {
- public:
- DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch) {}
- ~DirectoryWatcherImpl();
+DirectoryWatcherImpl::DirectoryWatcherImpl()
+ : root_watch_(InotifyReader::kInvalidWatch),
+ path_enumerator_thread_started_(false),
+ path_enumerator_thread_("directory_enumerator"),
+ loop_(MessageLoop::current()) {}
- virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate,
- bool recursive);
+DirectoryWatcherImpl::~DirectoryWatcherImpl() {
+ if (root_watch_ == InotifyReader::kInvalidWatch)
+ return;
+ for (InodeSet::iterator i = path_watched_.begin();
+ i != path_watched_.end();
+ ++i) {
+ DirectoryWatcherImpl* delete_me = path_watcher_[*i];
+ if (Singleton<InotifyReader>::get()->RemoveWatch(delete_me->root_watch_,
+ delete_me)) {
+ delete_me->root_watch_ = InotifyReader::kInvalidWatch;
+ }
+ }
+ path_watcher_.clear();
+ path_watched_.clear();
+ if (path_enumerator_thread_started_)
+ path_enumerator_thread_.Stop();
+}
- private:
- // Delegate to notify upon changes.
- DirectoryWatcher::Delegate* delegate_;
+void DirectoryWatcherImpl::HandleInotifyEvent(inotify_event* event) {
+ loop_->PostTask(FROM_HERE,
+ new DirectoryWatcherImplNotifyTask(delegate_, root_path_));
- // Path we're watching (passed to delegate).
- FilePath path_;
+ if (!(event->mask & IN_ISDIR))
+ return;
- // Watch returned by InotifyReader.
- InotifyReader::Watch watch_;
+ if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) {
+ NOTIMPLEMENTED();
+ } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) {
+ NOTIMPLEMENTED();
+ }
+}
- DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl);
-};
+bool DirectoryWatcherImpl::isPathWatched(ino_t inode) const {
+ return path_watched_.find(inode) != path_watched_.end();
+}
-DirectoryWatcherImpl::~DirectoryWatcherImpl() {
- if (watch_ != InotifyReader::kInvalidWatch)
- Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_);
+void DirectoryWatcherImpl::OnEnumeratedPath(const FilePathSet& subtree) {
+ DCHECK(recursive_);
+ watchSubtree(subtree);
}
bool DirectoryWatcherImpl::Watch(const FilePath& path,
DirectoryWatcher::Delegate* delegate, bool recursive) {
- DCHECK(watch_ == InotifyReader::kInvalidWatch); // Can only watch one path.
- if (recursive) {
- // TODO(phajdan.jr): Support recursive watches.
- // Unfortunately inotify has no "native" support for them, but it can be
- // emulated by walking the directory tree and setting up watches for each
- // directory. Of course this is ineffective for large directory trees.
- // For the algorithm, see the link below:
- // http://osdir.com/ml/gnome.dashboard.devel/2004-10/msg00022.html
- NOTIMPLEMENTED();
+ // Can only watch one path.
+ DCHECK(root_watch_ == InotifyReader::kInvalidWatch);
+
+ ino_t inode;
+ if (!file_util::GetInode(path, &inode))
return false;
- }
+ InotifyReader::Watch watch =
+ Singleton<InotifyReader>::get()->AddWatch(path, this);
+ if (watch == InotifyReader::kInvalidWatch)
+ return false;
+
delegate_ = delegate;
- path_ = path;
- watch_ = Singleton<InotifyReader>::get()->AddWatch(path, delegate_);
+ recursive_ = recursive;
+ root_path_ = path;
+ root_watch_ = watch;
- return watch_ != InotifyReader::kInvalidWatch;
+ {
+ AutoLock auto_lock(lock_);
+ path_watched_.insert(inode);
+ path_watcher_[inode] = this;
+ }
+
+ if (recursive_) {
+ path_enumerator_thread_started_ = path_enumerator_thread_.Start();
+ if (path_enumerator_thread_started_) {
+ path_enumerator_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ new DirectoryWatcherImplEnumeratePathTask(this, root_path_));
+ }
+ }
+
+ return true;
}
+bool DirectoryWatcherImpl::watchSubtree(const FilePathSet& subtree) {
+ DCHECK(recursive_);
+
+ if (root_watch_ == InotifyReader::kInvalidWatch)
+ return false;
+
+ bool success = true;
+ AutoLock auto_lock(lock_);
+ for (FilePathSet::iterator subdirectory = subtree.begin();
+ subdirectory != subtree.end();
+ ++subdirectory) {
+ ino_t inode;
+ if (!file_util::GetInode(*subdirectory, &inode)) {
+ success = false;
+ continue;
+ }
+ if (isPathWatched(inode))
+ continue;
+ DirectoryWatcherImpl* child = new DirectoryWatcherImpl();
+ child->loop_ = loop_;
+ if (!child->Watch(*subdirectory, delegate_, false)) {
+ scoped_refptr<DirectoryWatcherImpl> delete_me = child;
+ } else {
+ path_watcher_[inode] = child;
+ path_watched_.insert(inode);
+ }
+ }
+ return success;
+}
+
} // namespace
DirectoryWatcher::DirectoryWatcher() {
« no previous file with comments | « no previous file | base/directory_watcher_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698