blob: 4b0b557e2a41e2504fc6f8e8b9a83aa5b6adfc1e [file] [log] [blame]
/*
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <fcntl.h>
/* Zephyr headers */
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_spair, CONFIG_NET_SOCKETS_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/net/socket.h>
#include <zephyr/syscall_handler.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/fdtable.h>
#include "sockets_internal.h"
enum {
SPAIR_SIG_CANCEL, /**< operation has been canceled */
SPAIR_SIG_DATA, /**< @ref spair.recv_q has been updated */
};
enum {
SPAIR_FLAG_NONBLOCK = (1 << 0), /**< socket is non-blocking */
};
#define SPAIR_FLAGS_DEFAULT 0
/**
* Socketpair endpoint structure
*
* This structure represents one half of a socketpair (an 'endpoint').
*
* The implementation strives for compatibility with socketpair(2).
*
* Resources contained within this structure are said to be 'local', while
* resources contained within the other half of the socketpair (or other
* endpoint) are said to be 'remote'.
*
* Theory of operation:
* - each end of a socketpair owns a @a recv_q
* - since there is no write queue, data is either written or not
* - read and write operations may return partial transfers
* - read operations may block if the local @a recv_q is empty
* - write operations may block if the remote @a recv_q is full
* - each endpoint may be blocking or non-blocking
*/
__net_socket struct spair {
int remote; /**< the remote endpoint file descriptor */
uint32_t flags; /**< status and option bits */
struct k_sem sem; /**< semaphore for exclusive structure access */
struct k_pipe recv_q; /**< receive queue of local endpoint */
/** indicates local @a recv_q isn't empty */
struct k_poll_signal readable;
/** indicates local @a recv_q isn't full */
struct k_poll_signal writeable;
/** buffer for @a recv_q recv_q */
uint8_t buf[CONFIG_NET_SOCKETPAIR_BUFFER_SIZE];
};
/* forward declaration */
static const struct socket_op_vtable spair_fd_op_vtable;
#undef sock_is_nonblock
/** Determine if a @ref spair is in non-blocking mode */
static inline bool sock_is_nonblock(const struct spair *spair)
{
return !!(spair->flags & SPAIR_FLAG_NONBLOCK);
}
/** Determine if a @ref spair is connected */
static inline bool sock_is_connected(const struct spair *spair)
{
const struct spair *remote = z_get_fd_obj(spair->remote,
(const struct fd_op_vtable *)&spair_fd_op_vtable, 0);
if (remote == NULL) {
return false;
}
return true;
}
#undef sock_is_eof
/** Determine if a @ref spair has encountered end-of-file */
static inline bool sock_is_eof(const struct spair *spair)
{
return !sock_is_connected(spair);
}
/**
* Determine bytes available to write
*
* Specifically, this function calculates the number of bytes that may be
* written to a given @ref spair without blocking.
*/
static inline size_t spair_write_avail(struct spair *spair)
{
struct spair *const remote = z_get_fd_obj(spair->remote,
(const struct fd_op_vtable *)&spair_fd_op_vtable, 0);
if (remote == NULL) {
return 0;
}
return k_pipe_write_avail(&remote->recv_q);
}
/**
* Determine bytes available to read
*
* Specifically, this function calculates the number of bytes that may be
* read from a given @ref spair without blocking.
*/
static inline size_t spair_read_avail(struct spair *spair)
{
return k_pipe_read_avail(&spair->recv_q);
}
/** Swap two 32-bit integers */
static inline void swap32(uint32_t *a, uint32_t *b)
{
uint32_t c;
c = *b;
*b = *a;
*a = c;
}
/**
* Delete @param spair
*
* This function deletes one endpoint of a socketpair.
*
* Theory of operation:
* - we have a socketpair with two endpoints: A and B
* - we have two threads: T1 and T2
* - T1 operates on endpoint A
* - T2 operates on endpoint B
*
* There are two possible cases where a blocking operation must be notified
* when one endpoint is closed:
* -# T1 is blocked reading from A and T2 closes B
* T1 waits on A's write signal. T2 triggers the remote
* @ref spair.readable
* -# T1 is blocked writing to A and T2 closes B
* T1 is waits on B's read signal. T2 triggers the local
* @ref spair.writeable.
*
* If the remote endpoint is already closed, the former operation does not
* take place. Otherwise, the @ref spair.remote of the local endpoint is
* set to -1.
*
* If no threads are blocking on A, then the signals have no effect.
*
* The memory associated with the local endpoint is cleared and freed.
*/
static void spair_delete(struct spair *spair)
{
int res;
struct spair *remote = NULL;
bool have_remote_sem = false;
if (spair == NULL) {
return;
}
if (spair->remote != -1) {
remote = z_get_fd_obj(spair->remote,
(const struct fd_op_vtable *)&spair_fd_op_vtable, 0);
if (remote != NULL) {
res = k_sem_take(&remote->sem, K_FOREVER);
if (res == 0) {
have_remote_sem = true;
remote->remote = -1;
res = k_poll_signal_raise(&remote->readable,
SPAIR_SIG_CANCEL);
__ASSERT(res == 0,
"k_poll_signal_raise() failed: %d",
res);
}
}
}
spair->remote = -1;
res = k_poll_signal_raise(&spair->writeable, SPAIR_SIG_CANCEL);
__ASSERT(res == 0, "k_poll_signal_raise() failed: %d", res);
/* ensure no private information is released to the memory pool */
memset(spair, 0, sizeof(*spair));
#ifdef CONFIG_USERSPACE
k_object_free(spair);
#else
k_free(spair);
#endif
if (remote != NULL && have_remote_sem) {
k_sem_give(&remote->sem);
}
}
/**
* Create a @ref spair (1/2 of a socketpair)
*
* The idea is to call this twice, but store the "local" side in the
* @ref spair.remote field initially.
*
* If both allocations are successful, then swap the @ref spair.remote
* fields in the two @ref spair instances.
*/
static struct spair *spair_new(void)
{
struct spair *spair;
int res;
#ifdef CONFIG_USERSPACE
struct z_object *zo = z_dynamic_object_create(sizeof(*spair));
if (zo == NULL) {
spair = NULL;
} else {
spair = zo->name;
zo->type = K_OBJ_NET_SOCKET;
}
#else
spair = k_malloc(sizeof(*spair));
#endif
if (spair == NULL) {
errno = ENOMEM;
goto out;
}
memset(spair, 0, sizeof(*spair));
/* initialize any non-zero default values */
spair->remote = -1;
spair->flags = SPAIR_FLAGS_DEFAULT;
k_sem_init(&spair->sem, 1, 1);
k_pipe_init(&spair->recv_q, spair->buf, sizeof(spair->buf));
k_poll_signal_init(&spair->readable);
k_poll_signal_init(&spair->writeable);
/* A new socket is always writeable after creation */
res = k_poll_signal_raise(&spair->writeable, SPAIR_SIG_DATA);
__ASSERT(res == 0, "k_poll_signal_raise() failed: %d", res);
spair->remote = z_reserve_fd();
if (spair->remote == -1) {
errno = ENFILE;
goto cleanup;
}
z_finalize_fd(spair->remote, spair,
(const struct fd_op_vtable *)&spair_fd_op_vtable);
goto out;
cleanup:
spair_delete(spair);
spair = NULL;
out:
return spair;
}
int z_impl_zsock_socketpair(int family, int type, int proto, int *sv)
{
int res;
size_t i;
struct spair *obj[2] = {};
if (family != AF_UNIX) {
errno = EAFNOSUPPORT;
res = -1;
goto errout;
}
if (type != SOCK_STREAM) {
errno = EPROTOTYPE;
res = -1;
goto errout;
}
if (proto != 0) {
errno = EPROTONOSUPPORT;
res = -1;
goto errout;
}
if (sv == NULL) {
/* not listed in normative spec, but mimics Linux behaviour */
errno = EFAULT;
res = -1;
goto errout;
}
for (i = 0; i < 2; ++i) {
obj[i] = spair_new();
if (!obj[i]) {
res = -1;
goto cleanup;
}
}
/* connect the two endpoints */
swap32(&obj[0]->remote, &obj[1]->remote);
for (i = 0; i < 2; ++i) {
sv[i] = obj[i]->remote;
k_sem_give(&obj[0]->sem);
}
return 0;
cleanup:
for (i = 0; i < 2; ++i) {
spair_delete(obj[i]);
}
errout:
return res;
}
#ifdef CONFIG_USERSPACE
int z_vrfy_zsock_socketpair(int family, int type, int proto, int *sv)
{
int ret;
int tmp[2];
if (!sv || Z_SYSCALL_MEMORY_WRITE(sv, sizeof(tmp)) != 0) {
/* not listed in normative spec, but mimics linux behaviour */
errno = EFAULT;
ret = -1;
goto out;
}
ret = z_impl_zsock_socketpair(family, type, proto, tmp);
if (ret == 0) {
Z_OOPS(z_user_to_copy(sv, tmp, sizeof(tmp)));
}
out:
return ret;
}
#include <syscalls/zsock_socketpair_mrsh.c>
#endif /* CONFIG_USERSPACE */
/**
* Write data to one end of a @ref spair
*
* Data written on one file descriptor of a socketpair can be read at the
* other end using common POSIX calls such as read(2) or recv(2).
*
* If the underlying file descriptor has the @ref O_NONBLOCK flag set then
* this function will return immediately. If no data was written on a
* non-blocking file descriptor, then -1 will be returned and @ref errno will
* be set to @ref EAGAIN.
*
* Blocking write operations occur when the @ref O_NONBLOCK flag is @em not
* set and there is insufficient space in the @em remote @ref spair.pipe.
*
* Such a blocking write will suspend execution of the current thread until
* one of two possible results is received on the @em remote
* @ref spair.writeable:
*
* 1) @ref SPAIR_SIG_DATA - data has been read from the @em remote
* @ref spair.pipe. Thus, allowing more data to be written.
*
* 2) @ref SPAIR_SIG_CANCEL - the @em remote socketpair endpoint was closed
* Receipt of this result is analogous to SIGPIPE from POSIX
* ("Write on a pipe with no one to read it."). In this case, the function
* will return -1 and set @ref errno to @ref EPIPE.
*
* @param obj the address of an @ref spair object cast to `void *`
* @param buffer the buffer to write
* @param count the number of bytes to write from @p buffer
*
* @return on success, a number > 0 representing the number of bytes written
* @return -1 on error, with @ref errno set appropriately.
*/
static ssize_t spair_write(void *obj, const void *buffer, size_t count)
{
int res;
size_t avail;
bool is_nonblock;
size_t bytes_written;
bool have_local_sem = false;
bool have_remote_sem = false;
bool will_block = false;
struct spair *const spair = (struct spair *)obj;
struct spair *remote = NULL;
if (obj == NULL || buffer == NULL || count == 0) {
errno = EINVAL;
res = -1;
goto out;
}
res = k_sem_take(&spair->sem, K_NO_WAIT);
is_nonblock = sock_is_nonblock(spair);
if (res < 0) {
if (is_nonblock) {
errno = EAGAIN;
res = -1;
goto out;
}
res = k_sem_take(&spair->sem, K_FOREVER);
if (res < 0) {
errno = -res;
res = -1;
goto out;
}
is_nonblock = sock_is_nonblock(spair);
}
have_local_sem = true;
remote = z_get_fd_obj(spair->remote,
(const struct fd_op_vtable *)&spair_fd_op_vtable, 0);
if (remote == NULL) {
errno = EPIPE;
res = -1;
goto out;
}
res = k_sem_take(&remote->sem, K_NO_WAIT);
if (res < 0) {
if (is_nonblock) {
errno = EAGAIN;
res = -1;
goto out;
}
res = k_sem_take(&remote->sem, K_FOREVER);
if (res < 0) {
errno = -res;
res = -1;
goto out;
}
}
have_remote_sem = true;
avail = spair_write_avail(spair);
if (avail == 0) {
if (is_nonblock) {
errno = EAGAIN;
res = -1;
goto out;
}
will_block = true;
}
if (will_block) {
for (int signaled = false, result = -1; !signaled;
result = -1) {
struct k_poll_event events[] = {
K_POLL_EVENT_INITIALIZER(
K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY,
&remote->writeable),
};
k_sem_give(&remote->sem);
have_remote_sem = false;
res = k_poll(events, ARRAY_SIZE(events), K_FOREVER);
if (res < 0) {
errno = -res;
res = -1;
goto out;
}
remote = z_get_fd_obj(spair->remote,
(const struct fd_op_vtable *)
&spair_fd_op_vtable, 0);
if (remote == NULL) {
errno = EPIPE;
res = -1;
goto out;
}
res = k_sem_take(&remote->sem, K_FOREVER);
if (res < 0) {
errno = -res;
res = -1;
goto out;
}
have_remote_sem = true;
k_poll_signal_check(&remote->writeable, &signaled,
&result);
if (!signaled) {
continue;
}
switch (result) {
case SPAIR_SIG_DATA: {
break;
}
case SPAIR_SIG_CANCEL: {
errno = EPIPE;
res = -1;
goto out;
}
default: {
__ASSERT(false,
"unrecognized result: %d",
result);
continue;
}
}
/* SPAIR_SIG_DATA was received */
break;
}
}
res = k_pipe_put(&remote->recv_q, (void *)buffer, count,
&bytes_written, 1, K_NO_WAIT);
__ASSERT(res == 0, "k_pipe_put() failed: %d", res);
if (spair_write_avail(spair) == 0) {
k_poll_signal_reset(&remote->writeable);
}
res = k_poll_signal_raise(&remote->readable, SPAIR_SIG_DATA);
__ASSERT(res == 0, "k_poll_signal_raise() failed: %d", res);
res = bytes_written;
out:
if (remote != NULL && have_remote_sem) {
k_sem_give(&remote->sem);
}
if (spair != NULL && have_local_sem) {
k_sem_give(&spair->sem);
}
return res;
}
/**
* Read data from one end of a @ref spair
*
* Data written on one file descriptor of a socketpair (with e.g. write(2) or
* send(2)) can be read at the other end using common POSIX calls such as
* read(2) or recv(2).
*
* If the underlying file descriptor has the @ref O_NONBLOCK flag set then
* this function will return immediately. If no data was read from a
* non-blocking file descriptor, then -1 will be returned and @ref errno will
* be set to @ref EAGAIN.
*
* Blocking read operations occur when the @ref O_NONBLOCK flag is @em not set
* and there are no bytes to read in the @em local @ref spair.pipe.
*
* Such a blocking read will suspend execution of the current thread until
* one of two possible results is received on the @em local
* @ref spair.readable:
*
* -# @ref SPAIR_SIG_DATA - data has been written to the @em local
* @ref spair.pipe. Thus, allowing more data to be read.
*
* -# @ref SPAIR_SIG_CANCEL - read of the the @em local @spair.pipe
* must be cancelled for some reason (e.g. the file descriptor will be
* closed imminently). In this case, the function will return -1 and set
* @ref errno to @ref EINTR.
*
* @param obj the address of an @ref spair object cast to `void *`
* @param buffer the buffer in which to read
* @param count the number of bytes to read
*
* @return on success, a number > 0 representing the number of bytes written
* @return -1 on error, with @ref errno set appropriately.
*/
static ssize_t spair_read(void *obj, void *buffer, size_t count)
{
int res;
bool is_connected;
size_t avail;
bool is_nonblock;
size_t bytes_read;
bool have_local_sem = false;
bool will_block = false;
struct spair *const spair = (struct spair *)obj;
if (obj == NULL || buffer == NULL || count == 0) {
errno = EINVAL;
res = -1;
goto out;
}
res = k_sem_take(&spair->sem, K_NO_WAIT);
is_nonblock = sock_is_nonblock(spair);
if (res < 0) {
if (is_nonblock) {
errno = EAGAIN;
res = -1;
goto out;
}
res = k_sem_take(&spair->sem, K_FOREVER);
if (res < 0) {
errno = -res;
res = -1;
goto out;
}
is_nonblock = sock_is_nonblock(spair);
}
have_local_sem = true;
is_connected = sock_is_connected(spair);
avail = spair_read_avail(spair);
if (avail == 0) {
if (!is_connected) {
/* signal EOF */
res = 0;
goto out;
}
if (is_nonblock) {
errno = EAGAIN;
res = -1;
goto out;
}
will_block = true;
}
if (will_block) {
for (int signaled = false, result = -1; !signaled;
result = -1) {
struct k_poll_event events[] = {
K_POLL_EVENT_INITIALIZER(
K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY,
&spair->readable
),
};
k_sem_give(&spair->sem);
have_local_sem = false;
res = k_poll(events, ARRAY_SIZE(events), K_FOREVER);
__ASSERT(res == 0, "k_poll() failed: %d", res);
res = k_sem_take(&spair->sem, K_FOREVER);
__ASSERT(res == 0, "failed to take local sem: %d", res);
have_local_sem = true;
k_poll_signal_check(&spair->readable, &signaled,
&result);
if (!signaled) {
continue;
}
switch (result) {
case SPAIR_SIG_DATA: {
break;
}
case SPAIR_SIG_CANCEL: {
errno = EPIPE;
res = -1;
goto out;
}
default: {
__ASSERT(false,
"unrecognized result: %d",
result);
continue;
}
}
/* SPAIR_SIG_DATA was received */
break;
}
}
res = k_pipe_get(&spair->recv_q, (void *)buffer, count, &bytes_read,
1, K_NO_WAIT);
__ASSERT(res == 0, "k_pipe_get() failed: %d", res);
if (spair_read_avail(spair) == 0 && !sock_is_eof(spair)) {
k_poll_signal_reset(&spair->readable);
}
if (is_connected) {
res = k_poll_signal_raise(&spair->writeable, SPAIR_SIG_DATA);
__ASSERT(res == 0, "k_poll_signal_raise() failed: %d", res);
}
res = bytes_read;
out:
if (spair != NULL && have_local_sem) {
k_sem_give(&spair->sem);
}
return res;
}
static int zsock_poll_prepare_ctx(struct spair *const spair,
struct zsock_pollfd *const pfd,
struct k_poll_event **pev,
struct k_poll_event *pev_end)
{
int res;
struct spair *remote = NULL;
bool have_remote_sem = false;
if (pfd->events & ZSOCK_POLLIN) {
/* Tell poll() to short-circuit wait */
if (sock_is_eof(spair)) {
res = -EALREADY;
goto out;
}
if (*pev == pev_end) {
res = -ENOMEM;
goto out;
}
/* Wait until data has been written to the local end */
(*pev)->obj = &spair->readable;
}
if (pfd->events & ZSOCK_POLLOUT) {
/* Tell poll() to short-circuit wait */
if (!sock_is_connected(spair)) {
res = -EALREADY;
goto out;
}
if (*pev == pev_end) {
res = -ENOMEM;
goto out;
}
remote = z_get_fd_obj(spair->remote,
(const struct fd_op_vtable *)
&spair_fd_op_vtable, 0);
__ASSERT(remote != NULL, "remote is NULL");
res = k_sem_take(&remote->sem, K_FOREVER);
if (res < 0) {
goto out;
}
have_remote_sem = true;
/* Wait until the recv queue on the remote end is no longer full */
(*pev)->obj = &remote->writeable;
}
(*pev)->type = K_POLL_TYPE_SIGNAL;
(*pev)->mode = K_POLL_MODE_NOTIFY_ONLY;
(*pev)->state = K_POLL_STATE_NOT_READY;
(*pev)++;
res = 0;
out:
if (remote != NULL && have_remote_sem) {
k_sem_give(&remote->sem);
}
return res;
}
static int zsock_poll_update_ctx(struct spair *const spair,
struct zsock_pollfd *const pfd,
struct k_poll_event **pev)
{
int res;
int signaled;
int result;
struct spair *remote = NULL;
bool have_remote_sem = false;
if (pfd->events & ZSOCK_POLLOUT) {
if (!sock_is_connected(spair)) {
pfd->revents |= ZSOCK_POLLHUP;
goto pollout_done;
}
remote = z_get_fd_obj(spair->remote,
(const struct fd_op_vtable *) &spair_fd_op_vtable, 0);
__ASSERT(remote != NULL, "remote is NULL");
res = k_sem_take(&remote->sem, K_FOREVER);
if (res < 0) {
/* if other end is deleted, this might occur */
goto pollout_done;
}
have_remote_sem = true;
if (spair_write_avail(spair) > 0) {
pfd->revents |= ZSOCK_POLLOUT;
goto pollout_done;
}
/* check to see if op was canceled */
signaled = false;
k_poll_signal_check(&remote->writeable, &signaled, &result);
if (signaled) {
/* Cannot be SPAIR_SIG_DATA, because
* spair_write_avail() would have
* returned 0
*/
__ASSERT(result == SPAIR_SIG_CANCEL,
"invalid result %d", result);
pfd->revents |= ZSOCK_POLLHUP;
}
}
pollout_done:
if (pfd->events & ZSOCK_POLLIN) {
if (sock_is_eof(spair)) {
pfd->revents |= ZSOCK_POLLIN;
goto pollin_done;
}
if (spair_read_avail(spair) > 0) {
pfd->revents |= ZSOCK_POLLIN;
goto pollin_done;
}
/* check to see if op was canceled */
signaled = false;
k_poll_signal_check(&spair->readable, &signaled, &result);
if (signaled) {
/* Cannot be SPAIR_SIG_DATA, because
* spair_read_avail() would have
* returned 0
*/
__ASSERT(result == SPAIR_SIG_CANCEL,
"invalid result %d", result);
pfd->revents |= ZSOCK_POLLIN;
}
}
pollin_done:
res = 0;
(*pev)++;
if (remote != NULL && have_remote_sem) {
k_sem_give(&remote->sem);
}
return res;
}
static int spair_ioctl(void *obj, unsigned int request, va_list args)
{
int res;
struct zsock_pollfd *pfd;
struct k_poll_event **pev;
struct k_poll_event *pev_end;
int flags = 0;
bool have_local_sem = false;
struct spair *const spair = (struct spair *)obj;
if (spair == NULL) {
errno = EINVAL;
res = -1;
goto out;
}
/* The local sem is always taken in this function. If a subsequent
* function call requires the remote sem, it must acquire and free the
* remote sem.
*/
res = k_sem_take(&spair->sem, K_FOREVER);
__ASSERT(res == 0, "failed to take local sem: %d", res);
have_local_sem = true;
switch (request) {
case F_GETFL: {
if (sock_is_nonblock(spair)) {
flags |= O_NONBLOCK;
}
res = flags;
goto out;
}
case F_SETFL: {
flags = va_arg(args, int);
if (flags & O_NONBLOCK) {
spair->flags |= SPAIR_FLAG_NONBLOCK;
} else {
spair->flags &= ~SPAIR_FLAG_NONBLOCK;
}
res = 0;
goto out;
}
case ZFD_IOCTL_POLL_PREPARE: {
pfd = va_arg(args, struct zsock_pollfd *);
pev = va_arg(args, struct k_poll_event **);
pev_end = va_arg(args, struct k_poll_event *);
res = zsock_poll_prepare_ctx(obj, pfd, pev, pev_end);
goto out;
}
case ZFD_IOCTL_POLL_UPDATE: {
pfd = va_arg(args, struct zsock_pollfd *);
pev = va_arg(args, struct k_poll_event **);
res = zsock_poll_update_ctx(obj, pfd, pev);
goto out;
}
default: {
errno = EOPNOTSUPP;
res = -1;
goto out;
}
}
out:
if (spair != NULL && have_local_sem) {
k_sem_give(&spair->sem);
}
return res;
}
static int spair_bind(void *obj, const struct sockaddr *addr,
socklen_t addrlen)
{
ARG_UNUSED(obj);
ARG_UNUSED(addr);
ARG_UNUSED(addrlen);
errno = EISCONN;
return -1;
}
static int spair_connect(void *obj, const struct sockaddr *addr,
socklen_t addrlen)
{
ARG_UNUSED(obj);
ARG_UNUSED(addr);
ARG_UNUSED(addrlen);
errno = EISCONN;
return -1;
}
static int spair_listen(void *obj, int backlog)
{
ARG_UNUSED(obj);
ARG_UNUSED(backlog);
errno = EINVAL;
return -1;
}
static int spair_accept(void *obj, struct sockaddr *addr,
socklen_t *addrlen)
{
ARG_UNUSED(obj);
ARG_UNUSED(addr);
ARG_UNUSED(addrlen);
errno = EOPNOTSUPP;
return -1;
}
static ssize_t spair_sendto(void *obj, const void *buf, size_t len,
int flags, const struct sockaddr *dest_addr,
socklen_t addrlen)
{
ARG_UNUSED(flags);
ARG_UNUSED(dest_addr);
ARG_UNUSED(addrlen);
return spair_write(obj, buf, len);
}
static ssize_t spair_sendmsg(void *obj, const struct msghdr *msg,
int flags)
{
ARG_UNUSED(flags);
int res;
size_t len = 0;
bool is_connected;
size_t avail;
bool is_nonblock;
struct spair *const spair = (struct spair *)obj;
if (spair == NULL || msg == NULL) {
errno = EINVAL;
res = -1;
goto out;
}
is_connected = sock_is_connected(spair);
avail = is_connected ? spair_write_avail(spair) : 0;
is_nonblock = sock_is_nonblock(spair);
for (size_t i = 0; i < msg->msg_iovlen; ++i) {
/* check & msg->msg_iov[i]? */
/* check & msg->msg_iov[i].iov_base? */
len += msg->msg_iov[i].iov_len;
}
if (!is_connected) {
errno = EPIPE;
res = -1;
goto out;
}
if (len == 0) {
res = 0;
goto out;
}
if (len > avail && is_nonblock) {
errno = EMSGSIZE;
res = -1;
goto out;
}
for (size_t i = 0; i < msg->msg_iovlen; ++i) {
res = spair_write(spair, msg->msg_iov[i].iov_base,
msg->msg_iov[i].iov_len);
if (res == -1) {
goto out;
}
}
res = len;
out:
return res;
}
static ssize_t spair_recvfrom(void *obj, void *buf, size_t max_len,
int flags, struct sockaddr *src_addr,
socklen_t *addrlen)
{
(void)flags;
(void)src_addr;
(void)addrlen;
if (addrlen != NULL) {
/* Protocol (PF_UNIX) does not support addressing with connected
* sockets and, therefore, it is unspecified behaviour to modify
* src_addr. However, it would be ambiguous to leave addrlen
* untouched if the user expects it to be updated. It is not
* mentioned that modifying addrlen is unspecified. Therefore
* we choose to eliminate ambiguity.
*
* Setting it to zero mimics Linux's behaviour.
*/
*addrlen = 0;
}
return spair_read(obj, buf, max_len);
}
static int spair_getsockopt(void *obj, int level, int optname,
void *optval, socklen_t *optlen)
{
ARG_UNUSED(obj);
ARG_UNUSED(level);
ARG_UNUSED(optname);
ARG_UNUSED(optval);
ARG_UNUSED(optlen);
errno = ENOPROTOOPT;
return -1;
}
static int spair_setsockopt(void *obj, int level, int optname,
const void *optval, socklen_t optlen)
{
ARG_UNUSED(obj);
ARG_UNUSED(level);
ARG_UNUSED(optname);
ARG_UNUSED(optval);
ARG_UNUSED(optlen);
errno = ENOPROTOOPT;
return -1;
}
static int spair_close(void *obj)
{
struct spair *const spair = (struct spair *)obj;
int res;
res = k_sem_take(&spair->sem, K_FOREVER);
__ASSERT(res == 0, "failed to take local sem: %d", res);
/* disconnect the remote endpoint */
spair_delete(spair);
/* Note that the semaphore released already so need to do it here */
return 0;
}
static const struct socket_op_vtable spair_fd_op_vtable = {
.fd_vtable = {
.read = spair_read,
.write = spair_write,
.close = spair_close,
.ioctl = spair_ioctl,
},
.bind = spair_bind,
.connect = spair_connect,
.listen = spair_listen,
.accept = spair_accept,
.sendto = spair_sendto,
.sendmsg = spair_sendmsg,
.recvfrom = spair_recvfrom,
.getsockopt = spair_getsockopt,
.setsockopt = spair_setsockopt,
};