summaryrefslogtreecommitdiffstats
path: root/base/directory_watcher_inotify.cc
diff options
context:
space:
mode:
authorphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-18 14:35:58 +0000
committerphajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-18 14:35:58 +0000
commit34c70c9f8f89e5293cc9c6de9742adfbba5810bf (patch)
tree0da65c6ac92bdb7f2f68193a61efb93e8a4646b1 /base/directory_watcher_inotify.cc
parentb8096e69fa183c6facc254b7c7b78cb5099dd770 (diff)
downloadchromium_src-34c70c9f8f89e5293cc9c6de9742adfbba5810bf.zip
chromium_src-34c70c9f8f89e5293cc9c6de9742adfbba5810bf.tar.gz
chromium_src-34c70c9f8f89e5293cc9c6de9742adfbba5810bf.tar.bz2
Port DirectoryWatcher to Linux using inotify.
Review URL: http://codereview.chromium.org/42037 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@11962 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/directory_watcher_inotify.cc')
-rw-r--r--base/directory_watcher_inotify.cc329
1 files changed, 329 insertions, 0 deletions
diff --git a/base/directory_watcher_inotify.cc b/base/directory_watcher_inotify.cc
new file mode 100644
index 0000000..7e906fa
--- /dev/null
+++ b/base/directory_watcher_inotify.cc
@@ -0,0 +1,329 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/directory_watcher.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/inotify.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/hash_tables.h"
+#include "base/lock.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "base/task.h"
+#include "base/thread.h"
+
+namespace {
+
+// 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.
+ // Returns kInvalidWatch on failure.
+ Watch AddWatch(const FilePath& path, DirectoryWatcher::Delegate* delegate);
+
+ // 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);
+
+ // Callback for InotifyReaderTask.
+ void OnInotifyEvent(inotify_event* event);
+
+ private:
+ friend struct DefaultSingletonTraits<InotifyReader>;
+
+ typedef std::pair<DirectoryWatcher::Delegate*, MessageLoop*> DelegateInfo;
+ typedef std::multiset<DelegateInfo> DelegateSet;
+
+ 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_;
+
+ // For each watch we also want to know the path it's watching.
+ base::hash_map<Watch, FilePath> paths_;
+
+ // Lock to protect delegates_ and paths_.
+ Lock lock_;
+
+ // Separate thread on which we run blocking read for inotify events.
+ base::Thread thread_;
+
+ // File descriptor returned by inotify_init.
+ const int inotify_fd_;
+
+ // Use self-pipe trick to unblock select during shutdown.
+ int shutdown_pipe_[2];
+
+ // Flag set to true when startup was successful.
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(InotifyReader);
+};
+
+class InotifyReaderTask : public Task {
+ public:
+ InotifyReaderTask(InotifyReader* reader, int inotify_fd, int shutdown_fd)
+ : reader_(reader),
+ inotify_fd_(inotify_fd),
+ shutdown_fd_(shutdown_fd) {
+ }
+
+ virtual void Run() {
+ while (true) {
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(inotify_fd_, &rfds);
+ FD_SET(shutdown_fd_, &rfds);
+
+ // Wait until some inotify events are available.
+ int select_result = select(std::max(inotify_fd_, shutdown_fd_) + 1,
+ &rfds, NULL, NULL, NULL);
+ if (select_result < 0) {
+ if (errno == EINTR)
+ continue;
+ DLOG(WARNING) << "select failed: " << strerror(errno);
+ return;
+ }
+
+ if (FD_ISSET(shutdown_fd_, &rfds))
+ return;
+
+ // Adjust buffer size to current event queue size.
+ int buffer_size;
+ int ioctl_result = ioctl(inotify_fd_, FIONREAD, &buffer_size);
+
+ if (ioctl_result != 0) {
+ DLOG(WARNING) << "ioctl failed: " << strerror(errno);
+ return;
+ }
+
+ std::vector<char> buffer(buffer_size);
+
+ ssize_t bytes_read;
+ do {
+ bytes_read = read(inotify_fd_, &buffer[0], buffer_size);
+ } while (bytes_read < 0 && errno == EINTR);
+
+ if (bytes_read < 0) {
+ DLOG(WARNING) << "read from inotify fd failed: " << strerror(errno);
+ return;
+ }
+
+ ssize_t i = 0;
+ while (i < 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));
+ reader_->OnInotifyEvent(event);
+ i += event_size;
+ }
+ }
+ }
+
+ private:
+ InotifyReader* reader_;
+ int inotify_fd_;
+ int shutdown_fd_;
+
+ 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_;
+};
+
+InotifyReader::InotifyReader()
+ : thread_("inotify_reader"),
+ inotify_fd_(inotify_init()),
+ valid_(false) {
+ shutdown_pipe_[0] = -1;
+ shutdown_pipe_[1] = -1;
+ if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
+ thread_.message_loop()->PostTask(
+ FROM_HERE, new InotifyReaderTask(this, inotify_fd_, shutdown_pipe_[0]));
+ valid_ = true;
+ }
+}
+
+InotifyReader::~InotifyReader() {
+ if (valid_) {
+ // Write to the self-pipe so that the select call in InotifyReaderTask returns.
+ write(shutdown_pipe_[1], "", 1);
+ thread_.Stop();
+ }
+ if (inotify_fd_ >= 0)
+ close(inotify_fd_);
+ if (shutdown_pipe_[0] >= 0)
+ close(shutdown_pipe_[0]);
+ if (shutdown_pipe_[1] >= 0)
+ close(shutdown_pipe_[1]);
+}
+
+InotifyReader::Watch InotifyReader::AddWatch(
+ const FilePath& path, DirectoryWatcher::Delegate* delegate) {
+ if (!valid_)
+ return kInvalidWatch;
+
+ AutoLock auto_lock(lock_);
+
+ 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()));
+
+ return watch;
+}
+
+bool InotifyReader::RemoveWatch(Watch watch,
+ DirectoryWatcher::Delegate* delegate) {
+ if (!valid_)
+ return false;
+
+ AutoLock auto_lock(lock_);
+
+ 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())));
+
+ if (delegates_[watch].empty()) {
+ paths_.erase(watch);
+ delegates_.erase(watch);
+
+ return (inotify_rm_watch(inotify_fd_, watch) == 0);
+ }
+
+ return true;
+}
+
+void InotifyReader::OnInotifyEvent(inotify_event* event) {
+ if (event->mask & IN_IGNORED)
+ return;
+
+ DelegateSet delegates_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());
+ }
+
+ 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));
+ }
+}
+
+} // namespace
+
+// Private implementation class implementing the behavior of DirectoryWatcher.
+class DirectoryWatcher::Impl : public base::RefCounted<DirectoryWatcher::Impl> {
+ public:
+ Impl(DirectoryWatcher::Delegate* delegate)
+ : delegate_(delegate),
+ watch_(InotifyReader::kInvalidWatch) {
+ }
+
+ ~Impl();
+
+ // Register interest in any changes in |path|.
+ // Returns false on error.
+ bool Watch(const FilePath& path);
+
+ private:
+ // Delegate to notify upon changes.
+ DirectoryWatcher::Delegate* delegate_;
+
+ // Path we're watching (passed to delegate).
+ FilePath path_;
+
+ // Watch returned by InotifyReader.
+ InotifyReader::Watch watch_;
+};
+
+DirectoryWatcher::Impl::~Impl() {
+ if (watch_ != InotifyReader::kInvalidWatch)
+ Singleton<InotifyReader>::get()->RemoveWatch(watch_, delegate_);
+}
+
+bool DirectoryWatcher::Impl::Watch(const FilePath& path) {
+ DCHECK(watch_ == InotifyReader::kInvalidWatch); // Can only watch one path.
+
+ path_ = path;
+ watch_ = Singleton<InotifyReader>::get()->AddWatch(path, delegate_);
+
+ return watch_ != InotifyReader::kInvalidWatch;
+}
+
+DirectoryWatcher::DirectoryWatcher() {
+}
+
+DirectoryWatcher::~DirectoryWatcher() {
+ // Declared in .cc file for access to ~DirectoryWatcher::Impl.
+}
+
+bool DirectoryWatcher::Watch(const FilePath& path,
+ Delegate* delegate, bool recursive) {
+ 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();
+ return false;
+ }
+ impl_ = new DirectoryWatcher::Impl(delegate);
+ return impl_->Watch(path);
+}