a1b77fd589
git grep -l 'u\(8\|16\|32\|64\)_t' | \ xargs sed -i "s/u\(8\|16\|32\|64\)_t/uint\1_t/g" git grep -l 's\(8\|16\|32\|64\)_t' | \ xargs sed -i "s/s\(8\|16\|32\|64\)_t/int\1_t/g" Signed-off-by: Kumar Gala <kumar.gala@linaro.org>
1147 lines
24 KiB
C
1147 lines
24 KiB
C
/*
|
|
* Copyright (c) 2020 Friedt Professional Engineering Services, Inc
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <fcntl.h>
|
|
|
|
/* Zephyr headers */
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(net_spair, CONFIG_NET_SOCKETS_LOG_LEVEL);
|
|
|
|
#include <kernel.h>
|
|
#include <net/socket.h>
|
|
#include <syscall_handler.h>
|
|
#include <sys/__assert.h>
|
|
#include <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
|
|
* reources 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 write of local @a recv_q occurred */
|
|
struct k_poll_signal write_signal;
|
|
/** indicates read of local @a recv_q occurred */
|
|
struct k_poll_signal read_signal;
|
|
/** 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.write_signal
|
|
* -# T1 is blocked writing to A and T2 closes B
|
|
* T1 is waits on B's read signal. T2 triggers the local
|
|
* @ref spair.read_signal.
|
|
*
|
|
* 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 memeory 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->write_signal,
|
|
SPAIR_SIG_CANCEL);
|
|
__ASSERT(res == 0,
|
|
"k_poll_signal_raise() failed: %d",
|
|
res);
|
|
}
|
|
}
|
|
}
|
|
|
|
spair->remote = -1;
|
|
|
|
res = k_poll_signal_raise(&spair->read_signal, 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;
|
|
|
|
#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->write_signal);
|
|
k_poll_signal_init(&spair->read_signal);
|
|
|
|
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 (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.read_signal:
|
|
*
|
|
* 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 analagous 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;
|
|
int key;
|
|
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;
|
|
}
|
|
|
|
key = irq_lock();
|
|
is_nonblock = sock_is_nonblock(spair);
|
|
res = k_sem_take(&spair->sem, K_NO_WAIT);
|
|
irq_unlock(key);
|
|
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->read_signal),
|
|
};
|
|
|
|
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->read_signal, &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);
|
|
|
|
res = k_poll_signal_raise(&remote->write_signal, 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.write_signal:
|
|
*
|
|
* -# @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;
|
|
int key;
|
|
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;
|
|
}
|
|
|
|
key = irq_lock();
|
|
is_nonblock = sock_is_nonblock(spair);
|
|
res = k_sem_take(&spair->sem, K_NO_WAIT);
|
|
irq_unlock(key);
|
|
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->write_signal
|
|
),
|
|
};
|
|
|
|
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->write_signal, &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 (is_connected) {
|
|
res = k_poll_signal_raise(&spair->read_signal, 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->write_signal;
|
|
}
|
|
|
|
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 data has been read from the remote end */
|
|
(*pev)->obj = &remote->read_signal;
|
|
}
|
|
|
|
(*pev)->type = K_POLL_TYPE_SIGNAL;
|
|
(*pev)->mode = K_POLL_MODE_NOTIFY_ONLY;
|
|
(*pev)->state = K_POLL_STATE_NOT_READY;
|
|
k_poll_signal_reset((*pev)->obj);
|
|
|
|
(*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->read_signal, &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->write_signal, &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_CLOSE: {
|
|
/* disconnect the remote endpoint */
|
|
spair_delete(spair);
|
|
have_local_sem = false;
|
|
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 const struct socket_op_vtable spair_fd_op_vtable = {
|
|
.fd_vtable = {
|
|
.read = spair_read,
|
|
.write = spair_write,
|
|
.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,
|
|
};
|