blob: a54bdca1cefbfc7acd3d2e0a346a316d3f5dc58b [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 <fuchsia/device/c/fidl.h>
#include <fuchsia/io/c/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/fdio/io.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <string.h>
#include <zircon/device/vfs.h>
#include <zircon/syscalls.h>
#include "private.h"
#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(fuchsia_io_VMO_FLAG_READ == ZX_VM_PERM_READ,
"Vmar / Vmo flags should be aligned");
static_assert(fuchsia_io_VMO_FLAG_WRITE == ZX_VM_PERM_WRITE,
"Vmar / Vmo flags should be aligned");
static_assert(fuchsia_io_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) == fuchsia_device_DEVICE_SIGNAL_READABLE, "");
static_assert((POLLPRI << POLL_SHIFT) == fuchsia_device_DEVICE_SIGNAL_OOB, "");
static_assert((POLLOUT << POLL_SHIFT) == fuchsia_device_DEVICE_SIGNAL_WRITABLE, "");
static_assert((POLLERR << POLL_SHIFT) == fuchsia_device_DEVICE_SIGNAL_ERROR, "");
static_assert((POLLHUP << POLL_SHIFT) == fuchsia_device_DEVICE_SIGNAL_HANGUP, "");
// The |mode| argument used for |fuchsia.io.Directory/Open| calls.
#define FDIO_CONNECT_MODE ((uint32_t)0755)
// Validates a |path| argument.
//
// Returns ZX_OK if |path| is non-null and less than |PATH_MAX| in length
// (excluding the null terminator). Upon success, the length of the path is
// returned via |out_length|.
//
// Otherwise, returns |ZX_ERR_INVALID_ARGS|.
static 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;
}
*out_length = length;
return ZX_OK;
}
__EXPORT
zx_status_t fdio_service_connect(const char* path, zx_handle_t h) {
// TODO: fdio_validate_path?
if (path == nullptr) {
zx_handle_close(h);
return ZX_ERR_INVALID_ARGS;
}
// Otherwise attempt to connect through the root namespace
if (fdio_root_ns != nullptr) {
return fdio_ns_connect(fdio_root_ns, path, ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE, h);
}
// Otherwise we fail
zx_handle_close(h);
return ZX_ERR_NOT_FOUND;
}
__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 = ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE;
return fuchsia::io::Directory::Call::Open(zx::unowned_channel(dir),
flags,
FDIO_CONNECT_MODE,
fidl::StringView(length, path),
std::move(request));
}
__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
if (fdio_root_ns != nullptr) {
return fdio_ns_connect(fdio_root_ns, path, flags, request);
}
// Otherwise we fail
zx_handle_close(request);
return ZX_ERR_NOT_FOUND;
}
__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 = 0u;
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 fuchsia::io::Directory::Call::Open(zx::unowned_channel(dir),
flags,
FDIO_CONNECT_MODE,
fidl::StringView(length, path),
std::move(request));
}
__EXPORT
zx_handle_t fdio_service_clone(zx_handle_t handle) {
if (handle == ZX_HANDLE_INVALID) {
return ZX_HANDLE_INVALID;
}
zx::channel clone, request;
zx_status_t status = zx::channel::create(0, &clone, &request);
if (status != ZX_OK) {
return ZX_HANDLE_INVALID;
}
uint32_t flags = ZX_FS_FLAG_CLONE_SAME_RIGHTS;
status = fuchsia::io::Node::Call::Clone(zx::unowned_channel(handle), flags, std::move(request));
if (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 fuchsia::io::Node::Call::Clone(zx::unowned_channel(handle), flags, std::move(request));
}
// Creates an |fdio_t| from a Zircon socket object.
//
// Examines |socket| and determines whether to create a pipe, stream socket, or
// datagram socket.
//
// Always consumes |socket|.
static zx_status_t fdio_from_socket(zx_handle_t socket, fdio_t** out_io) {
zx_info_socket_t info;
memset(&info, 0, sizeof(info));
zx_status_t status = zx_object_get_info(socket,
ZX_INFO_SOCKET,
&info,
sizeof(info),
nullptr,
nullptr);
if (status != ZX_OK) {
zx_handle_close(socket);
return status;
}
fdio_t* io = nullptr;
if ((info.options & ZX_SOCKET_HAS_CONTROL) != 0) {
// If the socket has a control plane, then the socket is either
// a stream or a datagram socket.
if ((info.options & ZX_SOCKET_DATAGRAM) != 0) {
io = fdio_socket_create_datagram(socket, IOFLAG_SOCKET_CONNECTED);
} else {
io = fdio_socket_create_stream(socket, IOFLAG_SOCKET_CONNECTED);
}
} else {
// Without a control plane, the socket is a pipe.
io = fdio_pipe_create(socket);
}
if (!io) {
return ZX_ERR_NO_RESOURCES;
}
*out_io = io;
return ZX_OK;
}
// Create an |fdio_t| from a |handle| and an |info|.
//
// Uses |info| to determine what kind of |fdio_t| to create.
//
// Upon success, |out_io| receives ownership of all handles.
//
// Upon failure, consumes all handles.
static zx_status_t fdio_from_node_info(zx_handle_t raw_handle,
fuchsia::io::NodeInfo info,
fdio_t** out_io) {
zx::handle handle(raw_handle);
fdio_t* io = nullptr;
zx_status_t status = ZX_OK;
if (!handle.is_valid()) {
return ZX_ERR_INVALID_ARGS;
}
switch (info.which()) {
case fuchsia::io::NodeInfo::Tag::kDirectory:
io = fdio_dir_create(handle.release());
break;
case fuchsia::io::NodeInfo::Tag::kService:
io = fdio_remote_create(handle.release(), 0);
break;
case fuchsia::io::NodeInfo::Tag::kFile:
io = fdio_file_create(handle.release(), info.mutable_file().event.release());
break;
case fuchsia::io::NodeInfo::Tag::kDevice:
io = fdio_remote_create(handle.release(), info.mutable_device().event.release());
break;
case fuchsia::io::NodeInfo::Tag::kTty:
io = fdio_remote_create(handle.release(), info.mutable_tty().event.release());
break;
case fuchsia::io::NodeInfo::Tag::kVmofile: {
uint64_t seek = 0u;
zx_status_t io_status = fuchsia_io_FileSeek(
handle.get(), 0, fuchsia_io_SeekOrigin_START, &status, &seek);
if (io_status != ZX_OK) {
status = io_status;
}
if (status != ZX_OK) {
return status;
}
io = fdio_vmofile_create(handle.release(),
info.mutable_vmofile().vmo.release(),
info.vmofile().offset,
info.vmofile().length,
seek);
break;
}
case fuchsia::io::NodeInfo::Tag::kPipe: {
return fdio_from_socket(info.mutable_pipe().socket.release(), out_io);
}
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_handle_t channel, fdio_t** out_io) {
fuchsia::io::NodeInfo info;
zx_status_t status = fuchsia::io::Node::Call::Describe(zx::unowned_channel(channel), &info);
if (status != ZX_OK) {
zx_handle_close(channel);
return status;
}
return fdio_from_node_info(channel, std::move(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(handle, out_io);
case ZX_OBJ_TYPE_SOCKET:
return fdio_from_socket(handle, out_io);
case ZX_OBJ_TYPE_VMO:
io = fdio_vmo_create(handle, 0u);
break;
case ZX_OBJ_TYPE_LOG:
io = fdio_logger_create(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;
}
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 = 0u;
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 = fuchsia::io::Directory::Call::Open(zx::unowned_channel(dir),
flags,
mode,
fidl::StringView(length, path),
std::move(request));
if (status != ZX_OK) {
return status;
}
if (flags & ZX_FS_FLAG_DESCRIBE) {
fuchsia::io::NodeInfo node_info;
zx_status_t on_open_status = ZX_OK;
status = fuchsia::io::Directory::Call::HandleEvents(zx::unowned_channel(handle),
fuchsia::io::Directory::EventHandlers {
.on_open = [&node_info, &on_open_status](zx_status_t status,
fuchsia::io::NodeInfo* info) {
on_open_status = status;
if (info) {
node_info = std::move(*info);
}
return ZX_OK;
},
.unknown = [] {
return ZX_ERR_IO;
}
});
if (status == ZX_ERR_PEER_CLOSED) {
return status;
} else if (status != ZX_OK) {
return ZX_ERR_IO;
} else if (on_open_status != ZX_OK) {
return on_open_status;
}
return fdio_from_node_info(handle.release(), std::move(node_info), 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;
}