From 6ba67402317ca9b73207c9f4bc53cd612f625d50 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 13 Dec 2019 13:01:30 +0100 Subject: [PATCH] posix_select: initial import of `select()` function --- Makefile.dep | 9 ++ sys/Makefile | 3 + sys/posix/doc.txt | 6 ++ sys/posix/include/sys/select.h | 172 ++++++++++++++++++++++++++++++ sys/posix/select/Makefile | 3 + sys/posix/select/posix_select.c | 143 +++++++++++++++++++++++++ sys/posix/sockets/posix_sockets.c | 42 +++++++- 7 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 sys/posix/include/sys/select.h create mode 100644 sys/posix/select/Makefile create mode 100644 sys/posix/select/posix_select.c diff --git a/Makefile.dep b/Makefile.dep index 65abf25c11..bc8bb89299 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -470,6 +470,15 @@ ifneq (,$(filter newlib,$(USEMODULE))) endif endif +ifneq (,$(filter posix_select,$(USEMODULE))) + ifneq (,$(filter posix_sockets,$(USEMODULE))) + USEMODULE += sock_async + endif + USEMODULE += core_thread_flags + USEMODULE += posix_headers + USEMODULE += xtimer +endif + ifneq (,$(filter posix_sockets,$(USEMODULE))) USEMODULE += bitfield USEMODULE += random diff --git a/sys/Makefile b/sys/Makefile index 19f289758e..f09cef8023 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -10,6 +10,9 @@ endif ifneq (,$(filter posix_inet,$(USEMODULE))) DIRS += posix/inet endif +ifneq (,$(filter posix_select,$(USEMODULE))) + DIRS += posix/select +endif ifneq (,$(filter posix_semaphore,$(USEMODULE))) DIRS += posix/semaphore endif diff --git a/sys/posix/doc.txt b/sys/posix/doc.txt index d6ce3a0701..a7e47bc531 100644 --- a/sys/posix/doc.txt +++ b/sys/posix/doc.txt @@ -14,3 +14,9 @@ * * @ingroup sys */ + +/** + * @defgroup config_posix POSIX wrapper compile configurations + * @ingroup posix + * @ingroup config + */ diff --git a/sys/posix/include/sys/select.h b/sys/posix/include/sys/select.h new file mode 100644 index 0000000000..6899e262cf --- /dev/null +++ b/sys/posix/include/sys/select.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup posix_select POSIX select + * @ingroup posix + * @brief Select implementation for RIOT + * @see [The Open Group Base Specification Issue 7] + * (https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/) + * @todo Omitted from original specification for now: + * - Inclusion of ``; no POSIX signal handling implemented + * in RIOT yet + * - `pselect()` as it uses `sigset_t` from `` + * - handling of the `writefds` and `errorfds` parameters of `select()` + * @todo Currently, only [sockets](@ref posix_sockets) are supported + * @{ + * + * @file + * @brief Select types + * @see [The Open Group Base Specification Issue 7, 2018 edition, + * ](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/basedefs/sys_select.h) + */ + +#ifndef SYS_SELECT_H +#define SYS_SELECT_H + +#include +/* prevent cyclic dependency with newlib's `sys/types.h` */ +#if defined(MODULE_NEWLIB) && !defined(CPU_ESP32) && !defined(CPU_ESP8266) +#include +#else +#include +#endif + +#include "bitfield.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup config_posix + * @{ + */ +/** + * @brief Maximum number of file descriptors in an `fd_set` structure. + * + * @note Should have at least the same value as VFS_MAX_OPEN_FILES. + * + * Config-exposed version + */ +#ifndef CONFIG_POSIX_FD_SET_SIZE +#define CONFIG_POSIX_FD_SET_SIZE (16) +#endif +/** @} */ + +/** + * @brief @ref core_thread_flags for POSIX select + */ +#define POSIX_SELECT_THREAD_FLAG (1U << 3) + +/* ESP's newlib has this already defined in `sys/types.h` */ +#if !defined(CPU_ESP32) && !defined(CPU_ESP8266) +/** + * @brief Maximum number of file descriptors in an `fd_set` structure. + * + * POSIX-compliant version. + */ +#define FD_SETSIZE (CONFIG_POSIX_FD_SET_SIZE) + +/** + * @brief The `fd_set` structure + */ +typedef struct { + BITFIELD(fds, FD_SETSIZE); /**< Bit-field to represent the set of file + * descriptors */ +} fd_set; + +/** + * @brief Removes a file descriptor from an `fd_set` if it is a member + * + * @param[in] fd A file descriptor + * @param[in] fdsetp An `fd_set` + */ +static inline void FD_CLR(int fd, fd_set *fdsetp) +{ + bf_unset(fdsetp->fds, fd); +} + +/** + * @brief Checks if a file descriptor is a member of an `fd_set` + * + * @param[in] fd A file descriptor + * @param[in] fdsetp An `fd_set` + * + * @return non-zero value, if @p fd is a member of @p fdsetp + * @return 0, if @p fd is not a member of @p fdsetp + */ +static inline int FD_ISSET(int fd, fd_set *fdsetp) +{ + return (int)bf_isset(fdsetp->fds, fd); +} + +/** + * @brief Adds a file descriptor from an `fd_set` if it is not already a + * member + * + * @param[in] fd A file descriptor + * @param[in] fdsetp An `fd_set` + */ +static inline void FD_SET(int fd, fd_set *fdsetp) +{ + bf_set(fdsetp->fds, fd); +} + +/** + * @brief Initializes the descriptor set as an empty set + * + * @param[in] fdsetp An `fd_set` + */ +static inline void FD_ZERO(fd_set *fdsetp) +{ + memset(fdsetp->fds, 0, sizeof(fdsetp->fds)); +} +#endif /* !defined(CPU_ESP32) && !defined(CPU_ESP8266) */ + +/** + * @brief Examines the given file descriptor sets if they are ready for their + * respective operation. + * + * @param[in] nfds The range of descriptors tested. The first @p nfds + * descriptors shall be checked in each set; that is, + * the descriptors from zero through @p nfds - 1 in the + * descriptor sets shall be examined. + * @param[in,out] readfds The set of file descriptors to be checked for being + * ready to read. Indicates on output which file + * descriptors are ready to read. May be NULL to check + * no file descriptors. + * @param[in,out] writefds The set of file descriptors to be checked for being + * ready to write. Indicates on output which file + * descriptors are ready to write. May be NULL to check + * no file descriptors. + * **As only sockets are supported for now, these will + * be ignored** + * @param[in,out] errorfds The set of file descriptors to be checked for being + * error conditions pending. Indicates on output which + * file descriptors have error conditions pending. May + * be NULL to check no file descriptors. + * **As only sockets are supported for now, these will + * be ignored** + * @param[in] timeout Timeout for select to block until one or more of the + * checked file descriptors is ready. Set timeout + * to all-zero to return immediately without blocking. + * May be NULL to block indefinitely. + * + * @return number of members added to the file descriptor sets on success. + * @return -1 on error, `errno` is set to indicate the error. + */ +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, + struct timeval *timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* SYS_SELECT_H */ +/** @} */ diff --git a/sys/posix/select/Makefile b/sys/posix/select/Makefile new file mode 100644 index 0000000000..f693b59b05 --- /dev/null +++ b/sys/posix/select/Makefile @@ -0,0 +1,3 @@ +MODULE = posix_select + +include $(RIOTBASE)/Makefile.base diff --git a/sys/posix/select/posix_select.c b/sys/posix/select/posix_select.c new file mode 100644 index 0000000000..b8d56fa8dc --- /dev/null +++ b/sys/posix/select/posix_select.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * @file + * @author Martine S. Lenders + */ + +#include +#include +#include + +#include "thread_flags.h" +#include "vfs.h" +#include "xtimer.h" + +#if IS_USED(MODULE_POSIX_SOCKETS) +extern bool posix_socket_is(int fd); +extern unsigned posix_socket_avail(int fd); +extern void posix_socket_select(int fd); +#else /* MODULE_POSIX_SOCKETS */ +static inline bool posix_socket_is(int fd) +{ + (void)fd; + return false; +} + +static inline unsigned posix_socket_avail(int fd) +{ + (void)fd; + return 0; +} + +static inline void posix_socket_select(int fd) +{ + (void)fd; + return 0; +} +#endif /* IS_USED(MODULE_POSIX_SOCKETS) */ + +static int _set_timeout(xtimer_t *timeout_timer, struct timeval *timeout, + uint32_t offset, bool *wait) +{ + if (timeout != NULL) { + uint64_t t = ((uint64_t)(timeout->tv_sec * US_PER_SEC) + + timeout->tv_usec); + /* check for potential underflow before subtracting offset */ + if ((t == 0) || (offset > t)) { + *wait = false; + return 0; + } + t -= offset; + if (t > UINT32_MAX) { + errno = EINVAL; + /* don't have timer set yet so go to end */ + return -1; + } + else { + xtimer_set_timeout_flag(timeout_timer, (uint32_t)t); + } + } + return 0; +} + +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, + struct timeval *timeout) +{ + uint32_t start_time = xtimer_now_usec(); + fd_set ret_readfds; + xtimer_t timeout_timer; + int fds_set = 0; + bool wait = true; + + FD_ZERO(&ret_readfds); + /* TODO ignored writefds and errorfds for now since there is no point for + * them with sockets */ + if ((nfds >= FD_SETSIZE) || ((unsigned)nfds >= VFS_MAX_OPEN_FILES)) { + errno = EINVAL; + return -1; + } + for (int i = 0; i < nfds; i++) { + if ((readfds != NULL) && FD_ISSET(i, readfds)) { + if (!posix_socket_is(i)) { + errno = EBADF; + return -1; + } + if (posix_socket_avail(i) > 0) { + FD_SET(i, &ret_readfds); + fds_set++; + wait = false; + } + else { + posix_socket_select(i); + } + } + if ((writefds != NULL) && FD_ISSET(i, writefds) && + !posix_socket_is(i)) { + errno = EBADF; + return -1; + } + if ((errorfds != NULL) && FD_ISSET(i, errorfds) && + !posix_socket_is(i)) { + errno = EBADF; + return -1; + } + } + while (wait) { + if (_set_timeout(&timeout_timer, timeout, + xtimer_now_usec() - start_time, &wait) < 0) { + return -1; + } + if (!wait) { + errno = EINTR; + return -1; + } + thread_flags_t tflags = thread_flags_wait_any(POSIX_SELECT_THREAD_FLAG | + THREAD_FLAG_TIMEOUT); + if (tflags & POSIX_SELECT_THREAD_FLAG) { + for (int i = 0; i < nfds; i++) { + if (FD_ISSET(i, readfds)) { + if (posix_socket_avail(i) > 0) { + FD_SET(i, &ret_readfds); + fds_set++; + wait = false; + } + } + } + } + else if (tflags & THREAD_FLAG_TIMEOUT) { + errno = EINTR; + return -1; + } + xtimer_remove(&timeout_timer); + } + *readfds = ret_readfds; + return fds_set; +} diff --git a/sys/posix/sockets/posix_sockets.c b/sys/posix/sockets/posix_sockets.c index 5815d93707..27dffc1a98 100644 --- a/sys/posix/sockets/posix_sockets.c +++ b/sys/posix/sockets/posix_sockets.c @@ -41,6 +41,12 @@ #if IS_USED(MODULE_SOCK_ASYNC) #include "net/sock/async.h" #endif +#if IS_USED(MODULE_POSIX_SELECT) +#include + +#include "thread.h" +#include "thread_flags.h" +#endif /* enough to create sockets both with socket() and accept() */ #define _ACTUAL_SOCKET_POOL_SIZE (SOCKET_POOL_SIZE + \ @@ -86,6 +92,9 @@ typedef struct { #endif #if IS_USED(MODULE_SOCK_ASYNC) atomic_uint available; +#endif +#if IS_USED(MODULE_POSIX_SELECT) + thread_t *selecting_thread; #endif sock_tcp_ep_t local; /* to store bind before connect/listen */ } socket_t; @@ -115,6 +124,9 @@ static socket_t *_get_free_socket(void) if (_socket_pool[i].domain == AF_UNSPEC) { #if IS_USED(MODULE_SOCK_ASYNC) atomic_init(&_socket_pool[i].available, 0U); +#endif +#if IS_USED(MODULE_POSIX_SELECT) + _socket_pool[i].selecting_thread = NULL; #endif return &_socket_pool[i]; } @@ -359,8 +371,10 @@ static void _async_cb(void *sock, sock_async_flags_t type, if (type & SOCK_ASYNC_MSG_RECV) { atomic_fetch_add(&socket->available, 1); #if IS_USED(MODULE_POSIX_SELECT) - thread_flags_set(sock->socket->selecting_thread, - POSIX_SELECT_THREAD_FLAG); + if (socket->selecting_thread) { + thread_flags_set(socket->selecting_thread, + POSIX_SELECT_THREAD_FLAG); + } #endif } } @@ -1169,6 +1183,30 @@ unsigned posix_socket_avail(int fd) #endif } +int posix_socket_select(int fd) +{ +#if IS_USED(MODULE_POSIX_SELECT) + socket_t *socket = _get_socket(fd); + + if (socket != NULL) { + if (socket->sock == NULL) { /* socket is not connected */ + int res; + + /* bind implicitly */ + if ((res = _bind_connect(socket, NULL, 0)) < 0) { + return res; + } + } + socket->selecting_thread = (thread_t *)sched_active_thread; + return 0; + } +#else + (void)fd; +#endif + errno = ENOTSUP; + return -1; +} + /** * @} */