Files
vlibc/tests/test_mman.c
T

354 lines
11 KiB
C

/*
* vlibc — sys/mman.h test (task 33).
*
* Exercises the mmap family end to end over the raw syscall wrappers:
*
* 1. Anonymous mmap of one page → readable/writable, write + read-back
* round-trip, munmap releases it.
* 2. mmap PROT_NONE succeeds; mprotect to PROT_READ|PROT_WRITE makes the
* page writable (the write succeeds and reads back).
* 3. msync MS_SYNC on a MAP_SHARED file mapping flushes the pointer
* writes to the backing file: a read() of the same fd afterwards sees
* the data written through the mapping.
* 4. mlock/munlock of an anonymous page, and mlockall/munlockall,
* return 0.
* 5. posix_madvise (POSIX_MADV_DONTNEED) returns 0.
* 6. MAP_SHARED file mapping round-trip is proven by scenario 3 (write
* via the mapping, read back through the fd).
*
* Level-2 gated section: madvise(MADV_DONTNEED) returns 0, and POSIX
* shared memory round-trips: shm_open creates a /dev/shm object, a write
* survives close, a second shm_open (no O_CREAT) re-opens the same object
* and reads the data back (proving the real-file backing), and shm_unlink
* removes it.
*
* The -f mode runs the failure scenarios — mmap MAP_FIXED at an unaligned
* address → MAP_FAILED, munmap of that address → -1, shm_open of a name
* containing '/' → -1. These make the LIBRARY write errno (syscall_ret),
* which under a host-linked binary targets glibc's private dtv slot at
* %fs:0+8; the process therefore exits via raw SYS_exit_group without
* running host cleanup (house pattern, tests/test_unistd_file.c). In the
* default mode every exercised call succeeds, so no library errno write
* happens; the one host-TCB slot-1 save/restore at main brackets the whole
* run anyway (task 13 technique), keeping host state intact for the return
* through __libc_start_main. The test NEVER reads errno; every negative is
* asserted on the return value only.
*
* 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/fcntl.h"
#include "../include/sys/mman.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);
}
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 the
* whole default-mode run; 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;
}
/* Fill buf with the repeating pattern i * 7 + 1 (unsigned char). */
static void
fill_pattern(unsigned char *buf, unsigned long n)
{
unsigned long i;
for (i = 0; i < n; i++)
{
buf[i] = (unsigned char)(i * 7 + 1);
}
}
/* 1+2: anonymous mappings, protection change. */
static void
anon_scenarios(void)
{
static unsigned char pattern[4096];
unsigned char *p;
unsigned long i;
int ok = 1;
fill_pattern(pattern, sizeof(pattern));
p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
check(p != MAP_FAILED && p != NULL, "mmap 4096 anonymous returns a mapping");
if (p != MAP_FAILED && p != NULL)
{
for (i = 0; i < 4096; i++)
{
p[i] = pattern[i];
}
for (i = 0; i < 4096; i++)
{
if (p[i] != pattern[i])
{
ok = 0;
}
}
check(ok, "write + read-back round-trips on the anonymous mapping");
check(munmap(p, 4096) == 0, "munmap of the anonymous mapping returns 0");
}
p = mmap(NULL, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
check(p != MAP_FAILED && p != NULL, "mmap 4096 PROT_NONE anonymous returns a mapping");
if (p != MAP_FAILED && p != NULL)
{
check(mprotect(p, 4096, PROT_READ | PROT_WRITE) == 0,
"mprotect(PROT_NONE -> PROT_READ|PROT_WRITE) returns 0");
p[0] = 'P';
p[1] = 'W';
check(p[0] == 'P' && p[1] == 'W', "the page is writable after mprotect");
check(munmap(p, 4096) == 0, "munmap of the mprotect page returns 0");
}
}
/* 3+6: msync + MAP_SHARED file visibility, plus the mlock family (4+5). */
static void
file_and_lock_scenarios(const char *path)
{
static unsigned char pattern[4096];
unsigned char *p;
unsigned char rbuf[16];
int fd;
fill_pattern(pattern, sizeof(pattern));
fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
check(fd >= 0, "open creates the msync backing file");
if (fd < 0)
{
return;
}
check(write(fd, pattern, sizeof(pattern)) == (ssize_t)sizeof(pattern),
"write of 4096 bytes to the backing file returns 4096");
p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
check(p != MAP_FAILED && p != NULL, "mmap MAP_SHARED of the file returns a mapping");
if (p != MAP_FAILED && p != NULL)
{
p[0] = 'M';
p[1] = 'S';
p[2] = 'Y';
p[3] = 'N';
p[4] = 'C';
p[5] = '!';
check(msync(p, 4096, MS_SYNC) == 0, "msync MS_SYNC returns 0");
check(lseek(fd, 0, SEEK_SET) == 0, "lseek back to offset 0 returns 0");
check(read(fd, rbuf, sizeof(rbuf)) == (ssize_t)sizeof(rbuf),
"read of the backing file returns 16 bytes");
check(rbuf[0] == 'M' && rbuf[1] == 'S' && rbuf[2] == 'Y' && rbuf[3] == 'N' &&
rbuf[4] == 'C' && rbuf[5] == '!',
"the msync'd mapping writes are visible through the fd");
check(munmap(p, 4096) == 0, "munmap of the shared mapping returns 0");
}
check(close(fd) == 0, "close of the backing file returns 0");
{
unsigned char *q;
q = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
check(q != MAP_FAILED && q != NULL, "mmap a page for the mlock family");
if (q != MAP_FAILED && q != NULL)
{
q[0] = 'L';
check(mlock(q, 4096) == 0, "mlock of the page returns 0");
check(munlock(q, 4096) == 0, "munlock of the page returns 0");
check(munmap(q, 4096) == 0, "munmap of the mlock page returns 0");
}
}
check(mlockall(MCL_CURRENT | MCL_FUTURE) == 0, "mlockall(MCL_CURRENT|MCL_FUTURE) returns 0");
check(munlockall() == 0, "munlockall returns 0");
{
unsigned char *r;
r = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
check(r != MAP_FAILED && r != NULL, "mmap a page for posix_madvise");
if (r != MAP_FAILED && r != NULL)
{
check(posix_madvise(r, 4096, POSIX_MADV_DONTNEED) == 0,
"posix_madvise(POSIX_MADV_DONTNEED) returns 0");
check(munmap(r, 4096) == 0, "munmap of the posix_madvise page returns 0");
}
}
}
#if VLIBC_LEVEL_GE(2)
/* Level 2: madvise and POSIX shared memory (real /dev/shm objects). */
static void
level2_scenarios(void)
{
const char *name = "vlibc-t33-shm";
unsigned char *p;
char rbuf[16];
int fd;
int fd2;
p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
check(p != MAP_FAILED && p != NULL, "mmap a page for madvise");
if (p != MAP_FAILED && p != NULL)
{
p[0] = 'D';
check(madvise(p, 4096, MADV_DONTNEED) == 0, "madvise(MADV_DONTNEED) returns 0");
check(munmap(p, 4096) == 0, "munmap of the madvise page returns 0");
}
fd = shm_open(name, O_RDWR | O_CREAT, 0600);
check(fd >= 0, "shm_open creates the shared memory object");
if (fd < 0)
{
return;
}
check(write(fd, "shmdata", 7) == 7, "write to the shm object returns 7");
check(close(fd) == 0, "close of the shm object returns 0");
fd2 = shm_open(name, O_RDWR, 0);
check(fd2 >= 0, "shm_open re-opens the object without O_CREAT");
if (fd2 >= 0)
{
check(read(fd2, rbuf, sizeof(rbuf)) == 7, "read of the re-opened object returns 7");
check(rbuf[0] == 's' && rbuf[1] == 'h' && rbuf[2] == 'm' && rbuf[3] == 'd' &&
rbuf[4] == 'a' && rbuf[5] == 't' && rbuf[6] == 'a',
"the re-opened object holds the earlier write (file-backed)");
check(close(fd2) == 0, "close of the re-opened object returns 0");
}
check(shm_unlink(name) == 0, "shm_unlink removes the object");
}
#endif /* VLIBC_LEVEL_GE(2) */
/*
* 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)
{
int rc = 0;
if (mmap((void *)0x1, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1,
0) != MAP_FAILED)
{
say(2, "FAIL: mmap MAP_FIXED at an unaligned address did not return MAP_FAILED\n");
rc = 1;
}
else
{
say(1, "PASS: mmap MAP_FIXED at 0x1 -> MAP_FAILED\n");
}
if (munmap((void *)0x1, 4096) != -1)
{
say(2, "FAIL: munmap of an unaligned address did not return -1\n");
rc = 1;
}
else
{
say(1, "PASS: munmap((void *)0x1, 4096) -> -1\n");
}
#if VLIBC_LEVEL_GE(2)
if (shm_open("bad/name", O_RDWR | O_CREAT, 0600) != -1)
{
say(2, "FAIL: shm_open of a name containing '/' did not return -1\n");
rc = 1;
}
else
{
say(1, "PASS: shm_open(\"bad/name\") -> -1\n");
}
#endif
return rc;
}
int
main(int argc, char **argv)
{
const char *path = "/tmp/vlibc-t33-shmfile";
unsigned long saved;
int rc;
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.
*/
rc = failure_scenarios();
__syscall1(SYS_exit_group, rc);
return rc; /* not reached */
}
/* Bracket the whole default-mode run against the host-TCB hazard. */
saved = tcb_slot1();
anon_scenarios();
file_and_lock_scenarios(path);
#if VLIBC_LEVEL_GE(2)
level2_scenarios();
#endif
__syscall3(SYS_unlinkat, AT_FDCWD, (long)path, 0);
if (failures > 0)
{
say(2, "FAILED: one or more mman checks failed\n");
rc = 1;
}
else
{
say(1, "all mman tests passed\n");
rc = 0;
}
tcb_slot1_set(saved);
return rc;
}