blob: 84813959b364ee8d8f1ecb3021ce1047279233cf [file] [log] [blame] [edit]
// Copyright 2017 The Fuchsia 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 <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <fs/watcher.h>
#ifdef __Fuchsia__
#include <fuchsia/io/llcpp/fidl.h>
#include <zircon/device/vfs.h>
#include <zircon/syscalls.h>
#include <fbl/auto_lock.h>
#endif
#include <lib/zx/channel.h>
#include <utility>
#include <fbl/alloc_checker.h>
#include <fs/vfs.h>
#include <fs/vnode.h>
namespace fio = ::llcpp::fuchsia::io;
namespace fs {
WatcherContainer::WatcherContainer() = default;
WatcherContainer::~WatcherContainer() = default;
WatcherContainer::VnodeWatcher::VnodeWatcher(zx::channel h, uint32_t mask)
: h(std::move(h)), mask(mask & ~(fio::WATCH_MASK_EXISTING | fio::WATCH_MASK_IDLE)) {}
WatcherContainer::VnodeWatcher::~VnodeWatcher() {}
// Transmission buffer for sending directory watcher notifications to clients.
// Allows enqueueing multiple messages in a buffer before sending an IPC message
// to a client.
class WatchBuffer {
public:
DISALLOW_COPY_ASSIGN_AND_MOVE(WatchBuffer);
WatchBuffer() = default;
zx_status_t AddMsg(const zx::channel& c, unsigned event, fbl::StringPiece name);
zx_status_t Send(const zx::channel& c);
private:
size_t watch_buf_size_ = 0;
char watch_buf_[fio::MAX_BUF]{};
};
zx_status_t WatchBuffer::AddMsg(const zx::channel& c, unsigned event, fbl::StringPiece name) {
size_t slen = name.length();
size_t mlen = sizeof(vfs_watch_msg_t) + slen;
if (mlen + watch_buf_size_ > sizeof(watch_buf_)) {
// This message won't fit in the watch_buf; transmit first.
zx_status_t status = Send(c);
if (status != ZX_OK) {
return status;
}
}
vfs_watch_msg_t* vmsg =
reinterpret_cast<vfs_watch_msg_t*>((uintptr_t)watch_buf_ + watch_buf_size_);
vmsg->event = static_cast<uint8_t>(event);
vmsg->len = static_cast<uint8_t>(slen);
memcpy(vmsg->name, name.data(), slen);
watch_buf_size_ += mlen;
return ZX_OK;
}
zx_status_t WatchBuffer::Send(const zx::channel& c) {
if (watch_buf_size_ > 0) {
// Only write if we have something to write
zx_status_t status = c.write(0, watch_buf_, static_cast<uint32_t>(watch_buf_size_), nullptr, 0);
watch_buf_size_ = 0;
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t WatcherContainer::WatchDir(Vfs* vfs, Vnode* vn, uint32_t mask, uint32_t options,
zx::channel channel) {
if ((mask & fio::WATCH_MASK_ALL) == 0) {
// No events to watch
return ZX_ERR_INVALID_ARGS;
}
fbl::AllocChecker ac;
std::unique_ptr<VnodeWatcher> watcher(new (&ac) VnodeWatcher(std::move(channel), mask));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if (mask & fio::WATCH_MASK_EXISTING) {
VdirCookie dircookie;
memset(&dircookie, 0, sizeof(dircookie));
char readdir_buf[FDIO_CHUNK_SIZE];
WatchBuffer wb;
{
// Send "fio::WATCH_EVENT_EXISTING" for all entries in readdir.
while (true) {
size_t actual;
zx_status_t status =
vfs->Readdir(vn, &dircookie, readdir_buf, sizeof(readdir_buf), &actual);
if (status != ZX_OK || actual == 0) {
break;
}
void* ptr = readdir_buf;
while (actual >= sizeof(vdirent_t)) {
auto dirent = reinterpret_cast<vdirent_t*>(ptr);
if (dirent->name[0]) {
wb.AddMsg(watcher->h, fio::WATCH_EVENT_EXISTING,
fbl::StringPiece(dirent->name, dirent->size));
}
size_t entry_len = dirent->size + sizeof(vdirent_t);
ZX_ASSERT(entry_len <= actual); // Prevent underflow
actual -= entry_len;
ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(entry_len) +
reinterpret_cast<uintptr_t>(ptr));
}
}
}
// Send fio::WATCH_EVENT_IDLE to signify that readdir has completed.
if (mask & fio::WATCH_MASK_IDLE) {
wb.AddMsg(watcher->h, fio::WATCH_EVENT_IDLE, "");
}
wb.Send(watcher->h);
}
fbl::AutoLock lock(&lock_);
watch_list_.push_back(std::move(watcher));
return ZX_OK;
}
void WatcherContainer::Notify(fbl::StringPiece name, unsigned event) {
if (name.length() > fio::MAX_FILENAME) {
return;
}
fbl::AutoLock lock(&lock_);
if (watch_list_.is_empty()) {
return;
}
uint8_t msg[sizeof(vfs_watch_msg_t) + fio::MAX_FILENAME];
size_t msg_length = sizeof(vfs_watch_msg_t) + name.length();
vfs_watch_msg_t* vmsg = reinterpret_cast<vfs_watch_msg_t*>(msg);
vmsg->event = static_cast<uint8_t>(event);
vmsg->len = static_cast<uint8_t>(name.length());
memcpy(vmsg->name, name.data(), name.length());
for (auto it = watch_list_.begin(); it != watch_list_.end();) {
if (!(it->mask & (1 << event))) {
++it;
continue;
}
zx_status_t status = it->h.write(0, msg, static_cast<uint32_t>(msg_length), nullptr, 0);
if (status < 0) {
// Lazily remove watchers when their handles cannot accept incoming
// watch messages.
auto to_remove = it;
++it;
watch_list_.erase(to_remove);
} else {
++it;
}
}
}
} // namespace fs