blob: a5b70893a0dd0fe8bcb2f5d1ef4b902c6bcfd1a8 [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/llcpp/fidl.h>
#include <lib/fdio/io.h>
#include <lib/fdio/vfs.h>
#include <lib/fidl/txn_header.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/debug.h>
#include <fs/internal/connection.h>
#include <fs/internal/fidl_transaction.h>
#include <fs/trace.h>
#include <fs/vfs_types.h>
#include <fs/vnode.h>
namespace fio = ::llcpp::fuchsia::io;
static_assert(fio::OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE ==
(fio::OPEN_FLAG_DIRECTORY | fio::OPEN_FLAG_NOT_DIRECTORY |
fio::OPEN_FLAG_DESCRIBE | fio::OPEN_FLAG_NODE_REFERENCE),
"OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE value mismatch");
static_assert(PATH_MAX == fio::MAX_PATH, "POSIX PATH_MAX inconsistent with Fuchsia MAX_PATH");
static_assert(NAME_MAX == fio::MAX_FILENAME,
"POSIX NAME_MAX inconsistent with Fuchsia MAX_FILENAME");
namespace fs {
constexpr zx_signals_t kWakeSignals =
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED | kLocalTeardownSignal;
namespace internal {
fit::result<VnodeRepresentation, zx_status_t> Describe(const fbl::RefPtr<Vnode>& vnode,
VnodeProtocol protocol,
VnodeConnectionOptions options) {
if (options.flags.node_reference) {
return fit::ok(VnodeRepresentation::Connector());
}
fs::VnodeRepresentation representation;
zx_status_t status = vnode->GetNodeInfoForProtocol(protocol, options.rights, &representation);
if (status != ZX_OK) {
return fit::error(status);
}
return fit::ok(std::move(representation));
}
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 & fio::OPEN_FLAG_NODE_REFERENCE) {
constexpr uint32_t kValidFlagsForNodeRef =
fio::OPEN_FLAG_NODE_REFERENCE | fio::OPEN_FLAG_DIRECTORY | fio::OPEN_FLAG_NOT_DIRECTORY |
fio::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;
}
Binding::Binding(Connection* connection, async_dispatcher_t* dispatcher, zx::channel channel)
: wait_(this, channel.get(), kWakeSignals, 0),
connection_(connection),
dispatcher_(dispatcher),
channel_(std::move(channel)) {}
Binding::~Binding() { CancelDispatching(); }
Connection::Connection(Vfs* vfs, fbl::RefPtr<Vnode> vnode, VnodeProtocol protocol,
VnodeConnectionOptions options, FidlProtocol fidl_protocol)
: vnode_is_open_(!options.flags.node_reference),
vfs_(vfs),
vnode_(std::move(vnode)),
protocol_(protocol),
options_(VnodeConnectionOptions::FilterForNewConnection(options)),
fidl_protocol_(fidl_protocol) {
ZX_DEBUG_ASSERT(vfs);
ZX_DEBUG_ASSERT(vnode_);
}
Connection::~Connection() {
// Invoke a "close" call on the underlying vnode if we haven't already.
EnsureVnodeClosed();
// 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 (std::shared_ptr<Binding> binding = binding_; binding) {
binding->AsyncTeardown();
}
}
void Binding::AsyncTeardown() {
// This will wake up the dispatcher to call |Binding::HandleSignals|
// and eventually result in |Connection::SyncTeardown|.
ZX_ASSERT(channel_.signal(0, kLocalTeardownSignal) == ZX_OK);
}
void Connection::UnmountAndShutdown(fit::callback<void(zx_status_t)> callback) {
vfs_->UninstallAll(zx::time::infinite());
// We need the binding to live on in order to make a reply to this FIDL request.
// However, the connection object may be destroyed before the binding. We need to
// stop the binding from monitoring further incoming FIDL messages.
binding_->DetachFromConnection();
Vfs::ShutdownCallback closure([binding = std::move(binding_), callback = std::move(callback)](
zx_status_t status) mutable { callback(status); });
Vfs* vfs = vfs_;
SyncTeardown();
vfs->Shutdown(std::move(closure));
}
zx_status_t Connection::StartDispatching(zx::channel channel) {
ZX_DEBUG_ASSERT(channel);
ZX_DEBUG_ASSERT(!binding_);
ZX_DEBUG_ASSERT(vfs_->dispatcher());
ZX_DEBUG_ASSERT_MSG(InContainer(),
"Connection must be managed by the Vfs when dispatching FIDL messages.");
binding_ = std::make_shared<Binding>(this, vfs_->dispatcher(), std::move(channel));
zx_status_t status = binding_->StartDispatching();
if (status != ZX_OK) {
binding_.reset();
return status;
}
return ZX_OK;
}
zx_status_t Binding::StartDispatching() {
if (!connection_) {
return ZX_OK;
}
ZX_DEBUG_ASSERT(!wait_.is_pending());
return wait_.Begin(dispatcher_);
}
void Binding::CancelDispatching() {
// 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);
}
}
void Binding::DetachFromConnection() {
CancelDispatching();
UnregisterInflightTransaction();
connection_ = nullptr;
}
void Binding::HandleSignals(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (!connection_) {
// Before a |Connection| is destructed, it will clear this pointer
// in its corresponding |Binding| by calling |DetachFromConnection|.
return;
}
if (status != ZX_OK || !(signal->observed & ZX_CHANNEL_READABLE)) {
connection_->SyncTeardown();
return;
}
bool handling_ok = connection_->OnMessage();
if (!handling_ok) {
connection_->SyncTeardown();
}
}
bool Connection::OnMessage() {
if (vfs_->IsTerminating()) {
// Short-circuit locally destroyed connections, rather than servicing
// requests on their behalf. This prevents new requests from being
// served while filesystems are torn down.
return false;
}
if (closing_) {
// This prevents subsequent requests from being served after the
// observation of a |Node.Close| call.
return false;
}
std::shared_ptr<Binding> binding = binding_;
uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
fidl_msg_t msg = {
.bytes = bytes,
.handles = handles,
.num_bytes = 0,
.num_handles = 0,
};
zx_status_t r = binding->channel().read(0, bytes, handles, countof(bytes), countof(handles),
&msg.num_bytes, &msg.num_handles);
if (r != ZX_OK) {
return false;
}
if (msg.num_bytes < sizeof(fidl_message_header_t)) {
zx_handle_close_many(msg.handles, msg.num_handles);
return false;
}
auto header = reinterpret_cast<fidl_message_header_t*>(msg.bytes);
FidlTransaction txn(header->txid, binding);
bool handled = fidl_protocol_.TryDispatch(&msg, &txn);
if (!handled) {
vnode_->HandleFsSpecificMessage(&msg, &txn);
}
switch (txn.ToResult()) {
case FidlTransaction::Result::kRepliedSynchronously:
// If we get here, the message was successfully handled, synchronously.
return binding->StartDispatching() == ZX_OK;
case FidlTransaction::Result::kPendingAsyncReply:
// If we get here, the transaction was converted to an async one.
// Dispatching will be resumed by the transaction when it is completed.
return true;
case FidlTransaction::Result::kClosed:
return false;
}
#ifdef __GNUC__
// GCC does not infer that the above switch statement will always return by
// handling all defined enum members.
__builtin_abort();
#endif
}
void Connection::SyncTeardown() {
EnsureVnodeClosed();
binding_.reset();
// Tell the VFS that the connection closed remotely.
// This might have the side-effect of destroying this object,
// so this must be the last statement.
vfs_->OnConnectionClosedRemotely(this);
}
zx_status_t Connection::EnsureVnodeClosed() {
if (!vnode_is_open_) {
return ZX_OK;
}
vnode_is_open_ = false;
return vnode_->Close();
}
void Connection::NodeClone(uint32_t clone_flags, zx::channel channel) {
auto clone_options = VnodeConnectionOptions::FromIoV1Flags(clone_flags);
auto write_error = [describe = clone_options.flags.describe](zx::channel channel,
zx_status_t error) {
if (describe) {
fio::Node::SendOnOpenEvent(zx::unowned_channel(channel), error, fio::NodeInfo());
}
};
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);
// 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_);
auto result = vn->ValidateOptions(clone_options);
if (result.is_error()) {
return write_error(std::move(channel), result.error());
}
auto& validated_options = result.value();
zx_status_t open_status = ZX_OK;
if (!clone_options.flags.node_reference) {
open_status = OpenVnode(validated_options, &vn);
}
if (open_status != ZX_OK) {
return write_error(std::move(channel), open_status);
}
vfs_->Serve(vn, std::move(channel), validated_options);
}
Connection::Result<> Connection::NodeClose() {
Result<> result = FromStatus(EnsureVnodeClosed());
closing_ = true;
AsyncTeardown();
return result;
}
Connection::Result<VnodeRepresentation> Connection::NodeDescribe() {
return Describe(vnode(), protocol(), options());
}
void Connection::NodeSync(fit::callback<void(zx_status_t)> callback) {
FS_PRETTY_TRACE_DEBUG("[NodeSync] options: ", options());
if (options().flags.node_reference) {
return callback(ZX_ERR_BAD_HANDLE);
}
vnode_->Sync(Vnode::SyncCallback(std::move(callback)));
}
Connection::Result<VnodeAttributes> Connection::NodeGetAttr() {
FS_PRETTY_TRACE_DEBUG("[NodeGetAttr] options: ", options());
fs::VnodeAttributes attr;
zx_status_t r;
if ((r = vnode_->GetAttributes(&attr)) != ZX_OK) {
return fit::error(r);
}
return fit::ok(attr);
}
Connection::Result<> Connection::NodeSetAttr(uint32_t flags,
const fio::NodeAttributes& attributes) {
FS_PRETTY_TRACE_DEBUG("[NodeSetAttr] our options: ", options(), ", incoming flags: ", flags);
if (options().flags.node_reference) {
return fit::error(ZX_ERR_BAD_HANDLE);
}
if (!options().rights.write) {
return fit::error(ZX_ERR_BAD_HANDLE);
}
constexpr uint32_t supported_flags =
fio::NODE_ATTRIBUTE_FLAG_CREATION_TIME | fio::NODE_ATTRIBUTE_FLAG_MODIFICATION_TIME;
if (flags & ~supported_flags) {
return fit::error(ZX_ERR_INVALID_ARGS);
}
zx_status_t status = vnode_->SetAttributes(
fs::VnodeAttributesUpdate()
.set_creation_time(flags & fio::NODE_ATTRIBUTE_FLAG_CREATION_TIME
? std::make_optional(attributes.creation_time)
: std::nullopt)
.set_modification_time(flags & fio::NODE_ATTRIBUTE_FLAG_MODIFICATION_TIME
? std::make_optional(attributes.modification_time)
: std::nullopt));
return FromStatus(status);
}
Connection::Result<uint32_t> Connection::NodeNodeGetFlags() {
return fit::ok(options().ToIoV1Flags() & (kStatusFlags | ZX_FS_RIGHTS));
}
Connection::Result<> Connection::NodeNodeSetFlags(uint32_t flags) {
auto options = VnodeConnectionOptions::FromIoV1Flags(flags);
set_append(options.flags.append);
return fit::ok();
}
} // namespace internal
} // namespace fs