/* * vlibc — readv/writev scatter/gather test (todo 30). * * Exercises src/uio/readv.c and src/uio/writev.c end to end over pipe * pairs created with the raw SYS_pipe syscall (deliberately not the * pipe() wrapper of todo 19 — the uio functions are what is under test): * * Default-mode scenarios (in run order): * * 1. writev scatter: writev of the three buffers {"ab","cd","ef"} into a * pipe returns 6, and a raw read back returns the contiguous 6 bytes * "abcdef" (byte compare). * 2. readv gather: "abcdef" written to a pipe; readv over three len-2 * buffers fills each in order and returns 6; each buffer holds its * expected slice. * 3. empty iov: writev(fd, NULL, 0) — iovcnt 0 — returns 0 (the kernel * never dereferences the iov pointer when iovcnt is 0). * 4. readv returns total: writev writes the three buffers (6 bytes); a * single-buffer readv gathers the whole stream back and returns the * total byte count. * 5. writev to a closed fd returns -1 (assert -1 only). * * The negative path in scenario 5 makes 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 that call is bracketed with a * save/restore of the slot (task 13 technique) — only vlibc/raw-syscall * code runs between the write and the restore. The test itself NEVER reads * errno; every negative is asserted on the return value. The -f mode runs * the failure scenarios and exits via raw SYS_exit_group (house pattern, * tests/test_malloc.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. Not part of the library proper; compiled manually for this * todo (the tests/ + make check wiring is owned by a later todo). */ #include #include "../include/sys/uio.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; } /* Byte-compare buf against want over n bytes (no string.h in this TU). */ static int bytes_eq(const char *buf, const char *want, size_t n) { size_t i; for (i = 0; i < n; i++) { if (buf[i] != want[i]) { return 0; } } return 1; } /* The three scatter buffers every scenario writes. */ static void fill_write_iov(struct iovec *io, size_t n) { static const char ab[2] = {'a', 'b'}; static const char cd[2] = {'c', 'd'}; static const char ef[2] = {'e', 'f'}; io[0].iov_base = (void *)ab; io[0].iov_len = 2; io[1].iov_base = (void *)cd; io[1].iov_len = 2; io[2].iov_base = (void *)ef; io[2].iov_len = 2; (void)n; } /* 1+4. writev scatter -> contiguous read / readv-gather of the total. */ static int scatter_then_gather(void) { struct iovec wio[3]; struct iovec rio; char flat[8]; int fds[2]; int ok = 1; if (__syscall1(SYS_pipe, (long)fds) != 0) { check(0, "scatter_then_gather: raw SYS_pipe setup failed"); return 0; } fill_write_iov(wio, 3); check(writev(fds[1], wio, 3) == 6, "writev of three buffers returns 6"); check(__syscall3(SYS_read, fds[0], (long)flat, 6) == 6, "raw read back of the pipe returns 6"); check(bytes_eq(flat, "abcdef", 6), "read-back bytes are the contiguous \"abcdef\""); check(__syscall1(SYS_close, fds[0]) == 0 && __syscall1(SYS_close, fds[1]) == 0, "close of the first pipe pair"); /* Scenario 4: a fresh pipe, single-buffer readv gathers the total. */ if (__syscall1(SYS_pipe, (long)fds) != 0) { check(0, "readv-total: raw SYS_pipe setup failed"); return 0; } fill_write_iov(wio, 3); check(writev(fds[1], wio, 3) == 6, "writev fills the second pipe with 6 bytes"); rio.iov_base = flat; rio.iov_len = 6; check(readv(fds[0], &rio, 1) == 6, "readv of a 6-byte iovec returns 6 (the total)"); check(bytes_eq(flat, "abcdef", 6), "single-buffer readv gathered the whole stream"); check(__syscall1(SYS_close, fds[0]) == 0 && __syscall1(SYS_close, fds[1]) == 0, "close of the second pipe pair"); return ok; } /* 2. readv gathers into three separate buffers, in order. */ static void readv_gather_scenario(void) { struct iovec wio[1]; struct iovec rio[3]; char r0[2], r1[2], r2[2]; static const char ab[2] = {'a', 'b'}; static const char cd[2] = {'c', 'd'}; static const char ef[2] = {'e', 'f'}; int fds[2]; if (__syscall1(SYS_pipe, (long)fds) != 0) { check(0, "readv gather: raw SYS_pipe setup failed"); return; } wio[0].iov_base = (void *)ab; wio[0].iov_len = 2; check(writev(fds[1], wio, 1) == 2, "writev writes the \"ab\" segment"); wio[0].iov_base = (void *)cd; wio[0].iov_len = 2; check(writev(fds[1], wio, 1) == 2, "writev writes the \"cd\" segment"); wio[0].iov_base = (void *)ef; wio[0].iov_len = 2; check(writev(fds[1], wio, 1) == 2, "writev writes the \"ef\" segment"); rio[0].iov_base = r0; rio[0].iov_len = 2; rio[1].iov_base = r1; rio[1].iov_len = 2; rio[2].iov_base = r2; rio[2].iov_len = 2; check(readv(fds[0], rio, 3) == 6, "readv over three len-2 buffers returns 6"); check(bytes_eq(r0, "ab", 2) && bytes_eq(r1, "cd", 2) && bytes_eq(r2, "ef", 2), "each readv buffer holds its slice in order"); check(__syscall1(SYS_close, fds[0]) == 0 && __syscall1(SYS_close, fds[1]) == 0, "close of the gather pipe pair"); } /* 3. empty iov (iovcnt 0): the kernel never touches the NULL iov pointer. */ static void empty_iov_scenario(void) { int fds[2]; if (__syscall1(SYS_pipe, (long)fds) != 0) { check(0, "empty iov: raw SYS_pipe setup failed"); return; } check(writev(fds[1], NULL, 0) == 0, "writev(fd, NULL, 0) returns 0"); check(__syscall1(SYS_close, fds[0]) == 0 && __syscall1(SYS_close, fds[1]) == 0, "close of the empty-iov pipe pair"); } /* 5. writev on a closed fd -> -1 (errno bracketed in default mode). */ static void closed_fd_scenario(void) { struct iovec wio[1]; static const char ab[2] = {'a', 'b'}; unsigned long saved; int fds[2]; if (__syscall1(SYS_pipe, (long)fds) != 0) { check(0, "closed fd: raw SYS_pipe setup failed"); return; } check(__syscall1(SYS_close, fds[1]) == 0, "close of the write end returns 0"); wio[0].iov_base = (void *)ab; wio[0].iov_len = 2; saved = tcb_slot1(); check(writev(fds[1], wio, 1) == -1, "writev on the closed write end returns -1"); tcb_slot1_set(saved); check(__syscall1(SYS_close, fds[0]) == 0, "close of the read end returns 0"); } /* * 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). readv(fd, NULL, 1) is the QA * EFAULT case: with iovcnt 1 the kernel copies the iovec array out of the * NULL pointer and faults — errno EFAULT, asserted as -1 only. */ static int failure_scenarios(void) { struct iovec io[1]; char buf[2]; int rc = 0; io[0].iov_base = buf; io[0].iov_len = 2; if (readv(-1, io, 1) != -1) { say(2, "FAIL: readv(-1, valid iov, 1) did not return -1\n"); rc = 1; } else { say(1, "PASS: readv(-1, valid iov, 1) -> -1\n"); } if (readv(0, NULL, 1) != -1) { say(2, "FAIL: readv(fd, NULL, 1) did not return -1\n"); rc = 1; } else { say(1, "PASS: readv(fd, NULL, 1) -> -1 (kernel EFAULT)\n"); } return rc; } int main(int argc, char **argv) { if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f') { /* * 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. */ int rc = failure_scenarios(); __syscall1(SYS_exit_group, rc); return rc; /* not reached */ } scatter_then_gather(); readv_gather_scenario(); empty_iov_scenario(); closed_fd_scenario(); if (failures > 0) { say(2, "FAILED ("); say_dec(2, (unsigned long)failures); say(2, " check(s))\n"); return 1; } say(1, "all readv/writev tests passed\n"); return 0; }