feat(select): poll/select and fd_set
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
#ifndef VLIBC_POLL_H
|
||||||
|
#define VLIBC_POLL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <poll.h>.
|
||||||
|
*
|
||||||
|
* Poll multiple file descriptors for readiness. Every declaration here is a
|
||||||
|
* thin pass-through to the kernel: poll() blocks per its millisecond
|
||||||
|
* timeout, ppoll() takes a struct timespec and an optional signal mask
|
||||||
|
* directly, and both return the count of ready descriptors (0 on timeout,
|
||||||
|
* -1 with errno set on error).
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): poll, nfds_t, struct pollfd, the POLL* event bits.
|
||||||
|
* Level 2 (muslmimic): ppoll (Linux-specific; POSIX has only poll).
|
||||||
|
*
|
||||||
|
* struct pollfd and the POLL* values mirror the kernel ABI (x86_64
|
||||||
|
* asm-generic/poll.h) exactly: the wrappers pass them to SYS_ppoll
|
||||||
|
* unmodified. POLLERR, POLLHUP and POLLNVAL are report-only — they can
|
||||||
|
* appear in revents even when not requested in events.
|
||||||
|
*
|
||||||
|
* The signal mask type ppoll takes is declared under the shared guard
|
||||||
|
* below; <signal.h> is its canonical POSIX home and will own the full sig*
|
||||||
|
* API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Requestable event bits (poll()'s events field). */
|
||||||
|
#define POLLIN 0x001 /* readable */
|
||||||
|
#define POLLPRI 0x002 /* urgent readable (out-of-band) */
|
||||||
|
#define POLLOUT 0x004 /* writable */
|
||||||
|
|
||||||
|
/* Report-only event bits (poll()'s revents field). */
|
||||||
|
#define POLLERR 0x008 /* error condition */
|
||||||
|
#define POLLHUP 0x010 /* hung up */
|
||||||
|
#define POLLNVAL 0x020 /* invalid fd */
|
||||||
|
|
||||||
|
/* The number of struct pollfd entries poll()/ppoll() watch. */
|
||||||
|
typedef unsigned long nfds_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One descriptor watched by poll()/ppoll(). fd is the descriptor (negative
|
||||||
|
* to ignore), events the requested bits (POLLIN/POLLOUT), revents the bits
|
||||||
|
* the kernel reports (the requested bits plus any POLLERR/POLLHUP/POLLNVAL).
|
||||||
|
*/
|
||||||
|
struct pollfd
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
short events;
|
||||||
|
short revents;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for readiness on the first nfds entries of fds, blocking up to
|
||||||
|
* timeout milliseconds (timeout < 0 blocks indefinitely, 0 never blocks).
|
||||||
|
* Return the number of entries with a nonzero revents, 0 on timeout, or -1
|
||||||
|
* with errno set on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): Linux-specific. */
|
||||||
|
|
||||||
|
#ifndef VLIBC_SIGSET_T_DEFINED
|
||||||
|
#define VLIBC_SIGSET_T_DEFINED
|
||||||
|
/*
|
||||||
|
* Signal mask type: a single 64-bit word — the x86_64 Linux sigset_t (see
|
||||||
|
* setjmp.h). <signal.h> is the canonical POSIX home for sigset_t and builds
|
||||||
|
* the sig* API on this same layout; the typedef is repeated in
|
||||||
|
* <sys/select.h> under this guard so the two headers stay consistent.
|
||||||
|
*/
|
||||||
|
typedef unsigned long sigset_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like poll(), but the timeout is a struct timespec and, when sigmask is
|
||||||
|
* not NULL, the given signal mask is atomically installed for the duration
|
||||||
|
* of the wait (the previous mask is restored before returning).
|
||||||
|
* Linux-specific.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t *sigmask);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_POLL_H */
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#ifndef VLIBC_SYS_SELECT_H
|
||||||
|
#define VLIBC_SYS_SELECT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <sys/select.h>.
|
||||||
|
*
|
||||||
|
* Synchronous I/O multiplexing: wait for readiness on file-descriptor sets
|
||||||
|
* with select() or pselect(). Both are thin wrappers over the SYS_pselect6
|
||||||
|
* kernel ABI, (nfds, readfds, writefds, exceptfds, struct timespec *,
|
||||||
|
* { sigset_t *, size_t } *): pselect passes its timespec and optional signal
|
||||||
|
* mask straight through, select converts its struct timeval timeout to a
|
||||||
|
* timespec and passes a NULL sigset pair.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): select, pselect, FD_* macros, fd_set, struct
|
||||||
|
* timeval.
|
||||||
|
*
|
||||||
|
* fd_set is the kernel's bitmap layout on x86_64: an array of unsigned long
|
||||||
|
* words, with descriptor d held in bit d % 64 of word d / 64. FD_SETSIZE
|
||||||
|
* caps the tracked descriptors at 1024 (16 words), so select()/pselect()
|
||||||
|
* must be called with nfds <= FD_SETSIZE and FD_SET() only ever receives
|
||||||
|
* descriptors below FD_SETSIZE. select() and pselect() report the number of
|
||||||
|
* ready descriptors, 0 on timeout, or -1 with errno set on error.
|
||||||
|
*
|
||||||
|
* The signal mask type is declared here under the shared guard; <signal.h>
|
||||||
|
* is its canonical POSIX home and will own the full sig* API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum descriptors select()/pselect() track (fd_set's bit capacity). */
|
||||||
|
#define FD_SETSIZE 1024
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A set of file descriptors, one bit per descriptor, stored in the kernel
|
||||||
|
* bitmap layout: an array of unsigned long words, bit d in word
|
||||||
|
* d / (8 * sizeof(unsigned long)). Descriptors 0..FD_SETSIZE-1 fit; no
|
||||||
|
* operation may touch a descriptor at or above FD_SETSIZE.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned long fds_bits[FD_SETSIZE / (8 * sizeof(unsigned long))];
|
||||||
|
} fd_set;
|
||||||
|
|
||||||
|
/* The word holding bit d of an fd_set, and that bit's mask within the word. */
|
||||||
|
#define VLIBC_FDS_WORD(d) ((d) / (8 * sizeof(unsigned long)))
|
||||||
|
#define VLIBC_FDS_MASK(d) (1UL << ((d) % (8 * sizeof(unsigned long))))
|
||||||
|
|
||||||
|
/* Clear every descriptor bit of set. */
|
||||||
|
#define FD_ZERO(set) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
size_t fd_zero_i; \
|
||||||
|
for (fd_zero_i = 0; fd_zero_i < sizeof(fd_set) / sizeof(unsigned long); fd_zero_i++) \
|
||||||
|
{ \
|
||||||
|
(set)->fds_bits[fd_zero_i] = 0UL; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Add descriptor d to set. */
|
||||||
|
#define FD_SET(d, set) ((set)->fds_bits[VLIBC_FDS_WORD(d)] |= VLIBC_FDS_MASK(d))
|
||||||
|
|
||||||
|
/* Remove descriptor d from set. */
|
||||||
|
#define FD_CLR(d, set) ((set)->fds_bits[VLIBC_FDS_WORD(d)] &= ~VLIBC_FDS_MASK(d))
|
||||||
|
|
||||||
|
/* Nonzero when descriptor d is a member of set. */
|
||||||
|
#define FD_ISSET(d, set) ((set)->fds_bits[VLIBC_FDS_WORD(d)] & VLIBC_FDS_MASK(d))
|
||||||
|
|
||||||
|
#ifndef VLIBC_SIGSET_T_DEFINED
|
||||||
|
#define VLIBC_SIGSET_T_DEFINED
|
||||||
|
/*
|
||||||
|
* Signal mask type: a single 64-bit word — the x86_64 Linux sigset_t (see
|
||||||
|
* setjmp.h). pselect() and ppoll() only ever forward a pointer to the
|
||||||
|
* kernel, which reads the word directly. <signal.h> is the canonical POSIX
|
||||||
|
* home for sigset_t and builds the sig* API on this same layout; the
|
||||||
|
* typedef is repeated in <poll.h> under this guard so the two headers stay
|
||||||
|
* consistent.
|
||||||
|
*/
|
||||||
|
typedef unsigned long sigset_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Elapsed time in seconds and microseconds (see select()'s timeout). */
|
||||||
|
struct timeval
|
||||||
|
{
|
||||||
|
time_t tv_sec;
|
||||||
|
suseconds_t tv_usec;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for readiness on the descriptors marked in readfds, writefds and
|
||||||
|
* exceptfds (each may be NULL), up to nfds descriptors (the highest
|
||||||
|
* descriptor in any set plus one). timeout is an upper bound on the wait; a
|
||||||
|
* NULL timeout blocks indefinitely, { 0, 0 } never blocks. On return each
|
||||||
|
* non-NULL set holds only its ready descriptors. Return the number of ready
|
||||||
|
* descriptors across the sets, 0 on timeout, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like select(), but the timeout is a struct timespec and, when sigmask is
|
||||||
|
* not NULL, the given signal mask is atomically installed for the duration
|
||||||
|
* of the wait (the previous mask is restored before returning).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||||
|
const struct timespec *timeout, const sigset_t *sigmask);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_SYS_SELECT_H */
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* poll: plain POSIX poll over SYS_ppoll with a NULL sigmask (Linux has no
|
||||||
|
* separate poll syscall for >1024 fds; ppoll is the generic form). The
|
||||||
|
* millisecond timeout is converted to the struct timespec the kernel
|
||||||
|
* expects; a negative timeout means "block indefinitely" and stays NULL.
|
||||||
|
*
|
||||||
|
* The (nfds_t, int) parameter pair is the fixed POSIX signature, so the
|
||||||
|
* easily-swappable-parameters warning does not apply.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
poll(struct pollfd *fds, nfds_t nfds, // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
int timeout)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
struct timespec *tsp = NULL;
|
||||||
|
|
||||||
|
if (timeout >= 0)
|
||||||
|
{
|
||||||
|
ts.tv_sec = timeout / 1000;
|
||||||
|
ts.tv_nsec = (long)(timeout % 1000) * 1000000L;
|
||||||
|
tsp = &ts;
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall5(SYS_ppoll, (long)fds, (long)nfds, (long)tsp, 0L, 0L));
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ppoll: Linux-specific poll over SYS_ppoll with a struct timespec timeout
|
||||||
|
* and an optional signal mask (the kernel takes the mask and its size as
|
||||||
|
* separate trailing arguments; the size is only announced — as the 8-byte
|
||||||
|
* kernel sigset size — when a mask is actually given).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t *sigmask)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall5(SYS_ppoll, (long)fds, (long)nfds, (long)timeout, (long)sigmask,
|
||||||
|
sigmask != NULL ? (long)sizeof(sigset_t) : 0L));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pselect: plain POSIX pselect over SYS_pselect6. Its 6th kernel argument is
|
||||||
|
* a pointer to a { sigset_t *, size_t } pair (the kernel reads two words);
|
||||||
|
* NULL means "leave the signal mask alone", so the pair is only supplied —
|
||||||
|
* and only the 8-byte kernel sigset size announced — when sigmask is given.
|
||||||
|
* The timeout passes through as a struct timespec (NULL = indefinite).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||||
|
const struct timespec *timeout, const sigset_t *sigmask)
|
||||||
|
{
|
||||||
|
long data[2];
|
||||||
|
|
||||||
|
data[0] = (long)sigmask;
|
||||||
|
data[1] = (long)sizeof(sigset_t);
|
||||||
|
return syscall_ret(__syscall6(SYS_pselect6, (long)nfds, (long)readfds, (long)writefds,
|
||||||
|
(long)exceptfds, (long)timeout,
|
||||||
|
sigmask != NULL ? (long)data : 0L));
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* select: plain POSIX select over SYS_pselect6. The kernel wants a struct
|
||||||
|
* timespec, so the struct timeval timeout is converted here (NULL passes
|
||||||
|
* through as "block indefinitely") and the sigset pair stays NULL: select
|
||||||
|
* never changes the signal mask. select() and pselect() share this syscall
|
||||||
|
* on Linux; the 6th argument of pselect6 is a { sigset_t *, size_t } pair
|
||||||
|
* pointer that is NULL when the mask is left alone.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
struct timespec *tsp = NULL;
|
||||||
|
|
||||||
|
if (timeout != NULL)
|
||||||
|
{
|
||||||
|
if (timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_usec >= 1000000L)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ts.tv_sec = timeout->tv_sec;
|
||||||
|
ts.tv_nsec = (long)timeout->tv_usec * 1000;
|
||||||
|
tsp = &ts;
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall6(SYS_pselect6, (long)nfds, (long)readfds, (long)writefds,
|
||||||
|
(long)exceptfds, (long)tsp, 0L));
|
||||||
|
}
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — select/pselect/poll/ppoll test (todo 25).
|
||||||
|
*
|
||||||
|
* Exercises the fd_set macros and the multiplexing wrappers end to end:
|
||||||
|
*
|
||||||
|
* 1. FD_ZERO/FD_SET/FD_CLR/FD_ISSET round-trip, including bit 63/64 (the
|
||||||
|
* word boundary) and descriptor FD_SETSIZE-1, and fd_set has exactly
|
||||||
|
* FD_SETSIZE/8 bytes (the kernel bitmap size).
|
||||||
|
* 2. A pipe holding one byte: poll() returns 1 with POLLIN set,
|
||||||
|
* select()/pselect() return > 0 with the pipe bit set.
|
||||||
|
* 3. After the byte is read the pipe is empty: select()/pselect() with a
|
||||||
|
* 0 timeout return 0 and clear the set, poll() with timeout 0 returns
|
||||||
|
* 0, and no wait ever hangs.
|
||||||
|
* 4. Failure scenarios (only the return value, never errno): poll with an
|
||||||
|
* invalid fds pointer, poll(NULL, 1, 0), poll with an absurd nfds, and
|
||||||
|
* poll with a big nfds plus a bad pointer all return -1.
|
||||||
|
*
|
||||||
|
* Level-2 gated section: ppoll happy paths (data present / 0 timeout).
|
||||||
|
*
|
||||||
|
* The negative paths make the LIBRARY write errno (syscall_ret), which
|
||||||
|
* under a host-linked binary targets glibc's private dtv slot at %fs:0+8.
|
||||||
|
* In the default mode each such call is bracketed with a save/restore of
|
||||||
|
* that slot (task 13 technique) — only vlibc/raw-syscall code runs between
|
||||||
|
* the write and the restore. The test itself NEVER reads errno. The -f mode
|
||||||
|
* runs the failure scenarios and exits via raw SYS_exit_group (house
|
||||||
|
* pattern, tests/test_unistd_file.c).
|
||||||
|
*
|
||||||
|
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude the
|
||||||
|
* vlibc public headers shadow GCC's internal ones, so a host header would
|
||||||
|
* not compile.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "../include/poll.h"
|
||||||
|
#include "../include/sys/select.h"
|
||||||
|
#include "../include/unistd.h"
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Write a NUL-terminated string to fd via the raw syscall layer. The
|
||||||
|
* optimize attribute keeps GCC from lowering the length loop into a
|
||||||
|
* strlen call, which would leave a vlibc-owned symbol undefined in this
|
||||||
|
* host-linked standalone binary (house idiom, see src/string). */
|
||||||
|
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
|
||||||
|
say(int fd, const char *s)
|
||||||
|
{
|
||||||
|
long n = 0;
|
||||||
|
|
||||||
|
while (s[n] != '\0')
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
__syscall3(SYS_write, fd, (long)s, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write v in decimal to fd. */
|
||||||
|
static void
|
||||||
|
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
char buf[24];
|
||||||
|
int i = (int)sizeof(buf);
|
||||||
|
|
||||||
|
buf[--i] = '\0';
|
||||||
|
do
|
||||||
|
{
|
||||||
|
buf[--i] = (char)('0' + (v % 10));
|
||||||
|
v /= 10;
|
||||||
|
} while (v != 0);
|
||||||
|
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
check(int cond, const char *what)
|
||||||
|
{
|
||||||
|
if (cond)
|
||||||
|
{
|
||||||
|
say(1, "PASS: ");
|
||||||
|
say(1, what);
|
||||||
|
say(1, "\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(2, "FAIL: ");
|
||||||
|
say(2, what);
|
||||||
|
say(2, "\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Host-TCB slot-1 bracket: the library's errno write on a negative path
|
||||||
|
* lands at %fs:0+8, glibc's dtv pointer. Save and restore it around each
|
||||||
|
* such call; only vlibc/raw-syscall code runs in between (task 13
|
||||||
|
* technique).
|
||||||
|
*/
|
||||||
|
static unsigned long
|
||||||
|
tcb_slot1(void)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tcb_slot1_set(unsigned long value)
|
||||||
|
{
|
||||||
|
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A deliberately invalid fds pointer: address 1 is never mapped. */
|
||||||
|
static struct pollfd *
|
||||||
|
bad_fds(void)
|
||||||
|
{
|
||||||
|
return (struct pollfd *)(unsigned long)1; // NOLINT(performance-no-int-to-ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. fd_set macro round-trip. */
|
||||||
|
static void
|
||||||
|
fdset_scenarios(void)
|
||||||
|
{
|
||||||
|
fd_set s;
|
||||||
|
|
||||||
|
FD_ZERO(&s);
|
||||||
|
check(FD_ISSET(0, &s) == 0 && FD_ISSET(63, &s) == 0 && FD_ISSET(1023, &s) == 0,
|
||||||
|
"FD_ZERO leaves every bit clear");
|
||||||
|
FD_SET(7, &s);
|
||||||
|
FD_SET(1023, &s);
|
||||||
|
check(FD_ISSET(7, &s) != 0, "FD_ISSET sees FD_SET(7)");
|
||||||
|
check(FD_ISSET(1023, &s) != 0, "FD_ISSET sees FD_SET(1023) (last bit)");
|
||||||
|
check(FD_ISSET(8, &s) == 0 && FD_ISSET(1022, &s) == 0, "neighboring bits stay clear");
|
||||||
|
FD_CLR(7, &s);
|
||||||
|
check(FD_ISSET(7, &s) == 0 && FD_ISSET(1023, &s) != 0, "FD_CLR(7) clears only bit 7");
|
||||||
|
|
||||||
|
FD_ZERO(&s);
|
||||||
|
FD_SET(63, &s);
|
||||||
|
FD_SET(64, &s);
|
||||||
|
check(FD_ISSET(63, &s) != 0 && FD_ISSET(64, &s) != 0,
|
||||||
|
"FD_SET/FD_ISSET across the 64-bit word boundary");
|
||||||
|
check(FD_ISSET(62, &s) == 0 && FD_ISSET(65, &s) == 0, "word-boundary neighbors stay clear");
|
||||||
|
check(sizeof(fd_set) == (size_t)(FD_SETSIZE / 8), "fd_set is FD_SETSIZE/8 bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2+3. poll/select/pselect on a pipe, with and without data. */
|
||||||
|
static void
|
||||||
|
poll_select_scenarios(void)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
struct timeval tv;
|
||||||
|
struct timespec ts;
|
||||||
|
fd_set r;
|
||||||
|
int pr;
|
||||||
|
char c;
|
||||||
|
int fds[2];
|
||||||
|
|
||||||
|
check(pipe(fds) == 0, "pipe returns 0");
|
||||||
|
check(write(fds[1], "x", 1) == 1, "write of one byte to the pipe returns 1");
|
||||||
|
pr = fds[0];
|
||||||
|
|
||||||
|
pfd.fd = pr;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
check(poll(&pfd, 1, 1000) == 1, "poll on a pipe with data returns 1");
|
||||||
|
check((pfd.revents & POLLIN) != 0, "revents reports POLLIN");
|
||||||
|
|
||||||
|
FD_ZERO(&r);
|
||||||
|
FD_SET(pr, &r);
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
check(select(pr + 1, &r, NULL, NULL, &tv) > 0, "select finds the pipe readable");
|
||||||
|
check(FD_ISSET(pr, &r) != 0, "select leaves the pipe fd set in readfds");
|
||||||
|
|
||||||
|
FD_ZERO(&r);
|
||||||
|
FD_SET(pr, &r);
|
||||||
|
ts.tv_sec = 0;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(pselect(pr + 1, &r, NULL, NULL, &ts, NULL) > 0,
|
||||||
|
"pselect with a NULL sigset finds the pipe readable");
|
||||||
|
check(FD_ISSET(pr, &r) != 0, "pselect leaves the pipe fd set in readfds");
|
||||||
|
|
||||||
|
check(read(pr, &c, 1) == 1 && c == 'x', "the piped byte reads back");
|
||||||
|
|
||||||
|
FD_ZERO(&r);
|
||||||
|
FD_SET(pr, &r);
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
check(select(pr + 1, &r, NULL, NULL, &tv) == 0,
|
||||||
|
"select on the now-empty pipe times out with 0");
|
||||||
|
check(FD_ISSET(pr, &r) == 0, "empty-pipe select clears the readfds bit");
|
||||||
|
|
||||||
|
pfd.fd = pr;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
check(poll(&pfd, 1, 0) == 0, "poll with timeout 0 on the empty pipe returns 0");
|
||||||
|
check((pfd.revents & POLLIN) == 0, "no POLLIN on the empty pipe");
|
||||||
|
|
||||||
|
ts.tv_sec = 0;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(pselect(pr + 1, NULL, NULL, NULL, &ts, NULL) == 0,
|
||||||
|
"pselect with a 0 timeout and all-NULL sets returns 0");
|
||||||
|
check(close(fds[0]) == 0 && close(fds[1]) == 0, "close of both pipe ends returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Level-2 gate proof: ppoll happy paths. */
|
||||||
|
static void
|
||||||
|
ppoll_scenarios(void)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
struct timespec ts;
|
||||||
|
char c;
|
||||||
|
int fds[2];
|
||||||
|
|
||||||
|
check(pipe(fds) == 0, "pipe returns 0 (ppoll)");
|
||||||
|
check(write(fds[1], "y", 1) == 1, "write of one byte returns 1 (ppoll)");
|
||||||
|
pfd.fd = fds[0];
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
ts.tv_sec = 1;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(ppoll(&pfd, 1, &ts, NULL) == 1, "ppoll on a pipe with data returns 1");
|
||||||
|
check((pfd.revents & POLLIN) != 0, "ppoll revents reports POLLIN");
|
||||||
|
ts.tv_sec = 0;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(ppoll(&pfd, 1, &ts, NULL) == 1, "ppoll with a 0 timeout on unread data still returns 1");
|
||||||
|
check(read(fds[0], &c, 1) == 1 && c == 'y', "the ppoll byte reads back");
|
||||||
|
check(ppoll(&pfd, 1, &ts, NULL) == 0, "ppoll with a 0 timeout on the empty pipe returns 0");
|
||||||
|
check(close(fds[0]) == 0 && close(fds[1]) == 0, "close of both pipe ends returns 0 (ppoll)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* 4. Negative paths: -1 assertions only, errno bracketed (see above). */
|
||||||
|
static void
|
||||||
|
negative_scenarios(void)
|
||||||
|
{
|
||||||
|
unsigned long saved;
|
||||||
|
struct pollfd one;
|
||||||
|
|
||||||
|
one.fd = 0;
|
||||||
|
one.events = POLLIN;
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(bad_fds(), 1, 0) == -1, "poll with an invalid fds pointer returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(NULL, 1, 0) == -1, "poll(NULL, 1, 0) returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(&one, (nfds_t)1000000000, 0) == -1, "poll with an absurd nfds returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(bad_fds(), (nfds_t)1000000000, 0) == -1,
|
||||||
|
"poll with a big nfds and a bad pointer returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Failure scenarios (-f): every assertion is on the return value only, and
|
||||||
|
* the process exits through raw SYS_exit_group because the library writes
|
||||||
|
* errno on these paths (host-TCB hazard).
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
struct pollfd one;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
one.fd = 0;
|
||||||
|
one.events = POLLIN;
|
||||||
|
if (poll(bad_fds(), 1, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: poll with an invalid fds pointer did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: poll with an invalid fds pointer -> -1\n");
|
||||||
|
}
|
||||||
|
if (poll(NULL, 1, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: poll(NULL, 1, 0) did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: poll(NULL, 1, 0) -> -1\n");
|
||||||
|
}
|
||||||
|
if (poll(&one, (nfds_t)1000000000, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: poll with an absurd nfds did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: poll with an absurd nfds -> -1\n");
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The failure scenarios write errno inside the library; under the
|
||||||
|
* host libc that slot is glibc's private TLS state, so leave via
|
||||||
|
* the raw syscall without running host cleanup.
|
||||||
|
*/
|
||||||
|
rc = failure_scenarios();
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
fdset_scenarios();
|
||||||
|
poll_select_scenarios();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
ppoll_scenarios();
|
||||||
|
#endif
|
||||||
|
negative_scenarios();
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all poll/select tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user