From aa6ee31737105594afcff0cc600f5e933ce59ad7 Mon Sep 17 00:00:00 2001 From: Ben Gras Date: Fri, 28 Feb 2014 16:56:40 +0100 Subject: [PATCH] test74: add mmap-related regression tests tests many complex system/process memory interaction cases. has to run as root so it can flush the FS cache; needed to force FS cache misses for unmapped pages. See the comment in test74.c for a full description of what the tested cases are. also re-enable filemap on arm --- releasetools/gen_uEnv.txt.sh | 2 +- test/run | 2 +- test/test74.c | 370 ++++++++++++++++++++++++++++++++++- 3 files changed, 369 insertions(+), 5 deletions(-) diff --git a/releasetools/gen_uEnv.txt.sh b/releasetools/gen_uEnv.txt.sh index f53f3889f..b369a9de9 100755 --- a/releasetools/gen_uEnv.txt.sh +++ b/releasetools/gen_uEnv.txt.sh @@ -75,7 +75,7 @@ fill_cmd() { echo "# Set the command to be executed" echo "uenvcmd=run $BOOT" -echo "bootargs=console=$CONSOLE rootdevname=c0d0p1 verbose=$VERBOSE hz=$HZ filemap=0" +echo "bootargs=console=$CONSOLE rootdevname=c0d0p1 verbose=$VERBOSE hz=$HZ" echo echo 'bootminix=setenv bootargs \$bootargs board_name=\$board_name ; echo \$bootargs; go 0x80200000 \\\"$bootargs\\\"' echo diff --git a/test/run b/test/run index a61a4dcf2..223652033 100755 --- a/test/run +++ b/test/run @@ -21,7 +21,7 @@ badones= # list of tests that failed # Programs that require setuid setuids="test11 test33 test43 test44 test46 test56 test60 test61 test65 \ - test69 test76 test73 test77 test78" + test69 test76 test73 test74 test77 test78" # Scripts that require to be run as root rootscripts="testisofs testvnd" diff --git a/test/test74.c b/test/test74.c index da92740dc..7d2d23fb2 100644 --- a/test/test74.c +++ b/test/test74.c @@ -1,19 +1,54 @@ -/* Test 74 - mmap functionality test. +/* Test 74 - mmap functionality & regression test. + * + * This test tests some basic functionality of mmap, and also some + * cases that are quite complex for the system to handle. + * + * Memory pages are generally made available on demand. Memory copying + * is done by the kernel. As the kernel may encounter pagefaults in + * legitimate memory ranges (e.g. pages that aren't mapped; pages that + * are mapped RO as they are COW), it cooperates with VM to make the + * mappings and let the copy succeed transparently. + * + * With file-mapped ranges this can result in a deadlock, if care is + * not taken, as the copy might be request by VFS or an FS. This test + * triggers as many of these states as possible to ensure they are + * successful or (where appropriate) fail gracefully, i.e. without + * deadlock. + * + * To do this, system calls are done with source or target buffers with + * missing or readonly mappings, both anonymous and file-mapped. The + * cache is flushed before mmap() so that we know the mappings should + * not be present on mmap() time. Then e.g. a read() or write() is + * executed with that buffer as target. This triggers a FS copying + * to or from a missing range that it itself is needed to map in first. + * VFS detects this, requests VM to map in the pages, which does so with + * the help of another VFS thread and the FS, and then re-issues the + * request to the FS. + * + * Another case is the VFS itself does such a copy. This is actually + * unusual as filenames are already faulted in by the requesting process + * in libc by strlen(). select() allows such a case, however, so this + * is tested too. We are satisfied if the call completes. */ #include #include +#include #include +#include #include #include #include #include #include #include +#include #include "common.h" #include "testcache.h" +int max_error = 0; /* make all e()'s fatal */ + int dowriteblock(int b, int blocksize, u32_t seed, char *data) { @@ -75,19 +110,346 @@ readblock(int b, int blocksize, u32_t seed, char *data) void testend(void) { } +static void do_read(void *buf, int fd, int writable) +{ + ssize_t ret; + size_t n = PAGE_SIZE; + struct stat sb; + if(fstat(fd, &sb) < 0) e(1); + if(S_ISDIR(sb.st_mode)) return; + ret = read(fd, buf, n); + + /* if the buffer is writable, it should succeed */ + if(writable) { if(ret != n) e(3); return; } + + /* if the buffer is not writable, it should fail with EFAULT */ + if(ret >= 0) e(4); + if(errno != EFAULT) e(5); +} + +static void do_write(void *buf, int fd, int writable) +{ + size_t n = PAGE_SIZE; + struct stat sb; + if(fstat(fd, &sb) < 0) e(1); + if(S_ISDIR(sb.st_mode)) return; + if(write(fd, buf, n) != n) e(3); +} + +static void do_stat(void *buf, int fd, int writable) +{ + int r; + struct stat sb; + r = fstat(fd, (struct stat *) buf); + + /* should succeed if buf is writable */ + if(writable) { if(r < 0) e(3); return; } + + /* should fail with EFAULT if buf is not */ + if(r >= 0) e(4); + if(errno != EFAULT) e(5); +} + +static void do_getdents(void *buf, int fd, int writable) +{ + struct stat sb; + int r; + if(fstat(fd, &sb) < 0) e(1); + if(!S_ISDIR(sb.st_mode)) return; /* OK */ + r = getdents(fd, buf, PAGE_SIZE); + if(writable) { if(r < 0) e(3); return; } + + /* should fail with EFAULT if buf is not */ + if(r >= 0) e(4); + if(errno != EFAULT) e(5); +} + +static void do_readlink1(void *buf, int fd, int writable) +{ + char target[200]; + /* the system call just has to fail gracefully */ + readlink(buf, target, sizeof(target)); +} + +#define NODENAME "a" +#define TARGETNAME "b" + +static void do_readlink2(void *buf, int fd, int writable) +{ + ssize_t rl; + unlink(NODENAME); + if(symlink(TARGETNAME, NODENAME) < 0) e(1); + rl=readlink(NODENAME, buf, sizeof(buf)); + + /* if buf is writable, it should succeed, with a certain result */ + if(writable) { + if(rl < 0) e(2); + ((char *) buf)[rl] = '\0'; + if(strcmp(buf, TARGETNAME)) { + fprintf(stderr, "readlink: expected %s, got %s\n", + TARGETNAME, buf); + e(3); + } + return; + } + + /* if buf is not writable, it should fail with EFAULT */ + if(rl >= 0) e(4); + + if(errno != EFAULT) e(5); +} + +static void do_symlink1(void *buf, int fd, int writable) +{ + int r; + /* the system call just has to fail gracefully */ + r = symlink(buf, NODENAME); +} + +static void do_symlink2(void *buf, int fd, int writable) +{ + int r; + /* the system call just has to fail gracefully */ + r = symlink(NODENAME, buf); +} + +static void do_open(void *buf, int fd, int writable) +{ + int r; + /* the system call just has to fail gracefully */ + r = open(buf, O_RDONLY); + if(r >= 0) close(r); +} + +static void do_select1(void *buf, int fd, int writable) +{ + int r; + struct timeval timeout = { 0, 200000 }; /* 0.2 sec */ + /* the system call just has to fail gracefully */ + r = select(1, buf, NULL, NULL, &timeout); +} + +static void do_select2(void *buf, int fd, int writable) +{ + int r; + struct timeval timeout = { 0, 200000 }; /* 1 sec */ + /* the system call just has to fail gracefully */ + r = select(1, NULL, buf, NULL, &timeout); +} + +static void do_select3(void *buf, int fd, int writable) +{ + int r; + struct timeval timeout = { 0, 200000 }; /* 1 sec */ + /* the system call just has to fail gracefully */ + r = select(1, NULL, NULL, buf, &timeout); +} + +static void fillfile(int fd, int size) +{ + char *buf = malloc(size); + + if(size < 1 || size % PAGE_SIZE || !buf) { e(1); } + memset(buf, 'A', size); + buf[50] = '\0'; /* so it can be used as a filename arg */ + buf[size-1] = '\0'; + if(write(fd, buf, size) != size) { e(2); } + if(lseek(fd, SEEK_SET, 0) < 0) { e(3); } + free(buf); +} + +static void make_buffers(int size, + int *ret_fd_rw, int *ret_fd_ro, + void **filebuf_rw, void **filebuf_ro, void **anonbuf) +{ + char fn_rw[] = "testfile_rw.XXXXXX", fn_ro[] = "testfile_ro.XXXXXX"; + *ret_fd_rw = mkstemp(fn_rw); + *ret_fd_ro = mkstemp(fn_ro); + + if(size < 1 || size % PAGE_SIZE) { e(2); } + if(*ret_fd_rw < 0) { e(1); } + if(*ret_fd_ro < 0) { e(1); } + fillfile(*ret_fd_rw, size); + fillfile(*ret_fd_ro, size); + if(fcntl(*ret_fd_rw, F_FLUSH_FS_CACHE) < 0) { e(4); } + if(fcntl(*ret_fd_ro, F_FLUSH_FS_CACHE) < 0) { e(4); } + + if((*filebuf_rw = mmap(0, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FILE, *ret_fd_rw, 0)) == MAP_FAILED) { + e(5); + quit(); + } + + if((*filebuf_ro = mmap(0, size, PROT_READ, + MAP_PRIVATE | MAP_FILE, *ret_fd_ro, 0)) == MAP_FAILED) { + e(5); + quit(); + } + + if((*anonbuf = mmap(0, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0)) == MAP_FAILED) { + e(6); + quit(); + } + + if(unlink(fn_rw) < 0) { e(12); } + if(unlink(fn_ro) < 0) { e(12); } +} + +static void forget_buffers(void *buf1, void *buf2, void *buf3, int fd1, int fd2, int size) +{ + if(munmap(buf1, size) < 0) { e(1); } + if(munmap(buf2, size) < 0) { e(2); } + if(munmap(buf3, size) < 0) { e(2); } + if(fcntl(fd1, F_FLUSH_FS_CACHE) < 0) { e(3); } + if(fcntl(fd2, F_FLUSH_FS_CACHE) < 0) { e(3); } + if(close(fd1) < 0) { e(4); } + if(close(fd2) < 0) { e(4); } +} + +#define NEXPERIMENTS 12 +struct { + void (*do_operation)(void * buf, int fd, int writable); +} experiments[NEXPERIMENTS] = { + { do_read }, + { do_write }, + { do_stat }, + { do_getdents }, + { do_readlink1 }, + { do_readlink2 }, + { do_symlink1 }, + { do_symlink2 }, + { do_open, }, + { do_select1 }, + { do_select2 }, + { do_select3 }, +}; + +void test_memory_types_vs_operations(void) +{ +#define NFDS 4 +#define BUFSIZE (10 * PAGE_SIZE) + int exp, fds[NFDS]; + int f = 0, size = BUFSIZE; + + /* open some test fd's */ +#define OPEN(fn, mode) { assert(f >= 0 && f < NFDS); \ + fds[f] = open(fn, mode); if(fds[f] < 0) { e(2); } f++; } + OPEN("regular", O_RDWR | O_CREAT); + OPEN(".", O_RDONLY); + OPEN("/dev/ram", O_RDWR); + OPEN("/dev/zero", O_RDWR); + + /* make sure the regular file has plenty of size to play with */ + fillfile(fds[0], BUFSIZE); + + /* and the ramdisk too */ + if(ioctl(fds[2], MIOCRAMSIZE, &size) < 0) { e(3); } + + for(exp = 0; exp < NEXPERIMENTS; exp++) { + for(f = 0; f < NFDS; f++) { + void *anonmem, *filemem_rw, *filemem_ro; + int buffd_rw, buffd_ro; + + make_buffers(BUFSIZE, &buffd_rw, &buffd_ro, + &filemem_rw, &filemem_ro, &anonmem); + + if(lseek(fds[f], 0, SEEK_SET) != 0) { e(10); } + experiments[exp].do_operation(anonmem, fds[f], 1); + + if(lseek(fds[f], 0, SEEK_SET) != 0) { e(11); } + experiments[exp].do_operation(filemem_rw, fds[f], 1); + + if(lseek(fds[f], 0, SEEK_SET) != 0) { e(12); } + experiments[exp].do_operation(filemem_ro, fds[f], 0); + + forget_buffers(filemem_rw, filemem_ro, anonmem, buffd_rw, buffd_ro, BUFSIZE); + } + } +} + void basic_regression(void) { - void *block; + int fd, fd1, fd2; + ssize_t rb, wr; + char buf[PAGE_SIZE*2]; + void *block, *block1, *block2; #define BLOCKSIZE (PAGE_SIZE*10) block = mmap(0, BLOCKSIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if(block == MAP_FAILED) { e(1); exit(1); } + if(block == MAP_FAILED) { e(1); } memset(block, 0, BLOCKSIZE); /* shrink from bottom */ munmap(block, PAGE_SIZE); + + /* Next test: use a system call write() to access a block of + * unavailable file-mapped memory. + * + * This is a thorny corner case to make succeed transparently + * because + * (1) it is a filesystem that is doing the memory access + * (copy from the constblock1 range in this process to the + * FS) but is also the FS needed to satisfy the range if it + * isn't in the cache. + * (2) there are two separate memory regions involved, requiring + * separate VFS requests from VM to properly satisfy, requiring + * some complex state to be kept. + */ + + fd1 = open("../testsh1", O_RDONLY); + fd2 = open("../testsh2", O_RDONLY); + if(fd1 < 0 || fd2 < 0) { e(2); } + + /* clear cache of files before mmap so pages won't be present already */ + if(fcntl(fd1, F_FLUSH_FS_CACHE) < 0) { e(1); } + if(fcntl(fd2, F_FLUSH_FS_CACHE) < 0) { e(1); } + +#define LOCATION1 (void *) 0x90000000 +#define LOCATION2 (LOCATION1 + PAGE_SIZE) + block1 = mmap(LOCATION1, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FILE, fd1, 0); + if(block1 == MAP_FAILED) { e(4); } + if(block1 != LOCATION1) { e(5); } + + block2 = mmap(LOCATION2, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FILE, fd2, 0); + if(block2 == MAP_FAILED) { e(10); } + if(block2 != LOCATION2) { e(11); } + + unlink("testfile"); + fd = open("testfile", O_CREAT | O_RDWR); + if(fd < 0) { e(15); } + + /* write() using the mmap()ped memory as buffer */ + + if((wr=write(fd, LOCATION1, sizeof(buf))) != sizeof(buf)) { + fprintf(stderr, "wrote %zd bytes instead of %zd\n", + wr, sizeof(buf)); + e(20); + quit(); + } + + /* verify written contents */ + + if((rb=pread(fd, buf, sizeof(buf), 0)) != sizeof(buf)) { + if(rb < 0) perror("pread"); + fprintf(stderr, "wrote %zd bytes\n", wr); + fprintf(stderr, "read %zd bytes instead of %zd\n", + rb, sizeof(buf)); + e(21); + quit(); + } + + if(memcmp(buf, LOCATION1, sizeof(buf))) { + e(22); + quit(); + } + + close(fd); + close(fd1); + close(fd2); + } int @@ -99,6 +461,8 @@ main(int argc, char *argv[]) basic_regression(); + test_memory_types_vs_operations(); + makefiles(MAXFILES); cachequiet(!bigflag); -- 2.44.0