/* * 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 #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; }