blob: a17e1e4ace037d3618c736a43ca0fe0c8352d12e [file] [log] [blame]
// 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_;
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<InotifyReader>::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<InotifyReader>::get()->AddWatch(path, delegate_);
return watch_ != InotifyReader::kInvalidWatch;
}
} // namespace
DirectoryWatcher::DirectoryWatcher() {
impl_ = new DirectoryWatcherImpl();
}