blob: c1d3a1725debbf0e1b00bcf8b72c85dfc278fe0e [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 <fuchsia/io/c/fidl.h>
#include <lib/fdio/io.h>
#include <lib/fdio/vfs.h>
#include <lib/zircon-internal/debug.h>
#include <lib/zx/handle.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <zircon/assert.h>
#include <memory>
#include <type_traits>
#include <utility>
#include <fbl/string_buffer.h>
#include <fs/connection.h>
#include <fs/debug.h>
#include <fs/handler.h>
#include <fs/trace.h>
#include <fs/vfs_types.h>
#include <fs/vnode.h>
static_assert(fuchsia_io_OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE ==
(fuchsia_io_OPEN_FLAG_DIRECTORY | fuchsia_io_OPEN_FLAG_NOT_DIRECTORY |
fuchsia_io_OPEN_FLAG_DESCRIBE | fuchsia_io_OPEN_FLAG_NODE_REFERENCE),
"OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE value mismatch");
static_assert(PATH_MAX == fuchsia_io_MAX_PATH, "POSIX PATH_MAX inconsistent with Fuchsia MAX_PATH");
static_assert(NAME_MAX == fuchsia_io_MAX_FILENAME,
"POSIX NAME_MAX inconsistent with Fuchsia MAX_FILENAME");
namespace fs {
namespace {
void WriteDescribeError(zx::channel channel, zx_status_t status) {
fuchsia_io_NodeOnOpenEvent msg;
memset(&msg, 0, sizeof(msg));
msg.hdr.ordinal = fuchsia_io_NodeOnOpenOrdinal;
msg.s = status;
channel.write(0, &msg, sizeof(msg), nullptr, 0);
}
zx_status_t GetNodeInfo(const fbl::RefPtr<Vnode>& vn, VnodeConnectionOptions options,
fuchsia_io_NodeInfo* info) {
if (options.flags.node_reference) {
info->tag = fuchsia_io_NodeInfoTag_service;
return ZX_OK;
} else {
fs::VnodeRepresentation representation;
zx_status_t status = vn->GetNodeInfo(options.rights, &representation);
if (status != ZX_OK) {
return status;
}
representation.visit([info](auto&& repr) {
using T = std::decay_t<decltype(repr)>;
if constexpr (std::is_same_v<T, fs::VnodeRepresentation::Connector>) {
info->tag = fuchsia_io_NodeInfoTag_service;
} else if constexpr (std::is_same_v<T, fs::VnodeRepresentation::File>) {
info->tag = fuchsia_io_NodeInfoTag_file;
info->file.event = repr.observer.release();
} else if constexpr (std::is_same_v<T, fs::VnodeRepresentation::Directory>) {
info->tag = fuchsia_io_NodeInfoTag_directory;
} else if constexpr (std::is_same_v<T, fs::VnodeRepresentation::Pipe>) {
info->tag = fuchsia_io_NodeInfoTag_pipe;
info->pipe.socket = repr.socket.release();
} else if constexpr (std::is_same_v<T, fs::VnodeRepresentation::Memory>) {
info->tag = fuchsia_io_NodeInfoTag_vmofile;
info->vmofile.vmo = repr.vmo.release();
info->vmofile.offset = repr.offset;
info->vmofile.length = repr.length;
} else if constexpr (std::is_same_v<T, fs::VnodeRepresentation::Device>) {
info->tag = fuchsia_io_NodeInfoTag_device;
info->device.event = repr.event.release();
} else if constexpr (std::is_same_v<T, fs::VnodeRepresentation::Tty>) {
info->tag = fuchsia_io_NodeInfoTag_tty;
info->device.event = repr.event.release();
} else if constexpr (std::is_same_v<T, fs::VnodeRepresentation::Socket>) {
info->tag = fuchsia_io_NodeInfoTag_socket;
info->socket.socket = repr.socket.release();
} else {
ZX_ASSERT_MSG(false, "Representation variant is not initialized");
}
});
return ZX_OK;
}
}
void Describe(const fbl::RefPtr<Vnode>& vn, VnodeConnectionOptions options, OnOpenMsg* response,
zx_handle_t* handle) {
response->primary.hdr.ordinal = fuchsia_io_NodeOnOpenOrdinal;
response->extra.file.event = ZX_HANDLE_INVALID;
zx_status_t r = GetNodeInfo(vn, options, &response->extra);
// We unfortunately encode this message by hand because FIDL events
// are not yet supported by the C bindings.
auto encode_handle = [](zx_handle_t* encode_location, zx_handle_t* out) {
// If a handle was returned, transfer it to the output location, and
// encode it in-place.
*out = *encode_location;
if (*encode_location != ZX_HANDLE_INVALID) {
*encode_location = FIDL_HANDLE_PRESENT;
} else {
*encode_location = FIDL_HANDLE_ABSENT;
}
};
switch (response->extra.tag) {
case fuchsia_io_NodeInfoTag_service:
case fuchsia_io_NodeInfoTag_directory:
break;
case fuchsia_io_NodeInfoTag_file:
encode_handle(&response->extra.file.event, handle);
break;
case fuchsia_io_NodeInfoTag_pipe:
encode_handle(&response->extra.pipe.socket, handle);
break;
case fuchsia_io_NodeInfoTag_vmofile:
encode_handle(&response->extra.vmofile.vmo, handle);
break;
case fuchsia_io_NodeInfoTag_device:
encode_handle(&response->extra.device.event, handle);
break;
case fuchsia_io_NodeInfoTag_tty:
encode_handle(&response->extra.tty.event, handle);
break;
case fuchsia_io_NodeInfoTag_socket:
encode_handle(&response->extra.socket.socket, handle);
break;
default:
ZX_DEBUG_ASSERT_MSG(false, "Unsupported NodeInfoTag: %d\n", response->extra.tag);
}
// If a valid response was returned, encode it.
response->primary.s = r;
response->primary.info =
reinterpret_cast<fuchsia_io_NodeInfo*>(r == ZX_OK ? FIDL_ALLOC_PRESENT : FIDL_ALLOC_ABSENT);
}
// Perform basic flags sanitization.
// Returns false if the flags combination is invalid.
bool PrevalidateFlags(uint32_t flags) {
// If the caller specified an unknown right, reject the request.
if ((flags & ZX_FS_RIGHTS_SPACE) & ~ZX_FS_RIGHTS) {
return false;
}
if (flags & fuchsia_io_OPEN_FLAG_NODE_REFERENCE) {
constexpr uint32_t kValidFlagsForNodeRef =
fuchsia_io_OPEN_FLAG_NODE_REFERENCE | fuchsia_io_OPEN_FLAG_DIRECTORY |
fuchsia_io_OPEN_FLAG_NOT_DIRECTORY | fuchsia_io_OPEN_FLAG_DESCRIBE;
// Explicitly reject VNODE_REF_ONLY together with any invalid flags.
if (flags & ~kValidFlagsForNodeRef) {
return false;
}
}
return true;
}
zx_status_t EnforceHierarchicalRights(Rights parent_rights, VnodeConnectionOptions child_options,
VnodeConnectionOptions* out_options) {
if (child_options.flags.posix) {
if (!parent_rights.write && !child_options.rights.write && !parent_rights.execute &&
!child_options.rights.execute) {
// Posix compatibility flag allows the child dir connection to inherit every right from
// its immediate parent. Here we know there exists a read-only directory somewhere along
// the Open() chain, so remove this flag to rid the child connection the ability to
// inherit read-write right from e.g. crossing a read-write mount point
// down the line, or similarly with the execute right.
child_options.flags.posix = false;
}
}
if (!child_options.rights.StricterOrSameAs(parent_rights)) {
// Client asked for some right but we do not have it
return ZX_ERR_ACCESS_DENIED;
}
*out_options = child_options;
return ZX_OK;
}
void VnodeServe(Vfs* vfs, const fbl::RefPtr<Vnode>& vnode, zx::channel channel,
VnodeConnectionOptions options) {
if (options.flags.node_reference) {
vnode->Vnode::Serve(vfs, std::move(channel), options);
} else {
vnode->Serve(vfs, std::move(channel), options);
}
}
// Performs a path walk and opens a connection to another node.
void OpenAt(Vfs* vfs, const fbl::RefPtr<Vnode>& parent, zx::channel channel, fbl::StringPiece path,
VnodeConnectionOptions options, Rights parent_rights, uint32_t mode) {
bool describe = options.flags.describe;
fbl::RefPtr<Vnode> vnode;
zx_status_t r = vfs->Open(std::move(parent), &vnode, path, &path, options, mode);
if (r != ZX_OK) {
FS_TRACE_DEBUG("vfs: open failure: %d\n", r);
} else if (!options.flags.no_remote && vnode->IsRemote()) {
FS_TRACE_DEBUG("vfs: handoff to remote\n");
// Remote handoff to a remote filesystem node.
vfs->ForwardOpenRemote(std::move(vnode), std::move(channel), path, options, mode);
return;
}
if (describe) {
// Regardless of the error code, in the 'describe' case, we
// should respond to the client.
if (r != ZX_OK) {
WriteDescribeError(std::move(channel), r);
return;
}
OnOpenMsg response;
memset(&response, 0, sizeof(response));
zx_handle_t extra = ZX_HANDLE_INVALID;
Describe(vnode, options, &response, &extra);
uint32_t hcount = (extra != ZX_HANDLE_INVALID) ? 1 : 0;
channel.write(0, &response, sizeof(OnOpenMsg), &extra, hcount);
} else if (r != ZX_OK) {
return;
}
if (vnode->IsDirectory() && options.flags.posix) {
// Save this before modifying |options| below.
bool admin = options.rights.admin;
// This is such that POSIX open() can open a directory with O_RDONLY, and
// still get the write/execute right if the parent directory connection has the
// write/execute right respectively. With the execute right in particular, the resulting
// connection may be passed to fdio_get_vmo_exec() which requires the execute right.
// This transfers write and execute from the parent, if present.
options.rights |= parent_rights;
// The ADMIN right is not inherited. It must be explicitly specified.
options.rights.admin = admin;
}
VnodeServe(vfs, vnode, std::move(channel), options);
}
// This template defines a mechanism to transform a member of Connection
// into a FIDL-dispatch operation compatible format, independent of
// FIDL arguments.
//
// For example:
//
// ZXFIDL_OPERATION(Foo)
//
// Defines the following method:
//
// zx_status_t FooOp(void* ctx, Args... args);
//
// That invokes:
//
// zx_status_t Connection::Foo(Args... args);
//
// Such that FooOp may be used in the fuchsia_io_* ops table.
#define ZXFIDL_OPERATION(Method) \
template <typename... Args> \
zx_status_t Method##Op(void* ctx, Args... args) { \
TRACE_DURATION("vfs", #Method); \
auto connection = reinterpret_cast<Connection*>(ctx); \
return (connection->Connection::Method)(std::forward<Args>(args)...); \
}
ZXFIDL_OPERATION(NodeClone)
ZXFIDL_OPERATION(NodeClose)
ZXFIDL_OPERATION(NodeDescribe)
ZXFIDL_OPERATION(NodeSync)
ZXFIDL_OPERATION(NodeGetAttr)
ZXFIDL_OPERATION(NodeSetAttr)
ZXFIDL_OPERATION(NodeNodeGetFlags)
ZXFIDL_OPERATION(NodeNodeSetFlags)
const fuchsia_io_Node_ops kNodeOps = {
.Clone = NodeCloneOp,
.Close = NodeCloseOp,
.Describe = NodeDescribeOp,
.Sync = NodeSyncOp,
.GetAttr = NodeGetAttrOp,
.SetAttr = NodeSetAttrOp,
.NodeGetFlags = NodeNodeGetFlagsOp,
.NodeSetFlags = NodeNodeSetFlagsOp
};
ZXFIDL_OPERATION(FileRead)
ZXFIDL_OPERATION(FileReadAt)
ZXFIDL_OPERATION(FileWrite)
ZXFIDL_OPERATION(FileWriteAt)
ZXFIDL_OPERATION(FileSeek)
ZXFIDL_OPERATION(FileTruncate)
ZXFIDL_OPERATION(FileGetFlags)
ZXFIDL_OPERATION(FileSetFlags)
ZXFIDL_OPERATION(FileGetBuffer)
const fuchsia_io_File_ops kFileOps = {
.Clone = NodeCloneOp,
.Close = NodeCloseOp,
.Describe = NodeDescribeOp,
.Sync = NodeSyncOp,
.GetAttr = NodeGetAttrOp,
.SetAttr = NodeSetAttrOp,
.NodeGetFlags = NodeNodeGetFlagsOp,
.NodeSetFlags = NodeNodeSetFlagsOp,
.Read = FileReadOp,
.ReadAt = FileReadAtOp,
.Write = FileWriteOp,
.WriteAt = FileWriteAtOp,
.Seek = FileSeekOp,
.Truncate = FileTruncateOp,
.GetFlags = FileGetFlagsOp,
.SetFlags = FileSetFlagsOp,
.GetBuffer = FileGetBufferOp,
};
ZXFIDL_OPERATION(DirectoryOpen)
ZXFIDL_OPERATION(DirectoryUnlink)
ZXFIDL_OPERATION(DirectoryReadDirents)
ZXFIDL_OPERATION(DirectoryRewind)
ZXFIDL_OPERATION(DirectoryGetToken)
ZXFIDL_OPERATION(DirectoryRename)
ZXFIDL_OPERATION(DirectoryLink)
ZXFIDL_OPERATION(DirectoryWatch)
const fuchsia_io_Directory_ops kDirectoryOps{
.Clone = NodeCloneOp,
.Close = NodeCloseOp,
.Describe = NodeDescribeOp,
.Sync = NodeSyncOp,
.GetAttr = NodeGetAttrOp,
.SetAttr = NodeSetAttrOp,
.NodeGetFlags = NodeNodeGetFlagsOp,
.NodeSetFlags = NodeNodeSetFlagsOp,
.Open = DirectoryOpenOp,
.Unlink = DirectoryUnlinkOp,
.ReadDirents = DirectoryReadDirentsOp,
.Rewind = DirectoryRewindOp,
.GetToken = DirectoryGetTokenOp,
.Rename = DirectoryRenameOp,
.Link = DirectoryLinkOp,
.Watch = DirectoryWatchOp,
};
ZXFIDL_OPERATION(DirectoryAdminMount)
ZXFIDL_OPERATION(DirectoryAdminMountAndCreate)
ZXFIDL_OPERATION(DirectoryAdminUnmount)
ZXFIDL_OPERATION(DirectoryAdminUnmountNode)
ZXFIDL_OPERATION(DirectoryAdminQueryFilesystem)
ZXFIDL_OPERATION(DirectoryAdminGetDevicePath)
const fuchsia_io_DirectoryAdmin_ops kDirectoryAdminOps{
.Clone = NodeCloneOp,
.Close = NodeCloseOp,
.Describe = NodeDescribeOp,
.Sync = NodeSyncOp,
.GetAttr = NodeGetAttrOp,
.SetAttr = NodeSetAttrOp,
.NodeGetFlags = NodeNodeGetFlagsOp,
.NodeSetFlags = NodeNodeSetFlagsOp,
.Open = DirectoryOpenOp,
.Unlink = DirectoryUnlinkOp,
.ReadDirents = DirectoryReadDirentsOp,
.Rewind = DirectoryRewindOp,
.GetToken = DirectoryGetTokenOp,
.Rename = DirectoryRenameOp,
.Link = DirectoryLinkOp,
.Watch = DirectoryWatchOp,
.Mount = DirectoryAdminMountOp,
.MountAndCreate = DirectoryAdminMountAndCreateOp,
.Unmount = DirectoryAdminUnmountOp,
.UnmountNode = DirectoryAdminUnmountNodeOp,
.QueryFilesystem = DirectoryAdminQueryFilesystemOp,
.GetDevicePath = DirectoryAdminGetDevicePathOp,
};
} // namespace
constexpr zx_signals_t kWakeSignals =
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED | kLocalTeardownSignal;
// Flags which can be modified by SetFlags.
constexpr uint32_t kSettableStatusFlags = fuchsia_io_OPEN_FLAG_APPEND;
// All flags which indicate state of the connection (excluding rights).
constexpr uint32_t kStatusFlags = kSettableStatusFlags | fuchsia_io_OPEN_FLAG_NODE_REFERENCE;
Connection::Connection(Vfs* vfs, fbl::RefPtr<Vnode> vnode, zx::channel channel,
VnodeConnectionOptions options)
: vfs_(vfs),
vnode_(std::move(vnode)),
channel_(std::move(channel)),
wait_(this, ZX_HANDLE_INVALID, kWakeSignals),
options_(VnodeConnectionOptions::FilterForNewConnection(options)) {
ZX_DEBUG_ASSERT(vfs);
ZX_DEBUG_ASSERT(vnode_);
ZX_DEBUG_ASSERT(channel_);
}
Connection::~Connection() {
// Stop waiting and clean up if still connected.
if (wait_.is_pending()) {
zx_status_t status = wait_.Cancel();
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Could not cancel wait: status=%d", status);
}
// Invoke a "close" call to the underlying object if we haven't already.
if (is_open()) {
CallClose();
}
// Release the token associated with this connection's vnode since the connection
// will be releasing the vnode's reference once this function returns.
if (token_) {
vfs_->TokenDiscard(std::move(token_));
}
}
void Connection::AsyncTeardown() {
if (channel_) {
ZX_ASSERT(channel_.signal(0, kLocalTeardownSignal) == ZX_OK);
}
}
void Connection::SyncTeardown() {
if (wait_.Cancel() == ZX_OK) {
Terminate(/* call_close= */ true);
}
}
zx_status_t Connection::Serve() {
wait_.set_object(channel_.get());
return wait_.Begin(vfs_->dispatcher());
}
void Connection::HandleSignals(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
ZX_DEBUG_ASSERT(is_open());
if (status == ZX_OK) {
if (vfs_->IsTerminating()) {
// Short-circuit locally destroyed connections, rather than servicing
// requests on their behalf. This prevents new requests from being
// opened while filesystems are torn down.
status = ZX_ERR_PEER_CLOSED;
} else if (signal->observed & ZX_CHANNEL_READABLE) {
// Handle the message.
status = ReadMessage(channel_.get(), [this](fidl_msg_t* msg, FidlConnection* txn) {
return HandleMessage(msg, txn->Txn());
});
switch (status) {
case ERR_DISPATCHER_ASYNC:
return;
case ZX_OK:
status = wait_.Begin(dispatcher);
if (status == ZX_OK) {
return;
}
break;
default:
// In case of error, go to |Terminate|.
break;
}
}
}
bool call_close = (status != ERR_DISPATCHER_DONE);
Terminate(call_close);
}
void Connection::Terminate(bool call_close) {
if (call_close) {
// Give the dispatcher a chance to clean up.
CallClose();
} else {
// It's assumed that someone called the close handler
// prior to calling this function.
set_closed();
}
// Tell the VFS that the connection closed remotely.
// This might have the side-effect of destroying this object.
vfs_->OnConnectionClosedRemotely(this);
}
void Connection::CallClose() {
CloseMessage(
[this](fidl_msg_t* msg, FidlConnection* txn) { return HandleMessage(msg, txn->Txn()); });
set_closed();
}
zx_status_t Connection::NodeClone(uint32_t clone_flags, zx_handle_t object) {
zx::channel channel(object);
auto clone_options = VnodeConnectionOptions::FromIoV1Flags(clone_flags);
auto write_error = [describe = clone_options.flags.describe](zx::channel channel,
zx_status_t error) {
if (describe) {
WriteDescribeError(std::move(channel), error);
}
return ZX_OK;
};
if (!PrevalidateFlags(clone_flags)) {
FS_PRETTY_TRACE_DEBUG("[NodeClone] prevalidate failed",
", incoming flags: ", ZxFlags(clone_flags));
return write_error(std::move(channel), ZX_ERR_INVALID_ARGS);
}
FS_PRETTY_TRACE_DEBUG("[NodeClone] our options: ", options_,
", incoming options: ", clone_options);
bool describe = clone_options.flags.describe;
// If CLONE_SAME_RIGHTS is specified, the client cannot request any specific rights.
if (clone_options.flags.clone_same_rights && clone_options.rights.any()) {
return write_error(std::move(channel), ZX_ERR_INVALID_ARGS);
}
// These two flags are always preserved.
clone_options.flags.append = options_.flags.append;
clone_options.flags.node_reference = options_.flags.node_reference;
// If CLONE_SAME_RIGHTS is requested, cloned connection will inherit the same rights
// as those from the originating connection.
if (clone_options.flags.clone_same_rights) {
clone_options.rights = options_.rights;
}
if (!clone_options.rights.StricterOrSameAs(options_.rights)) {
FS_PRETTY_TRACE_DEBUG("Rights violation during NodeClone");
return write_error(std::move(channel), ZX_ERR_ACCESS_DENIED);
}
fbl::RefPtr<Vnode> vn(vnode_);
zx_status_t status = ZX_OK;
if (!clone_options.flags.node_reference) {
status = OpenVnode(clone_options, &vn);
}
if (describe) {
OnOpenMsg response;
memset(&response, 0, sizeof(response));
response.primary.s = status;
zx_handle_t extra = ZX_HANDLE_INVALID;
if (status == ZX_OK) {
Describe(vnode_, clone_options, &response, &extra);
}
uint32_t hcount = (extra != ZX_HANDLE_INVALID) ? 1 : 0;
channel.write(0, &response, sizeof(OnOpenMsg), &extra, hcount);
}
if (status == ZX_OK) {
VnodeServe(vfs_, vn, std::move(channel), clone_options);
}
return ZX_OK;
}
zx_status_t Connection::NodeClose(fidl_txn_t* txn) {
zx_status_t status;
if (options_.flags.node_reference) {
status = ZX_OK;
} else {
status = vnode_->Close();
}
fuchsia_io_NodeClose_reply(txn, status);
return ERR_DISPATCHER_DONE;
}
zx_status_t Connection::NodeDescribe(fidl_txn_t* txn) {
fuchsia_io_NodeInfo info;
memset(&info, 0, sizeof(info));
zx_status_t status = GetNodeInfo(vnode_, options_, &info);
if (status != ZX_OK) {
return status;
}
return fuchsia_io_NodeDescribe_reply(txn, &info);
}
zx_status_t Connection::NodeSync(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[NodeSync] options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_NodeSync_reply(txn, ZX_ERR_BAD_HANDLE);
}
Vnode::SyncCallback closure(
[this, ctxn = FidlConnection::CopyTxn(txn)](zx_status_t status) mutable {
fuchsia_io_NodeSync_reply(ctxn.Txn(), status);
// Try to reset the wait object
ZX_ASSERT_MSG(wait_.Begin(vfs_->dispatcher()) == ZX_OK, "Dispatch loop unexpectedly ended");
});
vnode_->Sync(std::move(closure));
return ERR_DISPATCHER_ASYNC;
}
zx_status_t Connection::NodeGetAttr(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[NodeGetAttr] options: ", options_);
fuchsia_io_NodeAttributes attributes;
memset(&attributes, 0, sizeof(attributes));
fs::VnodeAttributes attr;
zx_status_t r;
if ((r = vnode_->GetAttributes(&attr)) != ZX_OK) {
return fuchsia_io_NodeGetAttr_reply(txn, r, &attributes);
}
attributes.mode = attr.mode;
attributes.id = attr.inode;
attributes.content_size = attr.content_size;
attributes.storage_size = attr.storage_size;
attributes.link_count = attr.link_count;
attributes.creation_time = attr.creation_time;
attributes.modification_time = attr.modification_time;
return fuchsia_io_NodeGetAttr_reply(txn, ZX_OK, &attributes);
}
zx_status_t Connection::NodeSetAttr(uint32_t flags, const fuchsia_io_NodeAttributes* attributes,
fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[NodeSetAttr] our options: ", options_, ", incoming flags: ", flags);
if (options_.flags.node_reference) {
return fuchsia_io_NodeSetAttr_reply(txn, ZX_ERR_BAD_HANDLE);
}
if (!options_.rights.write) {
return fuchsia_io_NodeSetAttr_reply(txn, ZX_ERR_BAD_HANDLE);
}
constexpr uint32_t supported_flags = fuchsia_io_NODE_ATTRIBUTE_FLAG_CREATION_TIME |
fuchsia_io_NODE_ATTRIBUTE_FLAG_MODIFICATION_TIME;
if (flags & ~supported_flags) {
return fuchsia_io_NodeSetAttr_reply(txn, ZX_ERR_INVALID_ARGS);
}
zx_status_t status = vnode_->SetAttributes(
fs::VnodeAttributesUpdate()
.set_creation_time(flags & fuchsia_io_NODE_ATTRIBUTE_FLAG_CREATION_TIME
? std::make_optional(attributes->creation_time)
: std::nullopt)
.set_modification_time(flags & fuchsia_io_NODE_ATTRIBUTE_FLAG_MODIFICATION_TIME
? std::make_optional(attributes->modification_time)
: std::nullopt));
return fuchsia_io_NodeSetAttr_reply(txn, status);
}
zx_status_t Connection::NodeNodeGetFlags(fidl_txn_t* txn) {
uint32_t flags = options_.ToIoV1Flags() & (kStatusFlags | ZX_FS_RIGHTS);
return fuchsia_io_NodeNodeGetFlags_reply(txn, ZX_OK, flags);
}
zx_status_t Connection::NodeNodeSetFlags(uint32_t flags, fidl_txn_t* txn) {
auto options = VnodeConnectionOptions::FromIoV1Flags(flags);
options_.flags.append = options.flags.append;
return fuchsia_io_NodeNodeSetFlags_reply(txn, ZX_OK);
}
zx_status_t Connection::FileRead(uint64_t count, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[FileRead] options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_FileRead_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
} else if (!options_.rights.read) {
return fuchsia_io_FileRead_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
} else if (count > ZXFIDL_MAX_MSG_BYTES) {
return fuchsia_io_FileRead_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0);
}
uint8_t data[count];
size_t actual = 0;
zx_status_t status = vnode_->Read(data, count, offset_, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= count);
offset_ += actual;
}
return fuchsia_io_FileRead_reply(txn, status, data, actual);
}
zx_status_t Connection::FileReadAt(uint64_t count, uint64_t offset, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[FileReadAt] options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_FileReadAt_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
} else if (!options_.rights.read) {
return fuchsia_io_FileReadAt_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
} else if (count > ZXFIDL_MAX_MSG_BYTES) {
return fuchsia_io_FileReadAt_reply(txn, ZX_ERR_INVALID_ARGS, nullptr, 0);
}
uint8_t data[count];
size_t actual = 0;
zx_status_t status = vnode_->Read(data, count, offset, &actual);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(actual <= count);
}
return fuchsia_io_FileReadAt_reply(txn, status, data, actual);
}
zx_status_t Connection::FileWrite(const uint8_t* data_data, size_t data_count, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[FileWrite] options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_FileWrite_reply(txn, ZX_ERR_BAD_HANDLE, 0);
}
if (!options_.rights.write) {
return fuchsia_io_FileWrite_reply(txn, ZX_ERR_BAD_HANDLE, 0);
}
size_t actual = 0;
zx_status_t status;
if (options_.flags.append) {
size_t end;
status = vnode_->Append(data_data, data_count, &end, &actual);
if (status == ZX_OK) {
offset_ = end;
}
} else {
status = vnode_->Write(data_data, data_count, offset_, &actual);
if (status == ZX_OK) {
offset_ += actual;
}
}
ZX_DEBUG_ASSERT(actual <= data_count);
return fuchsia_io_FileWrite_reply(txn, status, actual);
}
zx_status_t Connection::FileWriteAt(const uint8_t* data_data, size_t data_count, uint64_t offset,
fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[FileWriteAt] options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_FileWriteAt_reply(txn, ZX_ERR_BAD_HANDLE, 0);
}
if (!options_.rights.write) {
return fuchsia_io_FileWriteAt_reply(txn, ZX_ERR_BAD_HANDLE, 0);
}
size_t actual = 0;
zx_status_t status = vnode_->Write(data_data, data_count, offset, &actual);
ZX_DEBUG_ASSERT(actual <= data_count);
return fuchsia_io_FileWriteAt_reply(txn, status, actual);
}
zx_status_t Connection::FileSeek(int64_t offset, fuchsia_io_SeekOrigin start, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[FileSeek] options: ", options_);
static_assert(SEEK_SET == fuchsia_io_SeekOrigin_START, "");
static_assert(SEEK_CUR == fuchsia_io_SeekOrigin_CURRENT, "");
static_assert(SEEK_END == fuchsia_io_SeekOrigin_END, "");
if (options_.flags.node_reference) {
return fuchsia_io_FileSeek_reply(txn, ZX_ERR_BAD_HANDLE, offset_);
}
fs::VnodeAttributes attr;
zx_status_t r;
if ((r = vnode_->GetAttributes(&attr)) < 0) {
return r;
}
size_t n;
switch (start) {
case SEEK_SET:
if (offset < 0) {
return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
}
n = offset;
break;
case SEEK_CUR:
n = offset_ + offset;
if (offset < 0) {
// if negative seek
if (n > offset_) {
// wrapped around. attempt to seek before start
return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
}
} else {
// positive seek
if (n < offset_) {
// wrapped around. overflow
return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
}
}
break;
case SEEK_END:
n = attr.content_size + offset;
if (offset < 0) {
// if negative seek
if (n > attr.content_size) {
// wrapped around. attempt to seek before start
return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
}
} else {
// positive seek
if (n < attr.content_size) {
// wrapped around
return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
}
}
break;
default:
return fuchsia_io_FileSeek_reply(txn, ZX_ERR_INVALID_ARGS, offset_);
}
offset_ = n;
return fuchsia_io_FileSeek_reply(txn, ZX_OK, offset_);
}
zx_status_t Connection::FileTruncate(uint64_t length, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[FileTruncate] options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_FileTruncate_reply(txn, ZX_ERR_BAD_HANDLE);
}
if (!options_.rights.write) {
return fuchsia_io_FileTruncate_reply(txn, ZX_ERR_BAD_HANDLE);
}
zx_status_t status = vnode_->Truncate(length);
return fuchsia_io_FileTruncate_reply(txn, status);
}
zx_status_t Connection::FileGetFlags(fidl_txn_t* txn) {
uint32_t flags = options_.ToIoV1Flags() & (kStatusFlags | ZX_FS_RIGHTS);
return fuchsia_io_FileGetFlags_reply(txn, ZX_OK, flags);
}
zx_status_t Connection::FileSetFlags(uint32_t flags, fidl_txn_t* txn) {
auto options = VnodeConnectionOptions::FromIoV1Flags(flags);
options_.flags.append = options.flags.append;
return fuchsia_io_FileSetFlags_reply(txn, ZX_OK);
}
zx_status_t Connection::FileGetBuffer(uint32_t flags, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[FileGetBuffer] our options: ", options_,
", incoming flags: ", ZxFlags(flags));
if (options_.flags.node_reference) {
return fuchsia_io_FileGetBuffer_reply(txn, ZX_ERR_BAD_HANDLE, nullptr);
}
if ((flags & fuchsia_io_VMO_FLAG_PRIVATE) && (flags & fuchsia_io_VMO_FLAG_EXACT)) {
return fuchsia_io_FileGetBuffer_reply(txn, ZX_ERR_INVALID_ARGS, nullptr);
} else if ((options_.flags.append) && (flags & fuchsia_io_VMO_FLAG_WRITE)) {
return fuchsia_io_FileGetBuffer_reply(txn, ZX_ERR_ACCESS_DENIED, nullptr);
} else if (!options_.rights.write && (flags & fuchsia_io_VMO_FLAG_WRITE)) {
return fuchsia_io_FileGetBuffer_reply(txn, ZX_ERR_ACCESS_DENIED, nullptr);
} else if (!options_.rights.execute && (flags & fuchsia_io_VMO_FLAG_EXEC)) {
return fuchsia_io_FileGetBuffer_reply(txn, ZX_ERR_ACCESS_DENIED, nullptr);
} else if (!options_.rights.read) {
return fuchsia_io_FileGetBuffer_reply(txn, ZX_ERR_ACCESS_DENIED, nullptr);
}
fuchsia_mem_Buffer buffer;
memset(&buffer, 0, sizeof(buffer));
zx_status_t status = vnode_->GetVmo(flags, &buffer.vmo, &buffer.size);
return fuchsia_io_FileGetBuffer_reply(txn, status, status == ZX_OK ? &buffer : nullptr);
}
zx_status_t Connection::DirectoryOpen(uint32_t open_flags, uint32_t mode, const char* path_data,
size_t path_size, zx_handle_t object) {
zx::channel channel(object);
auto open_options = VnodeConnectionOptions::FromIoV1Flags(open_flags);
auto write_error = [describe = open_options.flags.describe](zx::channel channel,
zx_status_t error) {
if (describe) {
WriteDescribeError(std::move(channel), error);
}
return ZX_OK;
};
if (!PrevalidateFlags(open_flags)) {
FS_PRETTY_TRACE_DEBUG("[DirectoryOpen] prevalidate failed",
", incoming flags: ", ZxFlags(open_flags),
", path: ", Path(path_data, path_size));
if (open_options.flags.describe) {
return write_error(std::move(channel), ZX_ERR_INVALID_ARGS);
}
}
FS_PRETTY_TRACE_DEBUG("[DirectoryOpen] our options: ", options_,
", incoming options: ", open_options,
", path: ", Path(path_data, path_size));
if (options_.flags.node_reference) {
return write_error(std::move(channel), ZX_ERR_BAD_HANDLE);
}
if (open_options.flags.clone_same_rights) {
return write_error(std::move(channel), ZX_ERR_INVALID_ARGS);
}
if (!open_options.flags.node_reference && !open_options.rights.any()) {
return write_error(std::move(channel), ZX_ERR_INVALID_ARGS);
}
if ((path_size < 1) || (path_size > PATH_MAX)) {
return write_error(std::move(channel), ZX_ERR_BAD_PATH);
}
// Check for directory rights inheritance
zx_status_t status = EnforceHierarchicalRights(options_.rights, open_options, &open_options);
if (status != ZX_OK) {
FS_PRETTY_TRACE_DEBUG("Rights violation during DirectoryOpen");
return write_error(std::move(channel), status);
}
OpenAt(vfs_, vnode_, std::move(channel), fbl::StringPiece(path_data, path_size), open_options,
options_.rights, mode);
return ZX_OK;
}
zx_status_t Connection::DirectoryUnlink(const char* path_data, size_t path_size, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryUnlink] our options: ", options_,
", path: ", Path(path_data, path_size));
if (options_.flags.node_reference) {
return fuchsia_io_DirectoryUnlink_reply(txn, ZX_ERR_BAD_HANDLE);
}
if (!options_.rights.write) {
return fuchsia_io_DirectoryUnlink_reply(txn, ZX_ERR_BAD_HANDLE);
}
zx_status_t status = vfs_->Unlink(vnode_, fbl::StringPiece(path_data, path_size));
return fuchsia_io_DirectoryUnlink_reply(txn, status);
}
zx_status_t Connection::DirectoryReadDirents(uint64_t max_out, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryReadDirents] our options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_DirectoryReadDirents_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
}
if (max_out > ZXFIDL_MAX_MSG_BYTES) {
return fuchsia_io_DirectoryReadDirents_reply(txn, ZX_ERR_BAD_HANDLE, nullptr, 0);
}
uint8_t data[max_out];
size_t actual = 0;
zx_status_t status = vfs_->Readdir(vnode_.get(), &dircookie_, data, max_out, &actual);
return fuchsia_io_DirectoryReadDirents_reply(txn, status, data, actual);
}
zx_status_t Connection::DirectoryRewind(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryRewind] our options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_DirectoryRewind_reply(txn, ZX_ERR_BAD_HANDLE);
}
dircookie_.Reset();
return fuchsia_io_DirectoryRewind_reply(txn, ZX_OK);
}
zx_status_t Connection::DirectoryGetToken(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryGetToken] our options: ", options_);
if (!options_.rights.write) {
return fuchsia_io_DirectoryGetToken_reply(txn, ZX_ERR_BAD_HANDLE, ZX_HANDLE_INVALID);
}
zx::event returned_token;
zx_status_t status = vfs_->VnodeToToken(vnode_, &token_, &returned_token);
return fuchsia_io_DirectoryGetToken_reply(txn, status, returned_token.release());
}
zx_status_t Connection::DirectoryRename(const char* src_data, size_t src_size,
zx_handle_t dst_parent_token, const char* dst_data,
size_t dst_size, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryRename] our options: ", options_,
", src: ", Path(src_data, src_size), ", dst: ", Path(dst_data, dst_size));
zx::event token(dst_parent_token);
fbl::StringPiece oldStr(src_data, src_size);
fbl::StringPiece newStr(dst_data, dst_size);
if (src_size < 1 || dst_size < 1) {
return fuchsia_io_DirectoryRename_reply(txn, ZX_ERR_INVALID_ARGS);
}
if (options_.flags.node_reference) {
return fuchsia_io_DirectoryRename_reply(txn, ZX_ERR_BAD_HANDLE);
}
if (!options_.rights.write) {
return fuchsia_io_DirectoryRename_reply(txn, ZX_ERR_BAD_HANDLE);
}
zx_status_t status = vfs_->Rename(std::move(token), vnode_, std::move(oldStr), std::move(newStr));
return fuchsia_io_DirectoryRename_reply(txn, status);
}
zx_status_t Connection::DirectoryLink(const char* src_data, size_t src_size,
zx_handle_t dst_parent_token, const char* dst_data,
size_t dst_size, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryLink] our options: ", options_,
", src: ", Path(src_data, src_size), ", dst: ", Path(dst_data, dst_size));
zx::event token(dst_parent_token);
fbl::StringPiece oldStr(src_data, src_size);
fbl::StringPiece newStr(dst_data, dst_size);
if (src_size < 1 || dst_size < 1) {
return fuchsia_io_DirectoryLink_reply(txn, ZX_ERR_INVALID_ARGS);
}
if (options_.flags.node_reference) {
return fuchsia_io_DirectoryLink_reply(txn, ZX_ERR_BAD_HANDLE);
}
if (!options_.rights.write) {
return fuchsia_io_DirectoryLink_reply(txn, ZX_ERR_BAD_HANDLE);
}
zx_status_t status = vfs_->Link(std::move(token), vnode_, std::move(oldStr), std::move(newStr));
return fuchsia_io_DirectoryLink_reply(txn, status);
}
zx_status_t Connection::DirectoryWatch(uint32_t mask, uint32_t options, zx_handle_t handle,
fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryWatch] our options: ", options_);
if (options_.flags.node_reference) {
return fuchsia_io_DirectoryWatch_reply(txn, ZX_ERR_BAD_HANDLE);
}
zx::channel watcher(handle);
zx_status_t status = vnode_->WatchDir(vfs_, mask, options, std::move(watcher));
return fuchsia_io_DirectoryWatch_reply(txn, status);
}
zx_status_t Connection::DirectoryAdminMount(zx_handle_t remote, fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryAdminMount] our options: ", options_);
if (!options_.rights.admin) {
vfs_unmount_handle(remote, 0);
return fuchsia_io_DirectoryAdminMount_reply(txn, ZX_ERR_ACCESS_DENIED);
}
MountChannel c = MountChannel(remote);
zx_status_t status = vfs_->InstallRemote(vnode_, std::move(c));
return fuchsia_io_DirectoryAdminMount_reply(txn, status);
}
zx_status_t Connection::DirectoryAdminMountAndCreate(zx_handle_t remote, const char* name,
size_t name_size, uint32_t flags,
fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryAdminMountAndCreate] our options: ", options_);
if (!options_.rights.admin) {
vfs_unmount_handle(remote, 0);
return fuchsia_io_DirectoryAdminMount_reply(txn, ZX_ERR_ACCESS_DENIED);
}
fbl::StringPiece str(name, name_size);
zx_status_t status = vfs_->MountMkdir(vnode_, std::move(str), MountChannel(remote), flags);
return fuchsia_io_DirectoryAdminMount_reply(txn, status);
}
zx_status_t Connection::DirectoryAdminUnmount(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryAdminUnmount] our options: ", options_);
if (!options_.rights.admin) {
return fuchsia_io_DirectoryAdminUnmount_reply(txn, ZX_ERR_ACCESS_DENIED);
}
vfs_->UninstallAll(ZX_TIME_INFINITE);
// Unmount is fatal to the requesting connections.
Vfs::ShutdownCallback closure(
[ch = std::move(channel_), ctxn = FidlConnection::CopyTxn(txn)](zx_status_t status) mutable {
fuchsia_io_DirectoryAdminUnmount_reply(ctxn.Txn(), status);
});
Vfs* vfs = vfs_;
Terminate(/* call_close= */ true);
vfs->Shutdown(std::move(closure));
return ERR_DISPATCHER_ASYNC;
}
zx_status_t Connection::DirectoryAdminUnmountNode(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryAdminUnmountNode] our options: ", options_);
if (!options_.rights.admin) {
return fuchsia_io_DirectoryAdminUnmountNode_reply(txn, ZX_ERR_ACCESS_DENIED, ZX_HANDLE_INVALID);
}
zx::channel c;
zx_status_t status = vfs_->UninstallRemote(vnode_, &c);
return fuchsia_io_DirectoryAdminUnmountNode_reply(txn, status, c.release());
}
zx_status_t Connection::DirectoryAdminQueryFilesystem(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryAdminQueryFilesystem] our options: ", options_);
fuchsia_io_FilesystemInfo info;
zx_status_t status = vnode_->QueryFilesystem(&info);
return fuchsia_io_DirectoryAdminQueryFilesystem_reply(txn, status,
status == ZX_OK ? &info : nullptr);
}
zx_status_t Connection::DirectoryAdminGetDevicePath(fidl_txn_t* txn) {
FS_PRETTY_TRACE_DEBUG("[DirectoryAdminGetDevicePath] our options: ", options_);
if (!options_.rights.admin) {
return fuchsia_io_DirectoryAdminGetDevicePath_reply(txn, ZX_ERR_ACCESS_DENIED, nullptr, 0);
}
char name[fuchsia_io_MAX_PATH];
size_t actual = 0;
zx_status_t status = vnode_->GetDevicePath(sizeof(name), name, &actual);
return fuchsia_io_DirectoryAdminGetDevicePath_reply(txn, status, name, actual);
}
zx_status_t Connection::HandleFsSpecificMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
zx_handle_close_many(msg->handles, msg->num_handles);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Connection::HandleMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
zx_status_t status = fuchsia_io_Node_try_dispatch(this, txn, msg, &kNodeOps);
if (status != ZX_ERR_NOT_SUPPORTED) {
return status;
}
status = fuchsia_io_File_try_dispatch(this, txn, msg, &kFileOps);
if (status != ZX_ERR_NOT_SUPPORTED) {
return status;
}
status = fuchsia_io_Directory_try_dispatch(this, txn, msg, &kDirectoryOps);
if (status != ZX_ERR_NOT_SUPPORTED) {
return status;
}
status = fuchsia_io_DirectoryAdmin_try_dispatch(this, txn, msg, &kDirectoryAdminOps);
if (status != ZX_ERR_NOT_SUPPORTED) {
return status;
}
return HandleFsSpecificMessage(msg, txn);
}
} // namespace fs