// 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/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 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. |watcher| will be notified on each change. // Returns kInvalidWatch on failure. Watch AddWatch(const FilePath& path, DirectoryWatcherImpl* watcher); // Remove |watch|. Returns true on success. bool RemoveWatch(Watch watch, DirectoryWatcherImpl* watcher); // Callback for InotifyReaderTask. void OnInotifyEvent(const inotify_event* event); private: friend struct DefaultSingletonTraits; typedef std::set WatcherSet; InotifyReader(); ~InotifyReader(); // We keep track of which delegates want to be notified on which watches. base::hash_map watchers_; // 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 DirectoryWatcherImpl : public DirectoryWatcher::PlatformDelegate { public: typedef std::set FilePathSet; DirectoryWatcherImpl(); ~DirectoryWatcherImpl(); void EnsureSetupFinished(); // Called for each event coming from one of watches. void OnInotifyEvent(const inotify_event* event); // Callback for RegisterSubtreeWatchesTask. bool OnEnumeratedSubtree(const FilePathSet& paths); // Start watching |path| for changes and notify |delegate| on each change. // If |recursive| is true, watch entire subtree. // Returns true if watch for |path| has been added successfully. 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, MessageLoop* backend_loop, bool recursive); private: typedef std::set WatchSet; typedef std::set InodeSet; // Returns true if |inode| is watched by DirectoryWatcherImpl. bool IsInodeWatched(ino_t inode) const; // Delegate to notify upon changes. DirectoryWatcher::Delegate* delegate_; // Path we're watching (passed to delegate). FilePath root_path_; // Watch returned by InotifyReader. InotifyReader::Watch watch_; // Set of watched inodes. InodeSet inodes_watched_; // Keep track of registered watches. WatchSet watches_; // Lock to protect inodes_watched_ and watches_. Lock lock_; // Flag set to true when recursively watching subtree. bool recursive_; // Loop where we post directory change notifications to. MessageLoop* loop_; // Event signaled when the background task finished adding initial inotify // watches for recursive watch. base::WaitableEvent recursive_setup_finished_; DISALLOW_COPY_AND_ASSIGN(DirectoryWatcherImpl); }; class RegisterSubtreeWatchesTask : public Task { public: RegisterSubtreeWatchesTask(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_->OnEnumeratedSubtree(subtree); } private: DirectoryWatcherImpl* watcher_; FilePath path_; DISALLOW_COPY_AND_ASSIGN(RegisterSubtreeWatchesTask); }; 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) : 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 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(&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); }; 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. HANDLE_EINTR(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, DirectoryWatcherImpl* 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; if (paths_[watch].empty()) paths_[watch] = path; // We don't yet watch this path. watchers_[watch].insert(watcher); return watch; } bool InotifyReader::RemoveWatch(Watch watch, DirectoryWatcherImpl* watcher) { if (!valid_) return false; AutoLock auto_lock(lock_); if (paths_[watch].empty()) return false; // We don't recognize this watch. watchers_[watch].erase(watcher); if (watchers_[watch].empty()) { paths_.erase(watch); 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. DirectoryWatcherImpl'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 DirectoryWatcherImpl 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); } } DirectoryWatcherImpl::DirectoryWatcherImpl() : watch_(InotifyReader::kInvalidWatch), recursive_setup_finished_(false, false) { } DirectoryWatcherImpl::~DirectoryWatcherImpl() { if (watch_ == InotifyReader::kInvalidWatch) return; if (recursive_) recursive_setup_finished_.Wait(); for (WatchSet::iterator watch = watches_.begin(); watch != watches_.end(); ++watch) { Singleton::get()->RemoveWatch(*watch, this); } watches_.clear(); inodes_watched_.clear(); } void DirectoryWatcherImpl::OnInotifyEvent(const inotify_event* event) { loop_->PostTask(FROM_HERE, new DirectoryWatcherImplNotifyTask(delegate_, root_path_)); if (!(event->mask & IN_ISDIR)) return; if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) { // TODO(phajdan.jr): add watch for this new directory. NOTIMPLEMENTED(); } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) { // TODO(phajdan.jr): remove our watch for this directory. NOTIMPLEMENTED(); } } bool DirectoryWatcherImpl::IsInodeWatched(ino_t inode) const { return inodes_watched_.find(inode) != inodes_watched_.end(); } bool DirectoryWatcherImpl::OnEnumeratedSubtree(const FilePathSet& subtree) { DCHECK(recursive_); if (watch_ == InotifyReader::kInvalidWatch) { recursive_setup_finished_.Signal(); return false; } bool success = true; { // Limit the scope of auto_lock so it releases lock_ before we signal // recursive_setup_finished_. Our dtor waits on recursive_setup_finished_ // and could otherwise destroy the lock before we release it. 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 (IsInodeWatched(inode)) continue; InotifyReader::Watch watch = Singleton::get()->AddWatch(*subdirectory, this); if (watch != InotifyReader::kInvalidWatch) { watches_.insert(watch); inodes_watched_.insert(inode); } } } recursive_setup_finished_.Signal(); return success; } bool DirectoryWatcherImpl::Watch(const FilePath& path, DirectoryWatcher::Delegate* delegate, MessageLoop* backend_loop, bool recursive) { // Can only watch one path. DCHECK(watch_ == InotifyReader::kInvalidWatch); ino_t inode; if (!file_util::GetInode(path, &inode)) return false; delegate_ = delegate; recursive_ = recursive; root_path_ = path; loop_ = MessageLoop::current(); watch_ = Singleton::get()->AddWatch(path, this); if (watch_ == InotifyReader::kInvalidWatch) return false; { AutoLock auto_lock(lock_); inodes_watched_.insert(inode); watches_.insert(watch_); } if (recursive_) { Task* subtree_task = new RegisterSubtreeWatchesTask(this, root_path_); if (backend_loop) { backend_loop->PostTask(FROM_HERE, subtree_task); } else { subtree_task->Run(); delete subtree_task; } } return true; } } // namespace DirectoryWatcher::DirectoryWatcher() { impl_ = new DirectoryWatcherImpl(); }