blob: 66f4babdf676ecae402ff44f620a40f5d43b4165 [file] [log] [blame]
// Copyright 2016 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 <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fdio/namespace.h>
#include <lib/zx/channel.h>
#include <lib/zxio/cpp/inception.h>
#include <poll.h>
#include <fbl/auto_lock.h>
#include "fdio_unistd.h"
#include "zxio.h"
namespace fio = fuchsia_io;
namespace fsocket = fuchsia_posix_socket;
namespace frawsocket = fuchsia_posix_socket_raw;
namespace fpacketsocket = fuchsia_posix_socket_packet;
static_assert(FDIO_CHUNK_SIZE >= PATH_MAX, "FDIO_CHUNK_SIZE must be large enough to contain paths");
static_assert(static_cast<uint32_t>(fio::wire::VmoFlags::kRead) == ZX_VM_PERM_READ,
"Vmar / Vmo flags should be aligned");
static_assert(static_cast<uint32_t>(fio::wire::VmoFlags::kWrite) == ZX_VM_PERM_WRITE,
"Vmar / Vmo flags should be aligned");
static_assert(static_cast<uint32_t>(fio::wire::VmoFlags::kExecute) == ZX_VM_PERM_EXECUTE,
"Vmar / Vmo flags should be aligned");
zx_status_t fdio_validate_path(const char* path, size_t* out_length) {
if (path == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
size_t length = strnlen(path, PATH_MAX);
if (length >= PATH_MAX) {
return ZX_ERR_INVALID_ARGS;
}
if (out_length != nullptr) {
*out_length = length;
}
return ZX_OK;
}
// Allocates an fdio_t instance containing storage for a zxio_t object.
static zx_status_t ZxioAllocator(zxio_object_type_t type, zxio_storage_t** out_storage,
void** out_context) {
fdio_ptr io;
// The type of storage (fdio subclass) depends on the type of the object until
// https://fxbug.dev/43267 is resolved, so this has to switch on the type.
switch (type) {
case ZXIO_OBJECT_TYPE_DEBUGLOG:
io = fbl::MakeRefCounted<fdio_internal::zxio>();
break;
case ZXIO_OBJECT_TYPE_DEVICE:
io = fbl::MakeRefCounted<fdio_internal::remote>();
break;
case ZXIO_OBJECT_TYPE_DIR:
io = fbl::MakeRefCounted<fdio_internal::remote>();
break;
case ZXIO_OBJECT_TYPE_FILE:
io = fbl::MakeRefCounted<fdio_internal::remote>();
break;
case ZXIO_OBJECT_TYPE_PIPE:
io = fbl::MakeRefCounted<fdio_internal::pipe>();
break;
case ZXIO_OBJECT_TYPE_SERVICE:
io = fbl::MakeRefCounted<fdio_internal::remote>();
break;
case ZXIO_OBJECT_TYPE_TTY:
io = fbl::MakeRefCounted<fdio_internal::remote>();
break;
case ZXIO_OBJECT_TYPE_VMO:
io = fbl::MakeRefCounted<fdio_internal::remote>();
break;
case ZXIO_OBJECT_TYPE_VMOFILE:
io = fbl::MakeRefCounted<fdio_internal::remote>();
break;
default:
// Unknown type - allocate a generic fdio object so that zxio_create can
// initialize a zxio object holding the object for us.
io = fbl::MakeRefCounted<fdio_internal::zxio>();
break;
}
if (io == nullptr) {
return ZX_ERR_NO_MEMORY;
}
*out_storage = &io->zxio_storage();
*out_context = fbl::ExportToRawPtr(&io);
return ZX_OK;
}
zx::status<fdio_ptr> fdio::create(fidl::ClientEnd<fio::Node> node, fio::wire::NodeInfo info) {
void* context = nullptr;
zx_status_t status = zxio_create_with_allocator(std::move(node), info, ZxioAllocator, &context);
// If the status is ZX_ERR_NO_MEMORY, then zxio_create_with_allocator has not allocated
// anything and we can return immediately with no cleanup.
if (status == ZX_ERR_NO_MEMORY) {
ZX_ASSERT(context == nullptr);
return zx::error(status);
}
// Otherwise, ZxioAllocator has allocated an fdio instance that we now own.
fdio_ptr io = fbl::ImportFromRawPtr(static_cast<fdio*>(context));
switch (status) {
case ZX_OK: {
return zx::ok(std::move(io));
}
case ZX_ERR_NOT_SUPPORTED: {
zx::handle retrieved_handle;
status = io->unwrap(retrieved_handle.reset_and_get_address());
if (status != ZX_OK) {
return zx::error(status);
}
node = fidl::ClientEnd<fio::Node>(zx::channel(std::move(retrieved_handle)));
break;
}
default: {
return zx::error(status);
}
}
switch (info.Which()) {
case fio::wire::NodeInfo::Tag::kSynchronousDatagramSocket: {
auto& socket = info.synchronous_datagram_socket();
return fdio_synchronous_datagram_socket_create(
std::move(socket.event),
fidl::ClientEnd<fsocket::SynchronousDatagramSocket>(node.TakeChannel()));
}
case fio::wire::NodeInfo::Tag::kStreamSocket: {
auto& socket = info.stream_socket().socket;
return fdio_stream_socket_create(std::move(socket),
fidl::ClientEnd<fsocket::StreamSocket>(node.TakeChannel()));
}
case fio::wire::NodeInfo::Tag::kRawSocket: {
auto& socket = info.raw_socket();
return fdio_raw_socket_create(std::move(socket.event),
fidl::ClientEnd<frawsocket::Socket>(node.TakeChannel()));
}
case fio::wire::NodeInfo::Tag::kPacketSocket: {
auto& socket = info.packet_socket();
return fdio_packet_socket_create(std::move(socket.event),
fidl::ClientEnd<fpacketsocket::Socket>(node.TakeChannel()));
}
default:
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
}
zx::status<fdio_ptr> fdio::create_with_describe(fidl::ClientEnd<fio::Node> node) {
auto response = fidl::WireCall(node)->Describe();
zx_status_t status = response.status();
if (status != ZX_OK) {
return zx::error(status);
}
return fdio::create(std::move(node), std::move(response.value().info));
}
zx::status<fdio_ptr> fdio::create_with_on_open(fidl::ClientEnd<fio::Node> node) {
class EventHandler : public fidl::WireSyncEventHandler<fio::Node> {
public:
explicit EventHandler(fidl::ClientEnd<fio::Node> client_end)
: client_end_(std::move(client_end)) {}
zx::status<fdio_ptr>& result() { return result_; }
const fidl::ClientEnd<fio::Node>& client_end() const { return client_end_; }
void OnOpen(fidl::WireEvent<fio::Node::OnOpen>* event) override {
if (event->s != ZX_OK) {
result_ = zx::error(event->s);
} else {
result_ = fdio::create(std::move(client_end_), std::move(event->info));
}
}
void OnConnectionInfo(fidl::WireEvent<fio::Node::OnConnectionInfo>* event) override {
result_ = zx::error(ZX_ERR_NOT_SUPPORTED);
}
private:
fidl::ClientEnd<fio::Node> client_end_;
zx::status<fdio_ptr> result_ = zx::error(ZX_ERR_INTERNAL);
};
EventHandler event_handler(std::move(node));
const fidl::Status status = event_handler.HandleOneEvent(event_handler.client_end());
if (!status.ok()) {
// TODO(https://fxbug.dev/30921): This should probably be ZX_ERR_IO (EIO in
// POSIX) or the transformation to errno should happen differently. This
// behavior is kept to avoid breaking tests that check for EPIPE when
// talking to a closed server endpoint.
if (status.is_peer_closed()) {
return zx::error(ZX_ERR_PEER_CLOSED);
}
return zx::error(ZX_ERR_IO);
}
return event_handler.result();
}
zx::status<fdio_ptr> fdio::create(zx::handle handle) {
zx_info_handle_basic_t info = {};
zx_status_t status = handle.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return zx::error(status);
}
if (info.type == ZX_OBJ_TYPE_CHANNEL) {
return fdio::create_with_describe(fidl::ClientEnd<fio::Node>(zx::channel(std::move(handle))));
}
// zxio understands how to wrap all non-channel types.
void* context = nullptr;
status = zxio_create_with_allocator(std::move(handle), info, ZxioAllocator, &context);
if (status == ZX_ERR_NO_MEMORY) {
// If zxio_create_with_allocator returns ZX_ERR_NO_MEMORY, it has not
// allocated any object and we do not have any cleanup to do.
ZX_ASSERT(context == nullptr);
return zx::error(status);
}
fdio_ptr io = fbl::ImportFromRawPtr(static_cast<fdio*>(context));
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(std::move(io));
}