This source file includes following definitions.
- InotifyReaderCallback
- valid_
- AddWatch
- RemoveWatch
- OnInotifyEvent
- OnFilePathChanged
- Cancel
- CancelOnMessageLoopThread
- WillDestroyCurrentMessageLoop
- UpdateWatches
#include "base/files/file_path_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/bind.h"
#include "base/containers/hash_tables.h"
#include "base/debug/trace_event.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/posix/eintr_wrapper.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
namespace base {
namespace {
class FilePathWatcherImpl;
class InotifyReader {
public:
typedef int Watch;
static const Watch kInvalidWatch = -1;
Watch AddWatch(const FilePath& path, FilePathWatcherImpl* watcher);
bool RemoveWatch(Watch watch, FilePathWatcherImpl* watcher);
void OnInotifyEvent(const inotify_event* event);
private:
friend struct DefaultLazyInstanceTraits<InotifyReader>;
typedef std::set<FilePathWatcherImpl*> WatcherSet;
InotifyReader();
~InotifyReader();
hash_map<Watch, WatcherSet> watchers_;
Lock lock_;
Thread thread_;
const int inotify_fd_;
int shutdown_pipe_[2];
bool valid_;
DISALLOW_COPY_AND_ASSIGN(InotifyReader);
};
class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
public MessageLoop::DestructionObserver {
public:
FilePathWatcherImpl();
void OnFilePathChanged(InotifyReader::Watch fired_watch,
const FilePath::StringType& child,
bool created);
virtual bool Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) OVERRIDE;
virtual void Cancel() OVERRIDE;
virtual void WillDestroyCurrentMessageLoop() OVERRIDE;
protected:
virtual ~FilePathWatcherImpl() {}
private:
virtual void CancelOnMessageLoopThread() OVERRIDE;
struct WatchEntry {
WatchEntry(InotifyReader::Watch watch, const FilePath::StringType& subdir)
: watch_(watch),
subdir_(subdir) {}
InotifyReader::Watch watch_;
FilePath::StringType subdir_;
FilePath::StringType linkname_;
};
typedef std::vector<WatchEntry> WatchVector;
bool UpdateWatches() WARN_UNUSED_RESULT;
FilePathWatcher::Callback callback_;
FilePath target_;
WatchVector watches_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
};
void InotifyReaderCallback(InotifyReader* reader, int inotify_fd,
int shutdown_fd) {
CHECK_LE(0, inotify_fd);
CHECK_GT(FD_SETSIZE, inotify_fd);
CHECK_LE(0, shutdown_fd);
CHECK_GT(FD_SETSIZE, shutdown_fd);
debug::TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop();
while (true) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(inotify_fd, &rfds);
FD_SET(shutdown_fd, &rfds);
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;
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;
}
}
}
static LazyInstance<InotifyReader>::Leaky g_inotify_reader =
LAZY_INSTANCE_INITIALIZER;
InotifyReader::InotifyReader()
: thread_("inotify_reader"),
inotify_fd_(inotify_init()),
valid_(false) {
if (inotify_fd_ < 0)
PLOG(ERROR) << "inotify_init() failed";
shutdown_pipe_[0] = -1;
shutdown_pipe_[1] = -1;
if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) {
thread_.message_loop()->PostTask(
FROM_HERE, Bind(&InotifyReaderCallback, this, inotify_fd_,
shutdown_pipe_[0]));
valid_ = true;
}
}
InotifyReader::~InotifyReader() {
if (valid_) {
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, FilePathWatcherImpl* 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 |
IN_ONLYDIR);
if (watch == kInvalidWatch)
return kInvalidWatch;
watchers_[watch].insert(watcher);
return watch;
}
bool InotifyReader::RemoveWatch(Watch watch,
FilePathWatcherImpl* 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;
FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL(""));
AutoLock auto_lock(lock_);
for (WatcherSet::iterator watcher = watchers_[event->wd].begin();
watcher != watchers_[event->wd].end();
++watcher) {
(*watcher)->OnFilePathChanged(event->wd,
child,
event->mask & (IN_CREATE | IN_MOVED_TO));
}
}
FilePathWatcherImpl::FilePathWatcherImpl() {
}
void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch,
const FilePath::StringType& child,
bool created) {
if (!message_loop()->BelongsToCurrentThread()) {
message_loop()->PostTask(FROM_HERE,
Bind(&FilePathWatcherImpl::OnFilePathChanged,
this,
fired_watch,
child,
created));
return;
}
DCHECK(MessageLoopForIO::current());
WatchVector::const_iterator watch_entry(watches_.begin());
for ( ; watch_entry != watches_.end(); ++watch_entry) {
if (fired_watch == watch_entry->watch_) {
bool change_on_target_path = child.empty() ||
((child == watch_entry->subdir_) && watch_entry->linkname_.empty()) ||
(child == watch_entry->linkname_);
DCHECK(watch_entry->subdir_.empty() ||
(watch_entry + 1) != watches_.end());
bool target_changed =
(watch_entry->subdir_.empty() && (child == watch_entry->linkname_)) ||
(watch_entry->subdir_.empty() && watch_entry->linkname_.empty()) ||
(watch_entry->subdir_ == child && (watch_entry + 1)->subdir_.empty());
if (change_on_target_path && !UpdateWatches()) {
callback_.Run(target_, true );
return;
}
if (target_changed ||
(change_on_target_path && !created) ||
(change_on_target_path && PathExists(target_))) {
callback_.Run(target_, false);
return;
}
}
}
}
bool FilePathWatcherImpl::Watch(const FilePath& path,
bool recursive,
const FilePathWatcher::Callback& callback) {
DCHECK(target_.empty());
DCHECK(MessageLoopForIO::current());
if (recursive) {
NOTIMPLEMENTED();
return false;
}
set_message_loop(MessageLoopProxy::current().get());
callback_ = callback;
target_ = path;
MessageLoop::current()->AddDestructionObserver(this);
std::vector<FilePath::StringType> comps;
target_.GetComponents(&comps);
DCHECK(!comps.empty());
std::vector<FilePath::StringType>::const_iterator comp = comps.begin();
for (++comp; comp != comps.end(); ++comp)
watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch, *comp));
watches_.push_back(WatchEntry(InotifyReader::kInvalidWatch,
FilePath::StringType()));
return UpdateWatches();
}
void FilePathWatcherImpl::Cancel() {
if (callback_.is_null()) {
set_cancelled();
return;
}
if (!message_loop()->BelongsToCurrentThread()) {
message_loop()->PostTask(FROM_HERE,
Bind(&FilePathWatcher::CancelWatch,
make_scoped_refptr(this)));
} else {
CancelOnMessageLoopThread();
}
}
void FilePathWatcherImpl::CancelOnMessageLoopThread() {
if (!is_cancelled())
set_cancelled();
if (!callback_.is_null()) {
MessageLoop::current()->RemoveDestructionObserver(this);
callback_.Reset();
}
for (WatchVector::iterator watch_entry(watches_.begin());
watch_entry != watches_.end(); ++watch_entry) {
if (watch_entry->watch_ != InotifyReader::kInvalidWatch)
g_inotify_reader.Get().RemoveWatch(watch_entry->watch_, this);
}
watches_.clear();
target_.clear();
}
void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() {
CancelOnMessageLoopThread();
}
bool FilePathWatcherImpl::UpdateWatches() {
DCHECK(message_loop()->BelongsToCurrentThread());
FilePath path(FILE_PATH_LITERAL("/"));
bool path_valid = true;
for (WatchVector::iterator watch_entry(watches_.begin());
watch_entry != watches_.end(); ++watch_entry) {
InotifyReader::Watch old_watch = watch_entry->watch_;
if (path_valid) {
watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this);
if ((watch_entry->watch_ == InotifyReader::kInvalidWatch) &&
base::IsLink(path)) {
FilePath link;
if (ReadSymbolicLink(path, &link)) {
if (!link.IsAbsolute())
link = path.DirName().Append(link);
watch_entry->watch_ =
g_inotify_reader.Get().AddWatch(link.DirName(), this);
if (watch_entry->watch_ != InotifyReader::kInvalidWatch) {
watch_entry->linkname_ = link.BaseName().value();
} else {
DPLOG(WARNING) << "Watch failed for " << link.DirName().value();
}
}
}
if (watch_entry->watch_ == InotifyReader::kInvalidWatch) {
path_valid = false;
}
} else {
watch_entry->watch_ = InotifyReader::kInvalidWatch;
}
if (old_watch != InotifyReader::kInvalidWatch &&
old_watch != watch_entry->watch_) {
g_inotify_reader.Get().RemoveWatch(old_watch, this);
}
path = path.Append(watch_entry->subdir_);
}
return true;
}
}
FilePathWatcher::FilePathWatcher() {
impl_ = new FilePathWatcherImpl();
}
}