333 lines
9.8 KiB
C
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;
|
|
}
|