zephyr/subsys/net/lib/sockets/socketpair.c
Chaitanya Tata 40ee8791f2 net: socketpair: Fix use after free
In low memory conditions, its possible for socketpair memory allocation
to fail and then the socketpair is freed but after that the remote
semaphore is released causing a crash.

Fix this by freeing the socketpair after releasing the semaphore. Add a
test case to induce low memory conditions (low HEAP and high socketpair
buffer size), with the fix issue is not seen.

Signed-off-by: Chaitanya Tata <Chaitanya.Tata@nordicsemi.no>
2023-09-18 20:34:12 +02:00

1197 lines
26 KiB
C

/*
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/net/socket.h>
#include <zephyr/posix/fcntl.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];
};
#ifdef CONFIG_NET_SOCKETPAIR_STATIC
K_MEM_SLAB_DEFINE_STATIC(spair_slab, sizeof(struct spair), CONFIG_NET_SOCKETPAIR_MAX * 2,
__alignof__(struct spair));
#endif /* CONFIG_NET_SOCKETPAIR_STATIC */
/* 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);
if (remote != NULL && have_remote_sem) {
k_sem_give(&remote->sem);
}
/* ensure no private information is released to the memory pool */
memset(spair, 0, sizeof(*spair));
#ifdef CONFIG_NET_SOCKETPAIR_STATIC
k_mem_slab_free(&spair_slab, (void *)spair);
#elif CONFIG_USERSPACE
k_object_free(spair);
#else
k_free(spair);
#endif
}
/**
* 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_NET_SOCKETPAIR_STATIC
res = k_mem_slab_alloc(&spair_slab, (void **) &spair, K_NO_WAIT);
if (res != 0) {
spair = NULL;
}
#elif 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) {
if (k_is_in_isr()) {
errno = EAGAIN;
res = -1;
goto out;
}
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) {
if (k_is_in_isr()) {
errno = EAGAIN;
res = -1;
goto out;
}
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_FIONBIO: {
spair->flags |= SPAIR_FLAG_NONBLOCK;
res = 0;
goto out;
}
case ZFD_IOCTL_FIONREAD: {
int *nbytes;
nbytes = va_arg(args, int *);
*nbytes = spair_read_avail(spair);
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,
};