blob: 2bd4d39a462935d9a14a953eb6d513c02f572925 [file] [log] [blame]
// Copyright 2021 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 <errno.h>
#include <lib/fdio/private.h>
#include <lib/zx/channel.h>
#include <lib/zx/socket.h>
#include <lib/zxio/null.h>
#include <lib/zxio/ops.h>
#include <poll.h>
#include <sys/inotify.h>
#include <map>
#include <vector>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include "sdk/lib/fdio/cleanpath.h"
#include "sdk/lib/fdio/fdio_unistd.h"
#include "sdk/lib/fdio/internal.h"
#include "sdk/lib/fdio/zxio.h"
namespace {
// Inotify structure for individual watch descriptor, which is equivalent to a filter.
struct FdioInotifyWd {
FdioInotifyWd(int input_filter_mask, int wd) {
mask = input_filter_mask;
watch_descriptor = wd;
}
int mask;
int watch_descriptor;
zx::channel client_request;
};
struct FdioInotify {
FdioInotify(zxio_t zx_io, zx::socket client_end, zx::socket server_end)
: io(zx_io), client(std::move(client_end)), server(std::move(server_end)) {
filepath_to_filter = std::make_unique<std::map<std::string, std::unique_ptr<FdioInotifyWd>>>();
watch_descriptors = std::make_unique<std::map<uint64_t, std::string>>();
}
zxio_t io;
mtx_t lock = {};
// The zx::socket object that is shared across all the filters for a single inotify instance.
zx::socket client;
// The zx::socket object that will be duplicated for different filters created.
zx::socket server;
// Monotonically increasing client-side watch descriptor generator. 0 value is reserved.
int next_watch_descriptor __TA_GUARDED(lock) = 1;
// Store filepath to watch desciptor mapping for identifying existing filters for a filepath.
std::unique_ptr<std::map<std::string, std::unique_ptr<FdioInotifyWd>>> filepath_to_filter
__TA_GUARDED(lock);
// Store reverse lookup of watch descriptor to filepath for inotify_remove.
std::unique_ptr<std::map<uint64_t, std::string>> watch_descriptors __TA_GUARDED(lock);
};
static_assert(sizeof(FdioInotify) <= sizeof(zxio_storage_t),
"FdioInotify must fit inside zxio_storage_t.");
inline FdioInotify* zxio_to_inotify(zxio_t* zxio) { return reinterpret_cast<FdioInotify*>(zxio); }
zx_status_t inotify_close(zxio_t* io) {
FdioInotify* inotify = reinterpret_cast<FdioInotify*>(io);
inotify->~FdioInotify();
return ZX_OK;
}
zx_status_t ionotify_get_read_buffer_available(zxio_t* io, size_t* out_available) {
zx_info_socket_t info = {};
zx_status_t status =
zxio_to_inotify(io)->client.get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
*out_available = info.rx_buf_available;
return ZX_OK;
}
zx_status_t inotify_readv(zxio_t* io, const zx_iovec_t* vector, size_t vector_count,
zxio_flags_t flags, size_t* out_actual) {
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
size_t total = fdio_iovec_get_capacity(vector, vector_count);
std::vector<uint8_t> buf(total);
size_t actual;
zx_status_t status = zxio_to_inotify(io)->client.read(0, buf.data(), total, &actual);
if (status != ZX_OK) {
return status;
}
fdio_iovec_copy_to(buf.data(), actual, vector, vector_count, out_actual);
return ZX_OK;
}
static void inotify_wait_begin(zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle,
zx_signals_t* out_zx_signals) {
*out_handle = zxio_to_inotify(io)->client.get();
zx_signals_t zx_signals = ZX_SIGNAL_NONE;
if (zxio_signals & ZXIO_SIGNAL_READABLE) {
zx_signals |= ZX_SOCKET_READABLE;
}
if (zxio_signals & ZXIO_SIGNAL_PEER_CLOSED) {
zx_signals |= ZX_SOCKET_PEER_CLOSED;
}
*out_zx_signals = zx_signals;
}
static void inotify_wait_end(zxio_t* io, zx_signals_t zx_signals,
zxio_signals_t* out_zxio_signals) {
zxio_signals_t zxio_signals = ZXIO_SIGNAL_NONE;
if (zx_signals & ZX_SOCKET_READABLE) {
zxio_signals |= ZXIO_SIGNAL_READABLE;
}
if (zx_signals & ZX_SOCKET_PEER_CLOSED) {
zxio_signals |= ZXIO_SIGNAL_PEER_CLOSED;
}
*out_zxio_signals = zxio_signals;
}
constexpr zxio_ops_t inotify_ops = []() {
zxio_ops_t ops = zxio_default_ops;
ops.close = inotify_close;
ops.get_read_buffer_available = ionotify_get_read_buffer_available;
ops.readv = inotify_readv;
ops.wait_begin = inotify_wait_begin;
ops.wait_end = inotify_wait_end;
return ops;
}();
bool fdio_is_inotify(const fdio_ptr& io) {
if (!io) {
return false;
}
return zxio_get_ops(&io->zxio_storage().io) == &inotify_ops;
}
} // namespace
__EXPORT
int inotify_init() { return inotify_init1(0); }
__EXPORT
int inotify_init1(int flags) {
if (flags & ~(IN_CLOEXEC | IN_NONBLOCK)) {
return ERRNO(EINVAL);
}
zx::status io = fdio_internal::zxio::create();
if (io.is_error()) {
return ERROR(io.status_value());
}
zx::socket client, server;
// Create a common socket shared between all the filters in an inotify instance.
// We need to avoid short writes for inotify events, and hence need to set
// socket type as datagram.
zx_status_t status = zx::socket::create(ZX_SOCKET_DATAGRAM, &client, &server);
if (status != ZX_OK) {
return ERROR(status);
}
auto inotify = new (&io->zxio_storage())
FdioInotify(io->zxio_storage().io, std::move(client), std::move(server));
zxio_init(&inotify->io, &inotify_ops);
std::optional fd = bind_to_fd(io.value());
if (fd.has_value()) {
return fd.value();
}
return ERRNO(EMFILE);
}
__EXPORT
int inotify_add_watch(int fd, const char* pathname, uint32_t mask) {
if (pathname == nullptr) {
return ERRNO(EFAULT);
}
if (pathname[0] == '\0') {
return ERRNO(ENOENT);
}
// canonicalize path and clean it on client-side.
fdio_internal::PathBuffer buffer;
bool has_ending_slash;
bool cleaned = fdio_internal::CleanPath(pathname, &buffer, &has_ending_slash);
if (!cleaned) {
return ERRNO(ENAMETOOLONG);
}
std::string_view cleanpath(buffer);
fdio_ptr iodir = fdio_iodir(AT_FDCWD, cleanpath);
if (iodir == nullptr) {
return ERRNO(EBADF);
}
// TODO: Only include events which we will support initially.
constexpr uint32_t allowed_events =
IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | IN_CLOSE_NOWRITE | IN_CLOSE | IN_OPEN |
IN_MOVED_FROM | IN_MOVED_TO | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF |
IN_MOVE_SELF | IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED | IN_ONLYDIR |
IN_DONT_FOLLOW | IN_EXCL_UNLINK | IN_MASK_ADD | IN_ISDIR | IN_ONESHOT;
if (mask & ~allowed_events) {
return ERRNO(EINVAL);
}
// Mask cannot support IN_MASK_ADD and IN_MASK_CREATE together.
if ((mask & (IN_MASK_ADD | IN_MASK_CREATE)) == (IN_MASK_ADD | IN_MASK_CREATE)) {
return ERRNO(EINVAL);
}
fdio_ptr io = fd_to_io(fd);
if (!fdio_is_inotify(io)) {
return ERRNO(EBADF);
}
FdioInotify* inotify = zxio_to_inotify(&io->zxio_storage().io);
fbl::AutoLock lock(&inotify->lock);
int watch_descriptor_to_use = inotify->next_watch_descriptor++;
std::unique_ptr<FdioInotifyWd> wd =
std::make_unique<FdioInotifyWd>(mask, watch_descriptor_to_use);
zx::socket dup_server_socket_per_filter;
zx_status_t status =
inotify->server.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup_server_socket_per_filter);
if (status != ZX_OK) {
return ERROR(status);
}
std::string cleanpath_copy(cleanpath);
// Check if filter already exists and simply needs modification.
auto res = inotify->filepath_to_filter->emplace(cleanpath_copy, std::move(wd));
if (!res.second) { // filter already present.
uint32_t old_mask = res.first->second->mask;
if (old_mask == mask || (mask & IN_MASK_CREATE)) {
return ERRNO(EEXIST);
}
// remove wd to path mapping.
auto old_watch_descriptor = res.first->second->watch_descriptor;
inotify->watch_descriptors->erase(old_watch_descriptor);
// Update filter.
inotify->filepath_to_filter->insert({cleanpath_copy, std::move(wd)});
}
// Update watch_descriptor to filepath mapping.
inotify->watch_descriptors->insert({watch_descriptor_to_use, std::move(cleanpath_copy)});
status = iodir->add_inotify_filter(cleanpath, mask, watch_descriptor_to_use,
std::move(dup_server_socket_per_filter));
if (status != ZX_OK) {
return ERROR(status);
}
return watch_descriptor_to_use;
}
__EXPORT
int inotify_rm_watch(int fd, int wd) {
fdio_ptr io = fd_to_io(fd);
if (!fdio_is_inotify(io)) {
return ERRNO(EBADF);
}
FdioInotify* inotify = zxio_to_inotify(&io->zxio_storage().io);
fbl::AutoLock lock(&inotify->lock);
auto iter_wd = inotify->watch_descriptors->find(wd);
// Filter not found or wd is not valid.
if (iter_wd == inotify->watch_descriptors->end()) {
return ERRNO(EINVAL);
}
std::string file_to_be_erased = iter_wd->second;
inotify->watch_descriptors->erase(iter_wd);
auto iter_file = inotify->filepath_to_filter->find(file_to_be_erased);
// Filter not found or wd is not valid.
if (iter_file == inotify->filepath_to_filter->end()) {
return ERRNO(EINVAL);
}
// TODO : Close the channel for VFS to cleanup.
// FdioInotifyWd* wd_to_be_erased = iter_file->second.get();
inotify->filepath_to_filter->erase(iter_file);
return 0;
}