-/* 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 <sys/types.h>
#include <sys/mman.h>
+#include <sys/ioctl.h>
#include <sys/ioc_memory.h>
+#include <sys/param.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
+#include <dirent.h>
#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)
{
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
basic_regression();
+ test_memory_types_vs_operations();
+
makefiles(MAXFILES);
cachequiet(!bigflag);