| Index: base/file_watcher_inotify.cc
|
| diff --git a/base/file_watcher_inotify.cc b/base/file_watcher_inotify.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9bc4626e40cb9d93e41ad5ded19ebfb825871dcd
|
| --- /dev/null
|
| +++ b/base/file_watcher_inotify.cc
|
| @@ -0,0 +1,323 @@
|
| +// 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/file_watcher.h"
|
| +
|
| +#include <errno.h>
|
| +#include <string.h>
|
| +#include <sys/inotify.h>
|
| +#include <sys/ioctl.h>
|
| +#include <sys/select.h>
|
| +#include <unistd.h>
|
| +
|
| +#include <algorithm>
|
| +#include <set>
|
| +#include <utility>
|
| +#include <vector>
|
| +
|
| +#include "base/eintr_wrapper.h"
|
| +#include "base/file_path.h"
|
| +#include "base/file_util.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"
|
| +#include "base/waitable_event.h"
|
| +
|
| +namespace {
|
| +
|
| +class FileWatcherImpl;
|
| +
|
| +// 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. |watcher| will be notified on each change.
|
| + // Returns kInvalidWatch on failure.
|
| + Watch AddWatch(const FilePath& path, FileWatcherImpl* watcher);
|
| +
|
| + // Remove |watch|. Returns true on success.
|
| + bool RemoveWatch(Watch watch, FileWatcherImpl* watcher);
|
| +
|
| + // Callback for InotifyReaderTask.
|
| + void OnInotifyEvent(const inotify_event* event);
|
| +
|
| + private:
|
| + friend struct DefaultSingletonTraits<InotifyReader>;
|
| +
|
| + typedef std::set<FileWatcherImpl*> WatcherSet;
|
| +
|
| + InotifyReader();
|
| + ~InotifyReader();
|
| +
|
| + // We keep track of which delegates want to be notified on which watches.
|
| + base::hash_map<Watch, WatcherSet> watchers_;
|
| +
|
| + // Lock to protect watchers_.
|
| + 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 FileWatcherImpl : public FileWatcher::PlatformDelegate {
|
| + public:
|
| + FileWatcherImpl();
|
| + ~FileWatcherImpl();
|
| +
|
| + // Called for each event coming from the watch.
|
| + void OnInotifyEvent(const inotify_event* event);
|
| +
|
| + // 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, FileWatcher::Delegate* delegate,
|
| + MessageLoop* backend_loop);
|
| +
|
| + private:
|
| + // Delegate to notify upon changes.
|
| + FileWatcher::Delegate* delegate_;
|
| +
|
| + // Watch returned by InotifyReader.
|
| + InotifyReader::Watch watch_;
|
| +
|
| + // The file we're watching.
|
| + FilePath path_;
|
| +
|
| + // Loop where we post file change notifications to.
|
| + MessageLoop* loop_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(FileWatcherImpl);
|
| +};
|
| +
|
| +class FileWatcherImplNotifyTask : public Task {
|
| + public:
|
| + FileWatcherImplNotifyTask(FileWatcher::Delegate* delegate,
|
| + const FilePath& path)
|
| + : delegate_(delegate), path_(path) {
|
| + }
|
| +
|
| + virtual void Run() {
|
| + delegate_->OnFileChanged(path_);
|
| + }
|
| +
|
| + private:
|
| + FileWatcher::Delegate* delegate_;
|
| + FilePath path_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(FileWatcherImplNotifyTask);
|
| +};
|
| +
|
| +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 =
|
| + HANDLE_EINTR(select(std::max(inotify_fd_, shutdown_fd_) + 1,
|
| + &rfds, NULL, NULL, NULL));
|
| + if (select_result < 0) {
|
| + DPLOG(WARNING) << "select failed";
|
| + return;
|
| + }
|
| +
|
| + if (FD_ISSET(shutdown_fd_, &rfds))
|
| + return;
|
| +
|
| + // Adjust buffer size to current event queue size.
|
| + int buffer_size;
|
| + int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD,
|
| + &buffer_size));
|
| +
|
| + if (ioctl_result != 0) {
|
| + DPLOG(WARNING) << "ioctl failed";
|
| + return;
|
| + }
|
| +
|
| + std::vector<char> buffer(buffer_size);
|
| +
|
| + ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd_, &buffer[0],
|
| + buffer_size));
|
| +
|
| + if (bytes_read < 0) {
|
| + DPLOG(WARNING) << "read from inotify fd failed";
|
| + 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);
|
| +};
|
| +
|
| +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.
|
| + ssize_t ret = HANDLE_EINTR(write(shutdown_pipe_[1], "", 1));
|
| + DPCHECK(ret > 0);
|
| + DCHECK_EQ(ret, 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, FileWatcherImpl* watcher) {
|
| + 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;
|
| +
|
| + watchers_[watch].insert(watcher);
|
| +
|
| + return watch;
|
| +}
|
| +
|
| +bool InotifyReader::RemoveWatch(Watch watch,
|
| + FileWatcherImpl* watcher) {
|
| + if (!valid_)
|
| + return false;
|
| +
|
| + AutoLock auto_lock(lock_);
|
| +
|
| + watchers_[watch].erase(watcher);
|
| +
|
| + if (watchers_[watch].empty()) {
|
| + watchers_.erase(watch);
|
| + return (inotify_rm_watch(inotify_fd_, watch) == 0);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void InotifyReader::OnInotifyEvent(const inotify_event* event) {
|
| + if (event->mask & IN_IGNORED)
|
| + return;
|
| +
|
| + // In case you want to limit the scope of this lock, it's not sufficient
|
| + // to just copy things under the lock, and then run the notifications
|
| + // without holding the lock. FileWatcherImpl's dtor removes its watches,
|
| + // and to do that obtains the lock. After it finishes removing watches,
|
| + // it's destroyed. So, if you copy under the lock and notify without the lock,
|
| + // it's possible you'll copy the FileWatcherImpl which is being
|
| + // destroyed, then it will destroy itself, and then you'll try to notify it.
|
| + AutoLock auto_lock(lock_);
|
| +
|
| + for (WatcherSet::iterator watcher = watchers_[event->wd].begin();
|
| + watcher != watchers_[event->wd].end();
|
| + ++watcher) {
|
| + (*watcher)->OnInotifyEvent(event);
|
| + }
|
| +}
|
| +
|
| +FileWatcherImpl::FileWatcherImpl()
|
| + : watch_(InotifyReader::kInvalidWatch) {
|
| +}
|
| +
|
| +FileWatcherImpl::~FileWatcherImpl() {
|
| + if (watch_ == InotifyReader::kInvalidWatch)
|
| + return;
|
| +
|
| + Singleton<InotifyReader>::get()->RemoveWatch(watch_, this);
|
| +}
|
| +
|
| +void FileWatcherImpl::OnInotifyEvent(const inotify_event* event) {
|
| + // Since we're watching the directory, filter out inotify events
|
| + // if it's not related to the file we're watching.
|
| + if (path_ != path_.DirName().Append(event->name))
|
| + return;
|
| +
|
| + loop_->PostTask(FROM_HERE,
|
| + new FileWatcherImplNotifyTask(delegate_, path_));
|
| +}
|
| +
|
| +bool FileWatcherImpl::Watch(const FilePath& path,
|
| + FileWatcher::Delegate* delegate,
|
| + MessageLoop* backend_loop) {
|
| + // Each FileWatcherImpl can only watch one file.
|
| + DCHECK(watch_ == InotifyReader::kInvalidWatch);
|
| +
|
| + // It's not possible to watch a file that doesn't exist, so instead,
|
| + // watch the parent directory.
|
| + if (!file_util::PathExists(path.DirName()))
|
| + return false;
|
| +
|
| + delegate_ = delegate;
|
| + path_ = path;
|
| + loop_ = MessageLoop::current();
|
| + watch_ = Singleton<InotifyReader>::get()->AddWatch(path.DirName(), this);
|
| + return watch_ != InotifyReader::kInvalidWatch;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +FileWatcher::FileWatcher() {
|
| + impl_ = new FileWatcherImpl();
|
| +}
|
|
|