blob: 9f8627e5299551e3c2ab5ee1593fb7fe43a599e0 [file]
// 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 <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/io.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/unsafe.h>
#include <string.h>
#include <zircon/device/vfs.h>
#include <zircon/syscalls.h>
#include <mutex>
#include "private-socket.h"
namespace fio = ::llcpp::fuchsia::io;
namespace fsocket = ::llcpp::fuchsia::posix::socket;
namespace fdevice = ::llcpp::fuchsia::device;
#define ZXDEBUG 0
// POLL_MASK and POLL_SHIFT intend to convert the lower five POLL events into
// ZX_USER_SIGNALs and vice-versa. Other events need to be manually converted to
// a zx_signals_t, if they are desired.
#define POLL_SHIFT 24
#define POLL_MASK 0x1F
static_assert(FDIO_CHUNK_SIZE >= PATH_MAX, "FDIO_CHUNK_SIZE must be large enough to contain paths");
static_assert(fio::VMO_FLAG_READ == ZX_VM_PERM_READ, "Vmar / Vmo flags should be aligned");
static_assert(fio::VMO_FLAG_WRITE == ZX_VM_PERM_WRITE, "Vmar / Vmo flags should be aligned");
static_assert(fio::VMO_FLAG_EXEC == ZX_VM_PERM_EXECUTE, "Vmar / Vmo flags should be aligned");
static_assert(ZX_USER_SIGNAL_0 == (1 << POLL_SHIFT), "");
static_assert((POLLIN << POLL_SHIFT) == fdevice::DEVICE_SIGNAL_READABLE, "");
static_assert((POLLPRI << POLL_SHIFT) == fdevice::DEVICE_SIGNAL_OOB, "");
static_assert((POLLOUT << POLL_SHIFT) == fdevice::DEVICE_SIGNAL_WRITABLE, "");
static_assert((POLLERR << POLL_SHIFT) == fdevice::DEVICE_SIGNAL_ERROR, "");
static_assert((POLLHUP << POLL_SHIFT) == fdevice::DEVICE_SIGNAL_HANGUP, "");
static_assert(fio::DEVICE_SIGNAL_READABLE == fdevice::DEVICE_SIGNAL_READABLE);
static_assert(fio::DEVICE_SIGNAL_OOB == fdevice::DEVICE_SIGNAL_OOB);
static_assert(fio::DEVICE_SIGNAL_WRITABLE == fdevice::DEVICE_SIGNAL_WRITABLE);
static_assert(fio::DEVICE_SIGNAL_ERROR == fdevice::DEVICE_SIGNAL_ERROR);
static_assert(fio::DEVICE_SIGNAL_HANGUP == fdevice::DEVICE_SIGNAL_HANGUP);
// The |mode| argument used for |fuchsia.io.Directory/Open| calls.
#define FDIO_CONNECT_MODE ((uint32_t)0755)
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;
}
__EXPORT
zx_status_t fdio_service_connect(const char* path, zx_handle_t h) {
return fdio_open(path, ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE, h);
}
__EXPORT
zx_status_t fdio_service_connect_at(zx_handle_t dir, const char* path, zx_handle_t request_raw) {
zx::channel request(request_raw);
size_t length = 0u;
zx_status_t status = fdio_validate_path(path, &length);
if (status != ZX_OK) {
return status;
}
if (dir == ZX_HANDLE_INVALID) {
return ZX_ERR_UNAVAILABLE;
}
uint32_t flags = fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE;
return fio::Directory::Call::Open(zx::unowned_channel(dir), flags, FDIO_CONNECT_MODE,
fidl::StringView(path, length), std::move(request))
.status();
}
zx_status_t fdio_service_connect_by_name(const char name[], zx::channel* out) {
static zx_handle_t service_root;
{
static std::once_flag once;
static zx_status_t status;
std::call_once(once, [&]() {
zx::channel c0, c1;
status = zx::channel::create(0, &c0, &c1);
if (status != ZX_OK) {
return;
}
// TODO(abarth): Use "/svc/" once that actually works.
status = fdio_service_connect("/svc/.", c0.release());
if (status != ZX_OK) {
return;
}
service_root = c1.release();
});
if (status != ZX_OK) {
return status;
}
}
zx::channel c0, c1;
zx_status_t status = zx::channel::create(0, &c0, &c1);
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect_at(service_root, name, c0.release());
if (status != ZX_OK) {
return status;
}
*out = std::move(c1);
return ZX_OK;
}
__EXPORT
zx_status_t fdio_open(const char* path, uint32_t flags, zx_handle_t request) {
// TODO: fdio_validate_path?
if (path == nullptr) {
zx_handle_close(request);
return ZX_ERR_INVALID_ARGS;
}
// Otherwise attempt to connect through the root namespace
return fdio_ns_connect(fdio_root_ns, path, flags, request);
}
__EXPORT
zx_status_t fdio_open_at(zx_handle_t dir, const char* path, uint32_t flags,
zx_handle_t raw_request) {
zx::channel request(raw_request);
size_t length;
zx_status_t status = fdio_validate_path(path, &length);
if (status != ZX_OK) {
return status;
}
if (flags & ZX_FS_FLAG_DESCRIBE) {
return ZX_ERR_INVALID_ARGS;
}
return fio::Directory::Call::Open(zx::unowned_channel(dir), flags, FDIO_CONNECT_MODE,
fidl::StringView(path, length), std::move(request))
.status();
}
static zx_status_t fdio_open_fd_common(fdio_t* iodir, const char* path, uint32_t flags,
int* out_fd) {
// We're opening a file descriptor rather than just a channel (like fdio_open), so we always want
// to Describe (or listen for an OnOpen event on) the opened connection. This ensures that the fd
// is valid before returning from here, and mimics how open() and openat() behave
// (fdio_flags_to_zxio always add _FLAG_DESCRIBE).
flags |= ZX_FS_FLAG_DESCRIBE;
fdio_t* io;
zx_status_t status = fdio_get_ops(iodir)->open(iodir, path, flags, FDIO_CONNECT_MODE, &io);
if (status != ZX_OK) {
return status;
}
int fd;
if ((fd = fdio_bind_to_fd(io, -1, 0)) < 0) {
fdio_get_ops(io)->close(io);
fdio_release(io);
return ZX_ERR_BAD_STATE;
}
*out_fd = fd;
return ZX_OK;
}
__EXPORT
zx_status_t fdio_open_fd(const char* path, uint32_t flags, int* out_fd) {
zx_status_t status = fdio_validate_path(path, nullptr);
if (status != ZX_OK) {
return status;
}
// Since we are sending a request to the root handle, require that we start at '/'. (In fdio_open
// above this is done by fdio_ns_connect.)
if (path[0] != '/') {
return ZX_ERR_NOT_FOUND;
}
path++;
return fdio_open_fd_common(fdio_root_handle, path, flags, out_fd);
}
__EXPORT
zx_status_t fdio_open_fd_at(int dir_fd, const char* path, uint32_t flags, int* out_fd) {
zx_status_t status = fdio_validate_path(path, nullptr);
if (status != ZX_OK) {
return status;
}
fdio_t* iodir = fdio_unsafe_fd_to_io(dir_fd);
if (iodir == NULL) {
return ZX_ERR_INVALID_ARGS;
}
status = fdio_open_fd_common(iodir, path, flags, out_fd);
fdio_unsafe_release(iodir);
return status;
}
__EXPORT
zx_handle_t fdio_service_clone(zx_handle_t handle) {
if (handle == ZX_HANDLE_INVALID) {
return ZX_HANDLE_INVALID;
}
zx::channel clone, request;
if (zx::channel::create(0, &clone, &request) != ZX_OK) {
return ZX_HANDLE_INVALID;
}
uint32_t flags = ZX_FS_FLAG_CLONE_SAME_RIGHTS;
auto result = fio::Node::Call::Clone(zx::unowned_channel(handle), flags, std::move(request));
if (result.status() != ZX_OK) {
return ZX_HANDLE_INVALID;
}
return clone.release();
}
__EXPORT
zx_status_t fdio_service_clone_to(zx_handle_t handle, zx_handle_t request_raw) {
zx::channel request(request_raw);
if (!request.is_valid()) {
return ZX_ERR_INVALID_ARGS;
}
uint32_t flags = ZX_FS_FLAG_CLONE_SAME_RIGHTS;
return fio::Node::Call::Clone(zx::unowned_channel(handle), flags, std::move(request)).status();
}
zx_status_t fdio_from_node_info(zx::channel handle, fio::NodeInfo info, fdio_t** out_io) {
if (!handle.is_valid()) {
return ZX_ERR_INVALID_ARGS;
}
fdio_t* io = nullptr;
switch (info.which()) {
case fio::NodeInfo::Tag::kDirectory:
io = fdio_dir_create(handle.release());
break;
case fio::NodeInfo::Tag::kService:
io = fdio_remote_create(handle.release(), 0);
break;
case fio::NodeInfo::Tag::kFile:
io = fdio_file_create(handle.release(), info.mutable_file().event.release());
break;
case fio::NodeInfo::Tag::kDevice:
io = fdio_remote_create(handle.release(), info.mutable_device().event.release());
break;
case fio::NodeInfo::Tag::kTty:
io = fdio_remote_create(handle.release(), info.mutable_tty().event.release());
break;
case fio::NodeInfo::Tag::kVmofile: {
fio::File::SyncClient control(std::move(handle));
auto result = control.Seek(0, fio::SeekOrigin::START);
zx_status_t status = result.status();
if (status != ZX_OK) {
return status;
}
status = result->s;
if (status != ZX_OK) {
return status;
}
io = fdio_vmofile_create(std::move(control), std::move(info.mutable_vmofile().vmo),
info.vmofile().offset, info.vmofile().length, result->offset);
break;
}
case fio::NodeInfo::Tag::kPipe: {
io = fdio_pipe_create(std::move(info.mutable_pipe().socket));
break;
}
case fio::NodeInfo::Tag::kSocket: {
// check the connection state.
zx_signals_t observed;
switch (zx_status_t status = info.socket().socket.wait_one(
ZXSIO_SIGNAL_CONNECTED, zx::time::infinite_past(), &observed)) {
case ZX_OK:
__FALLTHROUGH;
case ZX_ERR_TIMED_OUT:
break;
default:
return status;
}
zx_status_t status = fdio_socket_create(fsocket::Control::SyncClient(std::move(handle)),
std::move(info.mutable_socket().socket), &io);
if (status != ZX_OK) {
return status;
}
if (observed & ZXSIO_SIGNAL_CONNECTED) {
*fdio_get_ioflag(io) |= IOFLAG_SOCKET_CONNECTED;
}
break;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
if (io == nullptr) {
return ZX_ERR_NO_RESOURCES;
}
*out_io = io;
return ZX_OK;
}
// Creates an |fdio_t| from a Zircon channel object.
//
// The |channel| must implement the |fuchsia.io.Node| protocol. Uses the
// |Describe| method from the |fuchsia.io.Node| protocol to determine the type
// of |fdio_t| object to create.
//
// Always consumes |channel|.
static zx_status_t fdio_from_channel(zx::channel channel, fdio_t** out_io) {
auto response = fio::Node::Call::Describe(zx::unowned_channel(channel));
zx_status_t status = response.status();
if (status != ZX_OK) {
return status;
}
return fdio_from_node_info(std::move(channel), std::move(response.Unwrap()->info), out_io);
}
__EXPORT
zx_status_t fdio_create(zx_handle_t handle, fdio_t** out_io) {
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
fdio_t* io = nullptr;
switch (info.type) {
case ZX_OBJ_TYPE_CHANNEL:
return fdio_from_channel(zx::channel(handle), out_io);
case ZX_OBJ_TYPE_SOCKET:
io = fdio_pipe_create(zx::socket(handle));
break;
case ZX_OBJ_TYPE_VMO:
io = fdio_vmo_create(zx::vmo(handle), 0u);
break;
case ZX_OBJ_TYPE_LOG:
io = fdio_logger_create(zx::debuglog(handle));
break;
default: {
zx_handle_close(handle);
return ZX_ERR_INVALID_ARGS;
}
}
if (io == nullptr) {
return ZX_ERR_NO_MEMORY;
}
*out_io = io;
return ZX_OK;
}
// Creates an |fdio_t| by waiting for a |fuchsia.io/Node.OnOpen| event on |channel|.
zx_status_t fdio_from_on_open_event(zx::channel channel, fdio_t** out_io) {
fio::NodeInfo node_info;
zx_status_t on_open_status = ZX_OK;
zx_status_t status = fio::Directory::Call::HandleEvents(
zx::unowned_channel(channel),
fio::Directory::EventHandlers{
.on_open =
[&node_info, &on_open_status](zx_status_t status, fio::NodeInfo* info) {
on_open_status = status;
if (info) {
node_info = std::move(*info);
}
return ZX_OK;
},
.unknown = [] { return ZX_ERR_IO; }});
if (status != ZX_OK) {
return status;
}
if (on_open_status != ZX_OK) {
return on_open_status;
}
return fdio_from_node_info(std::move(channel), std::move(node_info), out_io);
}
zx_status_t fdio_remote_clone(zx_handle_t node, fdio_t** out_io) {
zx::channel handle, request;
zx_status_t status = zx::channel::create(0, &handle, &request);
if (status != ZX_OK) {
return status;
}
status = fio::Node::Call::Clone(zx::unowned_channel(node),
fio::CLONE_FLAG_SAME_RIGHTS | fio::OPEN_FLAG_DESCRIBE,
std::move(request)).status();
if (status != ZX_OK) {
return status;
}
return fdio_from_on_open_event(std::move(handle), out_io);
}
zx_status_t fdio_remote_open_at(zx_handle_t dir, const char* path, uint32_t flags, uint32_t mode,
fdio_t** out_io) {
size_t length;
zx_status_t status = fdio_validate_path(path, &length);
if (status != ZX_OK) {
return status;
}
zx::channel handle, request;
status = zx::channel::create(0, &handle, &request);
if (status != ZX_OK) {
return status;
}
status = fio::Directory::Call::Open(zx::unowned_channel(dir), flags, mode,
fidl::StringView(path, length), std::move(request))
.status();
if (status != ZX_OK) {
return status;
}
if (flags & ZX_FS_FLAG_DESCRIBE) {
return fdio_from_on_open_event(std::move(handle), out_io);
}
fdio_t* io = fdio_remote_create(handle.release(), 0);
if (io == nullptr) {
return ZX_ERR_NO_RESOURCES;
}
*out_io = io;
return ZX_OK;
}