blob: f591e49ec2f11778fb45ee4292b447ccc9a6c930 [file] [log] [blame]
// Copyright 2018 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 <lib/zxio/cpp/transitional.h>
#include <lib/zxio/fault_catcher.h>
#include <lib/zxio/null.h>
#include <lib/zxio/ops.h>
#include <sys/stat.h>
#include <zircon/compiler.h>
#include <zircon/syscalls/object.h>
#include <algorithm>
#include "sdk/lib/zxio/private.h"
#include "sdk/lib/zxio/vector.h"
namespace {
template <typename T>
zx_status_t set_sockopt_value(int16_t* out_code, void* output, socklen_t* output_size, T* input) {
if (*output_size < sizeof(T)) {
*out_code = EINVAL;
return ZX_ERR_INVALID_ARGS;
}
memcpy(output, input, sizeof(T));
*output_size = sizeof(T);
*out_code = 0;
return ZX_OK;
}
zx_status_t set_sockopt_value_int(int16_t* out_code, void* output, socklen_t* output_size,
uint32_t input) {
return set_sockopt_value(out_code, output, output_size, &input);
}
// Partial implementation of getsockopt for zx::Socket interpreted as Unix
// domain socket.
zx_status_t getsockopt(uint32_t so_type, zxio_t* io, int level, int optname, void* optval,
socklen_t* optlen, int16_t* out_code) {
if (optval == nullptr || optlen == nullptr) {
*out_code = EFAULT;
return ZX_ERR_INVALID_ARGS;
}
if (*optlen < sizeof(uint32_t) || level != SOL_SOCKET) {
*out_code = EINVAL;
return ZX_ERR_INVALID_ARGS;
}
switch (optname) {
case SO_DOMAIN:
return set_sockopt_value_int(out_code, optval, optlen, AF_UNIX);
case SO_TYPE:
return set_sockopt_value_int(out_code, optval, optlen, so_type);
case SO_PROTOCOL:
return set_sockopt_value_int(out_code, optval, optlen, 0);
case SO_LINGER: {
struct linger response{};
response.l_onoff = 0;
response.l_linger = 0;
return set_sockopt_value(out_code, optval, optlen, &response);
}
default:
*out_code = EINVAL;
return ZX_ERR_INVALID_ARGS;
}
}
} // namespace
static zxio_pipe_t& zxio_get_pipe(zxio_t* io) { return *reinterpret_cast<zxio_pipe_t*>(io); }
static constexpr zxio_ops_t zxio_pipe_ops = []() {
zxio_ops_t ops = zxio_default_ops;
ops.close = [](zxio_t* io, const bool should_wait) {
zxio_get_pipe(io).~zxio_pipe_t();
return ZX_OK;
};
ops.release = [](zxio_t* io, zx_handle_t* out_handle) {
*out_handle = zxio_get_pipe(io).socket.release();
return ZX_OK;
};
ops.clone = [](zxio_t* io, zx_handle_t* out_handle) {
zx::socket out_socket;
zx_status_t status = zxio_get_pipe(io).socket.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_socket);
if (status != ZX_OK) {
return status;
}
*out_handle = out_socket.release();
return ZX_OK;
};
ops.attr_get = [](zxio_t* io, zxio_node_attributes_t* inout_attr) {
if (inout_attr->has.abilities) {
ZXIO_NODE_ATTR_SET(
*inout_attr, abilities,
ZXIO_OPERATION_READ_BYTES | ZXIO_OPERATION_WRITE_BYTES | ZXIO_OPERATION_GET_ATTRIBUTES);
}
if (inout_attr->has.object_type) {
ZXIO_NODE_ATTR_SET(*inout_attr, object_type, ZXIO_OBJECT_TYPE_PIPE);
}
return ZX_OK;
};
ops.wait_begin = [](zxio_t* io, zxio_signals_t zxio_signals, zx_handle_t* out_handle,
zx_signals_t* out_zx_signals) {
*out_handle = zxio_get_pipe(io).socket.get();
zx_signals_t zx_signals = ZX_SIGNAL_NONE;
if (zxio_signals & ZXIO_SIGNAL_READABLE) {
zx_signals |= ZX_SOCKET_READABLE;
}
if (zxio_signals & ZXIO_SIGNAL_WRITABLE) {
zx_signals |= ZX_SOCKET_WRITABLE;
}
if (zxio_signals & ZXIO_SIGNAL_READ_DISABLED) {
zx_signals |= ZX_SOCKET_PEER_WRITE_DISABLED;
}
if (zxio_signals & ZXIO_SIGNAL_WRITE_DISABLED) {
zx_signals |= ZX_SOCKET_WRITE_DISABLED;
}
if (zxio_signals & ZXIO_SIGNAL_READ_THRESHOLD) {
zx_signals |= ZX_SOCKET_READ_THRESHOLD;
}
if (zxio_signals & ZXIO_SIGNAL_WRITE_THRESHOLD) {
zx_signals |= ZX_SOCKET_WRITE_THRESHOLD;
}
if (zxio_signals & ZXIO_SIGNAL_PEER_CLOSED) {
zx_signals |= ZX_SOCKET_PEER_CLOSED;
}
*out_zx_signals = zx_signals;
};
ops.wait_end = [](zxio_t* io, zx_signals_t zx_signals, zxio_signals_t* out_zxio_signals) {
zxio_signals_t zxio_signals = ZXIO_SIGNAL_NONE;
if (zx_signals & ZX_SOCKET_READABLE) {
zxio_signals |= ZXIO_SIGNAL_READABLE;
}
if (zx_signals & ZX_SOCKET_WRITABLE) {
zxio_signals |= ZXIO_SIGNAL_WRITABLE;
}
if (zx_signals & ZX_SOCKET_PEER_WRITE_DISABLED) {
zxio_signals |= ZXIO_SIGNAL_READ_DISABLED;
}
if (zx_signals & ZX_SOCKET_WRITE_DISABLED) {
zxio_signals |= ZXIO_SIGNAL_WRITE_DISABLED;
}
if (zx_signals & ZX_SOCKET_READ_THRESHOLD) {
zxio_signals |= ZXIO_SIGNAL_READ_THRESHOLD;
}
if (zx_signals & ZX_SOCKET_WRITE_THRESHOLD) {
zxio_signals |= ZXIO_SIGNAL_WRITE_THRESHOLD;
}
if (zx_signals & ZX_SOCKET_PEER_CLOSED) {
zxio_signals |= ZXIO_SIGNAL_PEER_CLOSED;
}
*out_zxio_signals = zxio_signals;
};
ops.get_read_buffer_available = [](zxio_t* io, size_t* out_available) {
if (out_available == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
zx_info_socket_t info;
memset(&info, 0, sizeof(info));
zx_status_t status =
zxio_get_pipe(io).socket.get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
*out_available = info.rx_buf_available;
return ZX_OK;
};
ops.shutdown = [](zxio_t* io, zxio_shutdown_options_t options, int16_t* out_code) {
if ((options & ZXIO_SHUTDOWN_OPTIONS_MASK) != options) {
return ZX_ERR_INVALID_ARGS;
}
uint32_t disposition = 0;
if (options & ZXIO_SHUTDOWN_OPTIONS_WRITE) {
disposition = ZX_SOCKET_DISPOSITION_WRITE_DISABLED;
}
uint32_t disposition_peer = 0;
if (options & ZXIO_SHUTDOWN_OPTIONS_READ) {
disposition_peer = ZX_SOCKET_DISPOSITION_WRITE_DISABLED;
}
*out_code = 0;
return zxio_get_pipe(io).socket.set_disposition(disposition, disposition_peer);
};
ops.recvmsg = [](zxio_t* io, struct msghdr* msg, int flags, size_t* out_actual,
int16_t* out_code) {
*out_code = 0;
return zxio_recvmsg_inner(io, msg, flags, out_actual);
};
ops.sendmsg = [](zxio_t* io, const struct msghdr* msg, int flags, size_t* out_actual,
int16_t* out_code) {
*out_code = 0;
return zxio_sendmsg_inner(io, msg, flags, out_actual);
};
ops.getsockname = [](zxio_t* io, struct sockaddr* addr, socklen_t* addrlen, int16_t* out_code) {
if (addrlen == nullptr || (*addrlen != 0 && addr == nullptr)) {
*out_code = EFAULT;
return ZX_OK;
}
struct sockaddr response;
response.sa_family = AF_UNIX;
const socklen_t response_size = sizeof(sa_family_t);
memcpy(addr, &response, std::min(*addrlen, response_size));
*addrlen = response_size;
*out_code = 0;
return ZX_OK;
};
ops.getpeername = ops.getsockname;
ops.flags_get_deprecated = [](zxio_t* io, uint32_t* out_flags) {
zx_info_handle_basic info;
zx_status_t status = zxio_get_pipe(io).socket.get_info(ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
ZX_ASSERT(info.type == ZX_OBJ_TYPE_SOCKET);
fuchsia_io::wire::OpenFlags flags{};
if (info.rights & ZX_RIGHT_READ) {
flags |= fuchsia_io::wire::OpenFlags::kRightReadable;
}
if (info.rights & ZX_RIGHT_WRITE) {
flags |= fuchsia_io::wire::OpenFlags::kRightWritable;
}
*out_flags = static_cast<uint32_t>(flags);
return ZX_OK;
};
ops.flags_set_deprecated = [](zxio_t* io, uint32_t flags) {
zx_info_handle_basic info;
zx_status_t status = zxio_get_pipe(io).socket.get_info(ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
bool set_readable = flags & static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kRightReadable);
bool set_writable = flags & static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kRightWritable);
bool is_readable = info.rights & ZX_RIGHT_READ;
bool is_writable = info.rights & ZX_RIGHT_WRITE;
// Ensure that the supported flags (readable, writeable) match the rights on the socket.
if ((set_readable != is_readable) || (set_writable != is_writable)) {
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
};
ops.flags_get = [](zxio_t* io, uint64_t* out_flags) {
zx_info_handle_basic info;
zx_status_t status = zxio_get_pipe(io).socket.get_info(ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
ZX_ASSERT(info.type == ZX_OBJ_TYPE_SOCKET);
fuchsia_io::wire::Flags flags{};
if (info.rights & ZX_RIGHT_READ) {
flags |= fuchsia_io::wire::Flags::kPermRead;
}
if (info.rights & ZX_RIGHT_WRITE) {
flags |= fuchsia_io::wire::Flags::kPermWrite;
}
*out_flags = static_cast<uint64_t>(flags);
return ZX_OK;
};
ops.flags_set = [](zxio_t* io, uint64_t flags) {
zx_info_handle_basic info;
zx_status_t status = zxio_get_pipe(io).socket.get_info(ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
// Ensure that the supported flags (readable, writeable) match the rights on the socket.
const bool set_readable = flags & static_cast<uint64_t>(fuchsia_io::wire::Flags::kPermRead);
const bool set_writable = flags & static_cast<uint64_t>(fuchsia_io::wire::Flags::kPermWrite);
const bool is_readable = info.rights & ZX_RIGHT_READ;
const bool is_writable = info.rights & ZX_RIGHT_WRITE;
if ((set_readable != is_readable) || (set_writable != is_writable)) {
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
};
return ops;
}();
static constexpr zxio_ops_t zxio_datagram_pipe_ops = []() {
zxio_ops_t ops = zxio_pipe_ops;
ops.readv = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
uint32_t zx_flags = 0;
if (flags & ZXIO_PEEK) {
zx_flags |= ZX_SOCKET_PEEK;
flags &= ~ZXIO_PEEK;
}
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
size_t total = 0;
for (size_t i = 0; i < vector_count; ++i) {
total += vector[i].capacity;
}
std::unique_ptr<uint8_t[]> buf(new uint8_t[total]);
size_t actual;
zx_status_t status = zxio_get_pipe(io).socket.read(zx_flags, buf.get(), total, &actual);
if (status != ZX_OK) {
return status;
}
uint8_t* data = buf.get();
size_t remaining = actual;
return zxio_do_vector(
vector, vector_count, out_actual,
[&](void* buffer, size_t capacity, size_t total_so_far, size_t* out_actual) {
size_t actual = std::min(capacity, remaining);
if (unlikely(!zxio_maybe_faultable_copy(reinterpret_cast<uint8_t*>(buffer), data, actual,
true))) {
return ZX_ERR_INVALID_ARGS;
}
data += actual;
remaining -= actual;
*out_actual = actual;
return ZX_OK;
});
};
ops.writev = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
size_t total = 0;
for (size_t i = 0; i < vector_count; ++i) {
total += vector[i].capacity;
}
std::unique_ptr<uint8_t[]> buf(new uint8_t[total]);
uint8_t* data = buf.get();
for (size_t i = 0; i < vector_count; ++i) {
if (unlikely(!zxio_maybe_faultable_copy(data,
reinterpret_cast<const uint8_t*>(vector[i].buffer),
vector[i].capacity, false))) {
return ZX_ERR_INVALID_ARGS;
}
data += vector[i].capacity;
}
return zxio_get_pipe(io).socket.write(0, buf.get(), total, out_actual);
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
return getsockopt(SOCK_DGRAM, io, level, optname, optval, optlen, out_code);
};
return ops;
}();
static constexpr zxio_ops_t zxio_stream_pipe_ops = []() {
zxio_ops_t ops = zxio_pipe_ops;
ops.readv = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
if (flags & ZXIO_PEEK) {
return zxio_datagram_pipe_ops.readv(io, vector, vector_count, flags, out_actual);
}
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
zx::socket& socket = zxio_get_pipe(io).socket;
return zxio_stream_do_vector(vector, vector_count, out_actual,
[&](void* buffer, size_t capacity, size_t* out_actual) {
return socket.read(0, buffer, capacity, out_actual);
});
};
ops.writev = [](zxio_t* io, const zx_iovec_t* vector, size_t vector_count, zxio_flags_t flags,
size_t* out_actual) {
if (flags) {
return ZX_ERR_NOT_SUPPORTED;
}
return zxio_stream_do_vector(
vector, vector_count, out_actual, [&](void* buffer, size_t capacity, size_t* out_actual) {
return zxio_get_pipe(io).socket.write(0, buffer, capacity, out_actual);
});
};
ops.getsockopt = [](zxio_t* io, int level, int optname, void* optval, socklen_t* optlen,
int16_t* out_code) {
return getsockopt(SOCK_STREAM, io, level, optname, optval, optlen, out_code);
};
return ops;
}();
zx_status_t zxio_pipe_init(zxio_storage_t* storage, zx::socket socket, zx_info_socket_t info) {
auto pipe = new (storage) zxio_pipe_t{
.io = storage->io,
.socket = std::move(socket),
};
zxio_init(&pipe->io,
info.options & ZX_SOCKET_DATAGRAM ? &zxio_datagram_pipe_ops : &zxio_stream_pipe_ops);
return ZX_OK;
}