Files

333 lines
9.8 KiB
C

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