// 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 #include #include #include #include #include #include #include #include #include #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; typedef std::pair DelegateInfo; typedef std::multiset 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 delegates_; // For each watch we also want to know the path it's watching. base::hash_map 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 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(&buffer[i]); size_t event_size = sizeof(inotify_event) + event->len; DCHECK(i + event_size <= static_cast(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_; DISALLOW_COPY_AND_ASSIGN(InotifyReaderNotifyTask); }; 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 bytes_written; do { bytes_written = write(shutdown_pipe_[1], "", 1); if (bytes_written == 0) continue; } while (bytes_written == -1 && errno == EINTR); 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)); } } class DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate { public: DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch) {} ~DirectoryWatcherImpl(); virtual bool Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate, bool recursive); 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_; DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl); }; DirectoryWatcherImpl::~DirectoryWatcherImpl() { if (watch_ != InotifyReader::kInvalidWatch) Singleton::get()->RemoveWatch(watch_, delegate_); } 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(); return false; } delegate_ = delegate; path_ = path; watch_ = Singleton::get()->AddWatch(path, delegate_); return watch_ != InotifyReader::kInvalidWatch; } } // namespace DirectoryWatcher::DirectoryWatcher() { impl_ = new DirectoryWatcherImpl(); }