/* * remaptest.c -- stage 19: mremap, checked from musl. * * Built with `musl-gcc -static-pie` (see build.rs). mremap makes an mmap * region bigger or smaller, moving it if needed; realloc() uses it for big * blocks so the bytes don't have to be copied. * * remaptest all checks (exit code = number of failed checks) */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define PG 4096UL static int fails; #define CHECK(cond, ...) \ do { \ if (!(cond)) { \ fails++; \ printf("remaptest: FAIL line %d: ", __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ } \ } while (0) static char *anon(size_t pages) { char *p = mmap(0, pages * PG, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); return p == MAP_FAILED ? 0 : p; } static void fill(char *p, size_t pages, int seed) { for (size_t i = 0; i < pages; i++) memset(p + i * PG, seed + (int)i, PG); } static int same(const char *p, size_t pages, int seed) { for (size_t i = 0; i < pages; i++) for (size_t j = 0; j < PG; j += 512) if (p[i * PG + j] != (char)(seed + (int)i)) return 0; return 1; } static int zero(const char *p, size_t len) { for (size_t i = 0; i < len; i += 256) if (p[i]) return 0; return 1; } /* Does touching `p` kill a child with SIGSEGV? (= nothing mapped there) */ static int faults(volatile char *p) { pid_t pid = fork(); if (pid == 0) { (void)*p; _exit(0); } int st = 0; waitpid(pid, &st, 0); return WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV; } static void grow_in_place(void) { char *p = anon(8); CHECK(p, "mmap 8 pages"); munmap(p + 3 * PG, 5 * PG); /* keep 3, leave room behind them */ fill(p, 3, 'a'); char *q = mremap(p, 3 * PG, 6 * PG, 0); CHECK(q == p, "grow in place: %p, wanted %p", q, p); if (q != p) return; CHECK(same(p, 3, 'a'), "grow in place kept the data"); CHECK(zero(p + 3 * PG, 3 * PG), "the new part is zero"); p[6 * PG - 1] = 1; /* and writable */ q = mremap(p, 6 * PG, 2 * PG, 0); CHECK(q == p, "shrink: %p", q); CHECK(same(p, 2, 'a'), "shrink kept the data"); CHECK(faults(p + 2 * PG), "the cut-off part is gone"); CHECK(mremap(p, 2 * PG, 2 * PG, 0) == p, "same size: nothing to do"); munmap(p, 2 * PG); } static void move(void) { char *a = anon(2); char *b = anon(1); /* right below a: can't grow in place */ CHECK(a && b && b + PG == a, "b (%p) sits right below a (%p)", b, a); fill(b, 1, 'm'); errno = 0; CHECK(mremap(b, PG, 4 * PG, 0) == MAP_FAILED && errno == ENOMEM, "no room and no MAYMOVE: ENOMEM (%d)", errno); char *c = mremap(b, PG, 4 * PG, MREMAP_MAYMOVE); CHECK(c != MAP_FAILED && c != b, "MAYMOVE moved it: %p", c); if (c == MAP_FAILED) return; CHECK(same(c, 1, 'm'), "the moved page kept its bytes"); CHECK(zero(c + PG, 3 * PG), "the rest is zero"); CHECK(faults(b), "nothing left at the old address"); /* MREMAP_FIXED: to an address we pick. */ char *spot = anon(4); munmap(spot, 4 * PG); char *d = mremap(c, 4 * PG, 2 * PG, MREMAP_MAYMOVE | MREMAP_FIXED, spot); CHECK(d == spot, "MREMAP_FIXED: %p, wanted %p", d, spot); if (d == spot) CHECK(same(d, 1, 'm'), "MREMAP_FIXED kept the bytes"); CHECK(faults(c), "and the old place is empty"); munmap(spot, 2 * PG); munmap(a, 2 * PG); } static void errors(void) { char *p = anon(2); errno = 0; CHECK(mremap(p + 1, PG, 2 * PG, MREMAP_MAYMOVE) == MAP_FAILED && errno == EINVAL, "unaligned: EINVAL (%d)", errno); errno = 0; CHECK(mremap(p, PG, 0, MREMAP_MAYMOVE) == MAP_FAILED && errno == EINVAL, "new_len 0: EINVAL (%d)", errno); errno = 0; CHECK(mremap(p, PG, 2 * PG, 64) == MAP_FAILED && errno == EINVAL, "unknown flag: EINVAL (%d)", errno); errno = 0; CHECK(mremap(p, PG, 2 * PG, MREMAP_FIXED, p + 8 * PG) == MAP_FAILED && errno == EINVAL, "FIXED without MAYMOVE: EINVAL (%d)", errno); errno = 0; CHECK(mremap(p, 2 * PG, 4 * PG, MREMAP_MAYMOVE | MREMAP_FIXED, p + PG) == MAP_FAILED && errno == EINVAL, "FIXED onto itself: EINVAL (%d)", errno); munmap(p, 2 * PG); errno = 0; CHECK(mremap(p, PG, 2 * PG, MREMAP_MAYMOVE) == MAP_FAILED && errno == EFAULT, "nothing mapped: EFAULT (%d)", errno); } static void file_backed(void) { int fd = open("/tmp/remap.dat", O_RDWR | O_CREAT | O_TRUNC, 0644); CHECK(fd >= 0, "open /tmp/remap.dat"); char page[PG]; for (int i = 0; i < 3; i++) { memset(page, 'F' + i, PG); write(fd, page, PG); } char *keep = anon(1); /* so the mapping below can't grow in place */ char *f = mmap(0, PG, PROT_READ, MAP_PRIVATE, fd, 0); (void)keep; CHECK(f != MAP_FAILED && f[0] == 'F', "file page 0"); char *g = mremap(f, PG, 3 * PG, MREMAP_MAYMOVE); CHECK(g != MAP_FAILED, "mremap a file mapping"); if (g != MAP_FAILED) { CHECK(g[0] == 'F' && g[PG] == 'G' && g[2 * PG + 7] == 'H', "moved file pages read the file: %c %c %c", g[0], g[PG], g[2 * PG + 7]); munmap(g, 3 * PG); } munmap(keep, PG); close(fd); unlink("/tmp/remap.dat"); } static void after_fork(void) { char *p = anon(1); fill(p, 1, 'p'); pid_t pid = fork(); if (pid == 0) { char *q = mremap(p, PG, 16 * PG, MREMAP_MAYMOVE); if (q == MAP_FAILED || q[0] != 'p') _exit(1); q[0] = 'c'; /* copy-on-write must still work after the move */ _exit(q[0] == 'c' ? 0 : 2); } int st = 0; waitpid(pid, &st, 0); CHECK(WIFEXITED(st) && WEXITSTATUS(st) == 0, "child moved and wrote: status %#x", st); CHECK(p[0] == 'p', "parent's copy unchanged: %c", p[0]); munmap(p, PG); } static void with_realloc(void) { size_t n = 1 << 20; unsigned *v = malloc(n); CHECK(v, "malloc 1 MiB"); for (size_t i = 0; i < n / 4; i++) v[i] = (unsigned)i * 2654435761u; for (size_t big = 4 << 20; big <= 32 << 20; big *= 2) { v = realloc(v, big); CHECK(v, "realloc %zu", big); if (!v) return; size_t bad = 0; for (size_t i = 0; i < n / 4; i++) bad += v[i] != (unsigned)i * 2654435761u; CHECK(bad == 0, "realloc to %zu MiB kept the data (%zu wrong)", big >> 20, bad); } v = realloc(v, 2 << 20); CHECK(v && v[12345] == 12345u * 2654435761u, "realloc down"); free(v); } int main(void) { grow_in_place(); move(); errors(); file_backed(); after_fork(); with_realloc(); printf("remaptest: %s (%d failed)\n", fails ? "FAILED" : "all passed", fails); return fails; }