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