blob: 38629e815b24c84c4559dce43b57a0ffb5159ac7 [file] [log] [blame]
// 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 <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef __Fuchsia__
#include <magenta/device/vfs.h>
#include <magenta/syscalls.h>
#include <fbl/auto_lock.h>
#endif
#include <fbl/alloc_checker.h>
#include <fs/vfs.h>
#include <fs/watcher.h>
#include <mx/channel.h>
namespace fs {
WatcherContainer::WatcherContainer() = default;
WatcherContainer::~WatcherContainer() = default;
WatcherContainer::VnodeWatcher::VnodeWatcher(mx::channel h, uint32_t mask) : h(fbl::move(h)),
mask(mask & ~(VFS_WATCH_MASK_EXISTING | VFS_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;
mx_status_t AddMsg(const mx::channel& c, unsigned event, const char* name);
mx_status_t Send(const mx::channel& c);
private:
size_t watch_buf_size_ = 0;
char watch_buf_[VFS_WATCH_MSG_MAX]{};
};
mx_status_t WatchBuffer::AddMsg(const mx::channel& c, unsigned event, const char* name) {
size_t slen = strlen(name);
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.
mx_status_t status = Send(c);
if (status != MX_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, slen);
watch_buf_size_ += mlen;
return MX_OK;
}
mx_status_t WatchBuffer::Send(const mx::channel& c) {
if (watch_buf_size_ > 0) {
// Only write if we have something to write
mx_status_t status = c.write(0, watch_buf_, static_cast<uint32_t>(watch_buf_size_), nullptr, 0);
watch_buf_size_ = 0;
if (status != MX_OK) {
return status;
}
}
return MX_OK;
}
mx_status_t WatcherContainer::WatchDir(mx::channel* out) {
fbl::AllocChecker ac;
fbl::unique_ptr<VnodeWatcher> watcher(new (&ac) VnodeWatcher(mx::channel(),
VFS_WATCH_MASK_ADDED));
if (!ac.check()) {
return MX_ERR_NO_MEMORY;
}
mx::channel out_channel;
if (mx::channel::create(0, &out_channel, &watcher->h) != MX_OK) {
return MX_ERR_NO_RESOURCES;
}
fbl::AutoLock lock(&lock_);
watch_list_.push_back(fbl::move(watcher));
*out = fbl::move(out_channel);
return MX_OK;
}
mx_status_t WatcherContainer::WatchDirV2(Vfs* vfs, Vnode* vn, const vfs_watch_dir_t* cmd) {
mx::channel c = mx::channel(cmd->channel);
if ((cmd->mask & VFS_WATCH_MASK_ALL) == 0) {
// No events to watch
return MX_ERR_INVALID_ARGS;
}
fbl::AllocChecker ac;
fbl::unique_ptr<VnodeWatcher> watcher(new (&ac) VnodeWatcher(fbl::move(c), cmd->mask));
if (!ac.check()) {
return MX_ERR_NO_MEMORY;
}
if (cmd->mask & VFS_WATCH_MASK_EXISTING) {
vdircookie_t dircookie;
memset(&dircookie, 0, sizeof(dircookie));
char readdir_buf[MXIO_CHUNK_SIZE];
WatchBuffer wb;
{
// Send "VFS_WATCH_EVT_EXISTING" for all entries in readdir
fbl::AutoLock lock(&vfs->vfs_lock_);
while (true) {
mx_status_t status = vn->Readdir(&dircookie, &readdir_buf, sizeof(readdir_buf));
if (status <= 0) {
break;
}
void* ptr = readdir_buf;
while (status > 0) {
auto dirent = reinterpret_cast<vdirent_t*>(ptr);
if (dirent->name[0]) {
wb.AddMsg(watcher->h, VFS_WATCH_EVT_EXISTING, dirent->name);
}
status -= dirent->size;
ptr = reinterpret_cast<void*>(
static_cast<uintptr_t>(dirent->size) +
reinterpret_cast<uintptr_t>(ptr));
}
}
}
// Send VFS_WATCH_EVT_IDLE to signify that readdir has completed
if (cmd->mask & VFS_WATCH_MASK_IDLE) {
wb.AddMsg(watcher->h, VFS_WATCH_EVT_IDLE, "");
}
wb.Send(watcher->h);
}
fbl::AutoLock lock(&lock_);
watch_list_.push_back(fbl::move(watcher));
return MX_OK;
}
void WatcherContainer::Notify(const char* name, size_t len, unsigned event) {
if (len > VFS_WATCH_NAME_MAX) {
return;
}
fbl::AutoLock lock(&lock_);
if (watch_list_.is_empty()) {
return;
}
uint8_t msg[sizeof(vfs_watch_msg_t) + len];
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>(len);
memcpy(vmsg->name, name, len);
for (auto it = watch_list_.begin(); it != watch_list_.end();) {
if (!(it->mask & VFS_WATCH_EVT_MASK(event))) {
++it;
continue;
}
mx_status_t status = it->h.write(0, msg, static_cast<uint32_t>(sizeof(msg)), 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