You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1133 lines
24 KiB
1133 lines
24 KiB
/* |
|
* 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 */ |
|
u32_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 */ |
|
u8_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(u32_t *a, u32_t *b) |
|
{ |
|
u32_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)); |
|
|
|
k_free(spair); |
|
|
|
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; |
|
|
|
spair = k_malloc(sizeof(*spair)); |
|
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, |
|
};
|
|
|