diff options
author | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
---|---|---|
committer | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
commit | c63f340d90800895f007de64b7d2d14624263331 (patch) | |
tree | 2c0849fa597dd6da831c8707b6f2603403778d7b /user/usr/bin/tests |
Created student weenix repository
Diffstat (limited to 'user/usr/bin/tests')
-rw-r--r-- | user/usr/bin/tests/eatinodes.c | 90 | ||||
-rw-r--r-- | user/usr/bin/tests/eatmem.c | 190 | ||||
-rw-r--r-- | user/usr/bin/tests/elf_test-64.c | 38 | ||||
-rw-r--r-- | user/usr/bin/tests/forkbomb.c | 84 | ||||
-rw-r--r-- | user/usr/bin/tests/forktest.c | 19 | ||||
-rw-r--r-- | user/usr/bin/tests/linkermagic.h | 232 | ||||
-rw-r--r-- | user/usr/bin/tests/memtest.c | 816 | ||||
l--------- | user/usr/bin/tests/mm.h | 1 | ||||
l--------- | user/usr/bin/tests/page.h | 1 | ||||
-rw-r--r-- | user/usr/bin/tests/pipetest.c | 191 | ||||
-rw-r--r-- | user/usr/bin/tests/prime.c | 107 | ||||
-rw-r--r-- | user/usr/bin/tests/s5fstest.c | 332 | ||||
-rw-r--r-- | user/usr/bin/tests/stress.c | 476 | ||||
-rw-r--r-- | user/usr/bin/tests/vfstest.c | 1172 |
14 files changed, 3749 insertions, 0 deletions
diff --git a/user/usr/bin/tests/eatinodes.c b/user/usr/bin/tests/eatinodes.c new file mode 100644 index 0000000..7e6f8a1 --- /dev/null +++ b/user/usr/bin/tests/eatinodes.c @@ -0,0 +1,90 @@ +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> + +static char root_dir[64]; + +static void eatinodes_start(void) +{ + int err; + + root_dir[0] = '\0'; + do + { + snprintf(root_dir, sizeof(root_dir), "eatinodes-%d", rand()); + err = mkdir(root_dir, 0777); + if (err && errno != EEXIST) + { + printf("Failed to make test root directory: %s\n", strerror(errno)); + exit(errno); + } + } while (err != 0); + printf("Created test root directory: ./%s\n", root_dir); + + err = chdir(root_dir); + if (err < 0) + { + printf("Could not cd into test directory\n"); + exit(1); + } +} + +static void eatinodes(void) +{ + int i; + int fd; + int err = 0; + char fname[32]; + + for (i = 0; !err; ++i) + { + snprintf(fname, sizeof(fname), "test-%d", i); + fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0666); + if (fd < 0) + { + printf("Could not open file %d: %s\n", i, strerror(errno)); + break; + } + err = close(fd); + if (err < 0) + { + printf("Could not close fd %d: %s\n", fd, strerror(errno)); + break; + } + printf("Created %d files\n", i); + } + int j; + printf("Cleaning up...\n"); + for (j = 0; j < i; ++j) + { + snprintf(fname, sizeof(fname), "test-%d", j); + err = unlink(fname); + if (err < 0) + { + printf("Could not remove file %d: %s\n", j, strerror(errno)); + } + } +} + +static void eatinodes_end(void) +{ + chdir(".."); + int err = rmdir(root_dir); + if (err < 0) + { + printf("Could not remove test directory: %s\n", strerror(errno)); + } +} + +int main(int argc, char **argv) +{ + eatinodes_start(); + eatinodes(); + eatinodes_end(); + + return 0; +} diff --git a/user/usr/bin/tests/eatmem.c b/user/usr/bin/tests/eatmem.c new file mode 100644 index 0000000..f4735aa --- /dev/null +++ b/user/usr/bin/tests/eatmem.c @@ -0,0 +1,190 @@ +/* + * Eats kernel memory. Lots of it. + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <stdio.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <test/test.h> + +#define PAGE_SIZE 4096 + +static void eat(void *addr, int *count, int *num) +{ + int status; + test_fork_begin() + { + if (*num <= 0) + { + /* Eat the memory until we die */ + while (1) + { + char foo = *((char *)addr + ((*count)++ * PAGE_SIZE)); + if ((*count & 0x7f) == 0) + { + printf("Ate %d pages\n", *count); + } + } + } + else + { + /* Eat until we have the necessary number of pages */ + while (*count < *num) + { + char foo = *((char *)addr + ((*count)++ * PAGE_SIZE)); + if ((*count & 0x7f) == 0) + { + printf("Ate %d pages\n", *count); + } + } + } + } + test_fork_end(&status); + if (*num <= 0 && EFAULT != status) + { + fprintf(stderr, "Child process didn't segfault!\n"); + exit(1); + } + if (*num < 0) + { + /* Free the required number of pages */ + munmap(addr, PAGE_SIZE * (-*num)); + printf("Gave back %d pages\n", -*num); + *count += *num; + } +} + +#define FLAG_DAEMON "-d" +#define FLAG_INFINITE "-i" +#define FLAG_ITER "-y" +#define FLAG_NUM "-#" + +#define OPT_DAEMON 1 +#define OPT_INFINITE 2 +#define OPT_ITER 4 +#define OPT_NUM 8 + +int parse_args(int argc, char **argv, int *opts, int *iter, int *num) +{ + int i; + *opts = *iter = *num = 0; + for (i = 1; i < argc; i++) + { + if (!strcmp(FLAG_DAEMON, argv[i])) + { + *opts |= OPT_DAEMON; + } + else if (!strcmp(FLAG_INFINITE, argv[i])) + { + *opts |= OPT_INFINITE; + } + else if (!strcmp(FLAG_ITER, argv[i])) + { + *opts |= OPT_ITER; + if (++i >= argc || + (errno = 0, *iter = strtol(argv[i], NULL, 0), 0 != errno)) + { + return -1; + } + } + else if (!strcmp(FLAG_NUM, argv[i])) + { + *opts |= OPT_NUM; + if (++i >= argc || + (errno = 0, *num = strtol(argv[i], NULL, 0), 0 != errno)) + { + return -1; + } + } + else + { + return -1; + } + } + return 0; +} + +int main(int argc, char **argv) +{ + int status; + void *addr; + int *count; + int *opts, *iter, *num; + + /* Get our huge mess of space. We map this as a bunch of regions at the + * beginning so that unmapping (above) actually does something. */ + if (MAP_FAILED == + (addr = mmap(NULL, PAGE_SIZE * 10000, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON, -1, 0))) + { + return 1; + } + + int i; + for (i = 0; i < 40; i++) + { + if (MAP_FAILED == mmap((char *)addr + PAGE_SIZE * 25 * i, + PAGE_SIZE * 25, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_SHARED | MAP_ANON, -1, 0)) + { + return 1; + } + } + + /* Set the initial count */ + count = addr; + *count = 0; + opts = count + 1; + iter = count + 2; + num = count + 3; + + if (0 > parse_args(argc, argv, opts, iter, num)) + { + fprintf( + stderr, + "USAGE: eatmem [options]\n" FLAG_DAEMON + " run as daemon\n" FLAG_INFINITE + " run forever\n" FLAG_ITER + " [num] number of iterations to yield\n" FLAG_NUM + " [num] number of pages to eat (if negative, to relinquish)\n"); + return 1; + } + + addr = (char *)addr + PAGE_SIZE; + + printf("OM NOM NOM NOM\n"); + + if (*opts & OPT_DAEMON) + { + if (fork()) + { + exit(0); + } + } + + eat(addr, count, num); + + printf("Ate %d pages in total\n", *count); + + if (*opts & OPT_INFINITE) + { + while (1) + { + sched_yield(); + } + } + else if (*opts & OPT_ITER) + { + while (--iter) + { + sched_yield(); + } + } + printf("Giving memory back now\n"); + return 0; +} diff --git a/user/usr/bin/tests/elf_test-64.c b/user/usr/bin/tests/elf_test-64.c new file mode 100644 index 0000000..9385ac1 --- /dev/null +++ b/user/usr/bin/tests/elf_test-64.c @@ -0,0 +1,38 @@ +#include <stdio.h> + +typedef struct +{ + long int a_type; /* Entry type */ + union { + long int a_val; /* Integer value */ + void *a_ptr; /* Pointer value */ + void (*a_fcn)(void); /* Function pointer value */ + } a_un; +} Elf64_auxv_t; + +int main(int argc, char **argv, char **envp, Elf64_auxv_t *auxv) +{ + // print argument count + printf("argc: %d\n", argc); + + // print argument vector + int i; + for (i = 0; argv[i] != 0; i++) + { + printf("argv[%d] (%p): %s\n", i, argv[i], argv[i]); + } + + // print environment vector + for (i = 0; envp[i] != 0; i++) + { + printf("envp[%d] (%p): %s\n", i, envp[i], envp[i]); + } + + // print auxiliary vector + for (i = 0; auxv[i].a_type != 0; i++) + { + printf("auxv[%d]: type %ld\n", i, auxv[i].a_type); + } + + return 0; +}
\ No newline at end of file diff --git a/user/usr/bin/tests/forkbomb.c b/user/usr/bin/tests/forkbomb.c new file mode 100644 index 0000000..543990c --- /dev/null +++ b/user/usr/bin/tests/forkbomb.c @@ -0,0 +1,84 @@ +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +/* TODO add options for different ways of forkbombing + (kind of low priority but would be fun) */ + +int main(int argc, char **argv) +{ + int n = 1; + pid_t pid; + + open("/dev/tty0", O_RDONLY, 0); + open("/dev/tty0", O_WRONLY, 0); + printf("Forking up a storm!\n"); + printf("If this runs for 10 minutes without crashing, then you "); + printf("probably aren't \nleaking resources\n"); + + if (fork()) + { + for (;;) + { + printf("Fork number : %d\n", n); + if ((pid = fork())) + { + if (pid == -1) + { + printf("Fork %d failed. Forkbomb stopping.\n", n); + exit(1); + } + int status; + sched_yield(); + wait(&status); + if (status != 0) + { + printf("Test failed. Child exit with status %d\n", status); + exit(1); + } + } + else + { + int a = 0; + sched_yield(); + exit(0); + } + n++; + } + } + +#if 0 +// Old forkbomb + if (!fork()) + { + for (;;) + { + printf("I am fork number %d\n", n); + if ((pid = fork())) + { + if (-1 != pid) + { + exit(0); + } + else + { + printf( + "%d-th fork failed. " + "forkbomb stopping.", + n); + exit(1); + } + } + ++n; + } + } + else + { + int status; + while (wait(&status) > 0) + ; + } +#endif + return 0; +} diff --git a/user/usr/bin/tests/forktest.c b/user/usr/bin/tests/forktest.c new file mode 100644 index 0000000..6e1475e --- /dev/null +++ b/user/usr/bin/tests/forktest.c @@ -0,0 +1,19 @@ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> + +int main(int argc, char *argv[], char *envp[]) +{ + printf("pid %d: Entering forktest\n", getpid()); + int pid = fork(); + printf("pid %d: Fork returned %d\n", getpid(), pid); + printf("pid %d: About to enter waitpid\n", getpid()); + int rc = waitpid(-1, 0, 0); + printf("pid %d: Waitpid returned %d\n", getpid(), rc); + if (rc == -1) + { + printf("pid %d: ERRNO = %d\n", getpid(), errno); + } + printf("pid %d: Exiting\n", getpid()); + return 0; +} diff --git a/user/usr/bin/tests/linkermagic.h b/user/usr/bin/tests/linkermagic.h new file mode 100644 index 0000000..2dfdd4f --- /dev/null +++ b/user/usr/bin/tests/linkermagic.h @@ -0,0 +1,232 @@ +#pragma once + +/* Linker variables */ +extern void *__executable_start; +extern void *_etext; +extern void *_edata; +extern void *_end; + +#define text_start ((void *)&__executable_start) +#define text_end ((void *)&_etext) +#define data_start ((void *)&_etext) +#define data_end ((void *)&_edata) +#define bss_start ((void *)&_edata) +#define bss_end ((void *)&_end) + +/* The following is the default linux linker script, for reference */ +#if 0 +/* +GNU ld (GNU Binutils for Debian) 2.18.0.20080103 + Supported emulations: + elf_i386 + i386linux + elf_x86_64 +using internal linker script: +================================================== +*/ +/* Script for -z combreloc: combine and sort reloc sections */ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", + "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) +SEARCH_DIR("/usr/i486-linux-gnu/lib32"); SEARCH_DIR("/usr/local/lib32"); SEARCH_DIR("/lib32"); SEARCH_DIR("/usr/lib32"); SEARCH_DIR("/usr/i486-linux-gnu/lib"); SEARCH_DIR("/usr/local/lib"); SEARCH_DIR("/lib"); SEARCH_DIR("/usr/lib"); +SECTIONS { + /* Read-only sections, merged into text segment: */ + PROVIDE(__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS; +.interp : { *(.interp) } +.note.gnu.build - id : { *(.note.gnu.build - id) } +.hash : { *(.hash) } +.gnu.hash : { *(.gnu.hash) } +.dynsym : { *(.dynsym) } +.dynstr : { *(.dynstr) } +.gnu.version : { *(.gnu.version) } +.gnu.version_d : { *(.gnu.version_d) } +.gnu.version_r : { *(.gnu.version_r) } +.rel.dyn : + { + *(.rel.init) + *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) + *(.rel.fini) + *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) + *(.rel.data.rel.ro *.rel.gnu.linkonce.d.rel.ro.*) + *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) + *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) + *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) + *(.rel.ctors) + *(.rel.dtors) + *(.rel.got) + *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) + } +.rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + } +.rel.plt : { *(.rel.plt) } +.rela.plt : { *(.rela.plt) } +.init : + { + KEEP(*(.init)) + } = 0x90909090 +.plt : { *(.plt) } +.text : + { + *(.text .stub .text.* .gnu.linkonce.t.*) + KEEP(*(.text.*personality *)) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + } = 0x90909090 +.fini : + { + KEEP(*(.fini)) + } = 0x90909090 + PROVIDE(__etext = .); + PROVIDE(_etext = .); + PROVIDE(etext = .); +.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } +.rodata1 : { *(.rodata1) } +.eh_frame_hdr : { *(.eh_frame_hdr) } +.eh_frame : ONLY_IF_RO { KEEP(*(.eh_frame)) } +.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(CONSTANT(MAXPAGESIZE)) - ((CONSTANT(MAXPAGESIZE) - .) & (CONSTANT(MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE)); + /* Exception handling */ +.eh_frame : ONLY_IF_RW { KEEP(*(.eh_frame)) } +.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + /* Thread Local Storage sections */ +.tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) } +.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } +.preinit_array : + { + PROVIDE_HIDDEN(__preinit_array_start = .); + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN(__preinit_array_end = .); + } +.init_array : + { + PROVIDE_HIDDEN(__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN(__init_array_end = .); + } +.fini_array : + { + PROVIDE_HIDDEN(__fini_array_start = .); + KEEP(*(.fini_array)) + KEEP(*(SORT(.fini_array.*))) + PROVIDE_HIDDEN(__fini_array_end = .); + } +.ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP(*crtbegin.o(.ctors)) + KEEP(*crtbegin ? .o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP(*(EXCLUDE_FILE(*crtend.o *crtend ? .o) .ctors)) + KEEP(*(SORT(.ctors.*))) + KEEP(*(.ctors)) + } +.dtors : + { + KEEP(*crtbegin.o(.dtors)) + KEEP(*crtbegin ? .o(.dtors)) + KEEP(*(EXCLUDE_FILE(*crtend.o *crtend ? .o) .dtors)) + KEEP(*(SORT(.dtors.*))) + KEEP(*(.dtors)) + } +.jcr : { KEEP(*(.jcr)) } +.data.rel.ro : { *(.data.rel.ro.local *.gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro *.gnu.linkonce.d.rel.ro.*) } +.dynamic : { *(.dynamic) } +.got : { *(.got) } + . = DATA_SEGMENT_RELRO_END(12, .); +.got.plt : { *(.got.plt) } +.data : + { + *(.data .data.* .gnu.linkonce.d.*) + KEEP(*(.gnu.linkonce.d.*personality *)) + SORT(CONSTRUCTORS) + } +.data1 : { *(.data1) } + _edata = .; PROVIDE(edata = .); + __bss_start = .; +.bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we don't + pad the .data section. */ + . = ALIGN(. != 0 ? 32 / 8 : 1); + } + . = ALIGN(32 / 8); + . = ALIGN(32 / 8); + _end = .; PROVIDE(end = .); + . = DATA_SEGMENT_END(.); + /* Stabs debugging sections. */ +.stab 0 : { *(.stab) } +.stabstr 0 : { *(.stabstr) } +.stab.excl 0 : { *(.stab.excl) } +.stab.exclstr 0 : { *(.stab.exclstr) } +.stab.index 0 : { *(.stab.index) } +.stab.indexstr 0 : { *(.stab.indexstr) } +.comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ +.debug 0 : { *(.debug) } +.line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ +.debug_srcinfo 0 : { *(.debug_srcinfo) } +.debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ +.debug_aranges 0 : { *(.debug_aranges) } +.debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ +.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } +.debug_abbrev 0 : { *(.debug_abbrev) } +.debug_line 0 : { *(.debug_line) } +.debug_frame 0 : { *(.debug_frame) } +.debug_str 0 : { *(.debug_str) } +.debug_loc 0 : { *(.debug_loc) } +.debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ +.debug_weaknames 0 : { *(.debug_weaknames) } +.debug_funcnames 0 : { *(.debug_funcnames) } +.debug_typenames 0 : { *(.debug_typenames) } +.debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3 */ +.debug_pubtypes 0 : { *(.debug_pubtypes) } +.debug_ranges 0 : { *(.debug_ranges) } +.gnu.attributes 0 : { KEEP(*(.gnu.attributes)) } +/ DISCARD / : { *(.note.GNU - stack) *(.gnu_debuglink) } +} +/* + +================================================== +*/ +#endif diff --git a/user/usr/bin/tests/memtest.c b/user/usr/bin/tests/memtest.c new file mode 100644 index 0000000..eee16ed --- /dev/null +++ b/user/usr/bin/tests/memtest.c @@ -0,0 +1,816 @@ +/* + * Test correct user space memory management, particularly segfaults + * Tests fun cases of mmap, munmap, and brk + * -- Alvin Kerber (alvin) + * + * Modified by twd in 7/2018 to work with improved address space layout. + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <unistd.h> +#include <weenix/syscall.h> + +#include <test/test.h> + +/* Shared header trickery */ +#include "page.h" + +#include "linkermagic.h" + +#define syscall_success(expr) \ + test_assert(0 <= (expr), "\nunexpected error: %s (%d)", \ + test_errstr(errno), errno) + +/* Helpful defines */ +#define assert_fault(statement, msg) \ + do \ + { \ + int __status; \ + test_fork_begin() \ + { \ + statement; \ + return 0; \ + } \ + test_fork_end(&__status); \ + test_assert(EFAULT == __status, \ + "Unexpected lack of segfault on " #statement " : " msg); \ + } while (0); + +#define assert_nofault(statement, msg) \ + do \ + { \ + int __status; \ + test_fork_begin() \ + { \ + statement; \ + return 0; \ + } \ + test_fork_end(&__status); \ + test_assert(0 == __status, \ + "Unexpected segfault on " #statement " : " msg); \ + } while (0); + +static char root_dir[64]; + +/* Overflow the stack */ +static void overflow(void) +{ + int junk[1000]; + overflow(); +} + +static int test_overflow(void) +{ + printf("Testing stack overflow\n"); + assert_fault(overflow(), "Stack overflow"); + return 0; +} + +static int test_mmap_bounds(void) +{ + int fd, status; + void *addr; + + printf("Testing boundaries and permissions of mmap()\n"); + + test_assert(0 < (fd = open("/dev/zero", O_RDWR, 0)), NULL); + test_assert( + MAP_FAILED != (addr = mmap(NULL, PAGE_SIZE * 3, PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd, 0)), + NULL); + /* Make sure we can actually access these addresses */ + test_assert('\0' == *(char *)addr, NULL); + test_assert('\0' == *((char *)addr + PAGE_SIZE), NULL); + test_assert('\0' == *((char *)addr + PAGE_SIZE * 2), NULL); + test_assert('\0' == *((char *)addr + PAGE_SIZE * 3 - 1), NULL); + + /* Unmap the ends */ + test_assert(0 == munmap(addr, PAGE_SIZE), NULL); + test_assert(0 == munmap((char *)addr + PAGE_SIZE * 2, PAGE_SIZE), NULL); + + /* Adjust to center, now surrounded by unmapped regions */ + addr = (char *)addr + PAGE_SIZE; + + /* Make sure we didn't unmap the middle */ + test_assert('\0' == *((char *)addr), NULL); + test_assert('\0' == *((char *)addr + PAGE_SIZE - 1), NULL); + assert_nofault(*(char *)addr = 'a', ""); + assert_nofault(*((char *)addr + PAGE_SIZE - 1) = 'b', ""); + + /* Regions around it are unmapped */ + assert_fault(char foo = *((char *)addr + PAGE_SIZE), ""); + assert_fault(char foo = *((char *)addr - PAGE_SIZE), ""); + assert_fault(char foo = *((char *)addr - 1), ""); + assert_fault(*((char *)addr + PAGE_SIZE) = 'a', ""); + assert_fault(*((char *)addr - 1) = 'a', ""); + assert_fault(*((char *)addr + PAGE_SIZE * 2 - 1) = 'a', ""); + + /* Remap as read-only */ + test_assert( + addr == mmap(addr, 1, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, 0), NULL); + + assert_fault(*((char *)addr) = 'a', ""); + assert_fault(*((char *)addr + PAGE_SIZE - 1) = 'a', ""); + + /* "Unmap" */ + test_assert(0 == munmap((char *)addr - PAGE_SIZE, PAGE_SIZE), NULL); + test_assert(0 == munmap((char *)addr + PAGE_SIZE, PAGE_SIZE), NULL); + + /* Make sure it's still there, also that it's overwritten */ + test_assert('\0' == *((char *)addr), NULL); + test_assert('\0' == *((char *)addr + PAGE_SIZE - 1), NULL); + + /* Unmap for real */ + test_assert(0 == munmap(addr, 1), NULL); + + assert_fault(char foo = *(char *)addr, ""); + assert_fault(char foo = *((char *)addr + PAGE_SIZE - 1), ""); + + /* Test fun permissions */ + test_assert(addr == mmap(addr, PAGE_SIZE, PROT_EXEC, + MAP_PRIVATE | MAP_FIXED, fd, 0), + NULL); + assert_fault(char foo = *(char *)addr, ""); + assert_fault(char foo = *((char *)addr + PAGE_SIZE - 1), ""); + assert_fault(*((char *)addr) = 'a', ""); + + test_assert( + addr == mmap(addr, PAGE_SIZE, 0, MAP_PRIVATE | MAP_FIXED, fd, 0), NULL); + assert_fault(char foo = *(char *)addr, ""); + assert_fault(char foo = *((char *)addr + PAGE_SIZE - 1), ""); + assert_fault(*((char *)addr) = 'a', ""); + + return 0; +} + +static int test_brk_bounds(void) +{ + void *oldbrk, *newbrk; + int status; + + printf("Testing boundaries and permissions of brk()\n"); + + /* "Stabilize" our old brk at a page boundary */ + test_assert((void *)-1 != (oldbrk = sbrk(0)), NULL); + oldbrk = PAGE_ALIGN_UP(oldbrk); + test_assert(0 == brk(oldbrk), NULL); + + /* Look at next page-aligned addr */ + newbrk = (char *)oldbrk + PAGE_SIZE; + + assert_fault(char foo = *(char *)newbrk, ""); + assert_fault(*(char *)newbrk = 'a', ""); + + /* Move brk to next page-aligned addr */ + test_assert(0 == brk(newbrk), NULL); + + /* Access the new memory */ + test_assert('\0' == *(char *)oldbrk, NULL); + test_assert('\0' == *((char *)newbrk - 1), NULL); + *((char *)newbrk - 1) = 'a'; + + assert_fault(char foo = *(char *)newbrk, ""); + assert_fault(*(char *)newbrk = 'a', ""); + + /* Move brk up by 1 byte */ + test_assert(0 == brk((char *)newbrk + 1), NULL); + + /* Access the new memory */ + test_assert('\0' == *(char *)newbrk, NULL); + test_assert('\0' == *((char *)newbrk + PAGE_SIZE - 1), NULL); + assert_nofault(*(char *)newbrk = 'b', ""); + + /* Old memory didn't change */ + test_assert('a' == *((char *)newbrk - 1), NULL); + + /* Move it back */ + test_assert(0 == brk(newbrk), NULL); + + assert_fault(char foo = *(char *)newbrk, ""); + assert_fault(*(char *)newbrk = 'a', ""); + + /* Move it up, make sure region wiped. Note that the actual wipe test is + * 'evil' and is in eviltest. This just checks to make sure the brk region + * is private mapped (modified in subprocesses) */ + test_assert(0 == brk((char *)newbrk + PAGE_SIZE), NULL); + test_assert('\0' == *(char *)newbrk, NULL); + test_assert('\0' == *((char *)newbrk + PAGE_SIZE - 1), NULL); + + /* Move it down by 1 byte */ + test_assert(0 == brk((char *)newbrk - 1), NULL); + + /* Access still-accessible memory */ + test_assert('a' == *((char *)newbrk - 1), NULL); + *((char *)newbrk - 2) = 'z'; + + /* Move brk to multiple addrs on same page, make sure page remains */ + test_assert(0 == brk((char *)newbrk - 1000), NULL); + test_assert('z' == *((char *)newbrk - 2), NULL); + test_assert(0 == brk((char *)oldbrk + 1), NULL); + test_assert('z' == *((char *)newbrk - 2), NULL); + test_assert(0 == brk((char *)oldbrk + 1000), NULL); + test_assert('a' == *((char *)newbrk - 1), NULL); + + return 0; +} + +static int test_munmap(void) +{ + char *addr, *middle; + + printf("Testing munmap()\n"); + + /* Map lots of areas. We're kind of lazy, so for now they're all anonymous + */ + test_assert( + MAP_FAILED != (addr = mmap(NULL, PAGE_SIZE * 20, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0)), + NULL); + + *(addr + PAGE_SIZE * 8) = '^'; + *(addr + PAGE_SIZE * 12) = '$'; + + /* Make sure TLB / page tables are cleared on unmap */ + assert_fault(*addr = 'a'; munmap(addr, PAGE_SIZE); char foo = *addr;, ""); + assert_fault(*addr = 'a'; munmap(addr, PAGE_SIZE * 20); char foo = *addr; + , ""); + assert_fault(*(addr + PAGE_SIZE * 10) = 'a'; + munmap(addr + PAGE_SIZE * 10, PAGE_SIZE * 5); + char foo = *(addr + PAGE_SIZE * 10);, ""); + + /* Overwrite middle of area (implicit unmap) */ + test_assert( + MAP_FAILED != (middle = mmap(addr + PAGE_SIZE * 10, PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON | MAP_FIXED, -1, 0)), + NULL); + + /* Make sure we overwrote the middle but not the whole thing */ + test_assert('\0' == *middle, NULL); + assert_nofault(*middle = 'a', ""); + test_assert('a' == *middle, NULL); + + test_assert('\0' == *(addr + PAGE_SIZE * 9), NULL); + assert_nofault(*(addr + PAGE_SIZE * 9) = 'a', ""); + test_assert('\0' == *(addr + PAGE_SIZE * 9), NULL); + + test_assert('\0' == *(addr + PAGE_SIZE * 11), NULL); + assert_nofault(*(addr + PAGE_SIZE * 11) = 'a', ""); + test_assert('\0' == *(addr + PAGE_SIZE * 11), NULL); + + test_assert('\0' == *addr, NULL); + test_assert('\0' == *(addr + PAGE_SIZE * 20 - 1), NULL); + + /* Make sure the offsets are appropriate */ + test_assert('^' == *(addr + PAGE_SIZE * 8), NULL); + test_assert('$' == *(addr + PAGE_SIZE * 12), NULL); + + /* Unmap a weird overlapping region */ + test_assert(0 == munmap(addr + PAGE_SIZE * 9, PAGE_SIZE * 3), NULL); + + /* Make sure everything's gone */ + assert_fault(char foo = *(addr + PAGE_SIZE * 9), ""); + assert_fault(char foo = *(addr + PAGE_SIZE * 10), ""); + assert_fault(char foo = *(addr + PAGE_SIZE * 12 - 1), ""); + + /* Make sure offsets are still correct */ + test_assert('^' == *(addr + PAGE_SIZE * 8), NULL); + test_assert('$' == *(addr + PAGE_SIZE * 12), NULL); + + /* Unmap nothing at all */ + test_assert(0 == munmap(addr + PAGE_SIZE * 10, PAGE_SIZE), NULL); + test_assert(0 == munmap(addr + PAGE_SIZE * 9, PAGE_SIZE * 3), NULL); + + /* Unmap almost the whole (remaining) thing */ + test_assert(0 == munmap(addr + PAGE_SIZE, PAGE_SIZE * 19), NULL); + + /* Make sure the beginning's still there */ + test_assert('\0' == *addr, NULL); + + /* Finish up, make sure everything's gone */ + test_assert(0 == munmap(addr, PAGE_SIZE * 15), NULL); + assert_fault(char foo = *(addr + PAGE_SIZE), ""); + assert_fault(char foo = *(addr), ""); + assert_fault(char foo = *(addr + PAGE_SIZE * 20 - 1), ""); + + return 0; +} + +static int test_start_brk(void) +{ + printf("Testing using brk() near starting brk\n"); + test_assert(bss_end == sbrk(0), "brk should not have moved yet"); + test_assert(!PAGE_ALIGNED(bss_end) && !PAGE_ALIGNED((char *)bss_end + 1), + "starting brk is page aligned; test is too easy..."); + + /* Up to next page boundary should already be accessible (end of bss) */ + char *oldbrk = PAGE_ALIGN_UP(bss_end); + test_assert('\0' == *(oldbrk - 1), NULL); + *(oldbrk - 1) = 'a'; + assert_fault(char foo = *oldbrk, ""); + + /* Move brk up to next page boundary */ + test_assert(0 == brk(oldbrk), NULL); + test_assert('a' == *(oldbrk - 1), NULL); + *(oldbrk - 1) = 'b'; + assert_fault(char foo = *oldbrk, ""); + assert_fault(char foo = *(oldbrk + PAGE_SIZE), ""); + + /* Try to move before starting brk */ + test_assert(0 != brk((char *)bss_end - 1), NULL); + test_assert(0 != brk(PAGE_ALIGN_DOWN(bss_end)), NULL); + + /* Move it up another page */ + char *newbrk = oldbrk + PAGE_SIZE; + test_assert(0 == brk(newbrk), NULL); + + /* Make sure everything accessible (read/write) */ + test_assert('b' == *(oldbrk - 1), NULL); + test_assert('\0' == *oldbrk, NULL); + test_assert('\0' == *(newbrk - 1), NULL); + *oldbrk = 'z'; + *(newbrk - 1) = 'y'; + assert_fault(char foo = *newbrk, ""); + assert_fault(char foo = *(newbrk + PAGE_SIZE), ""); + + /* Try to move before starting brk */ + test_assert(0 != brk((char *)bss_end - 1), NULL); + test_assert(0 != brk(PAGE_ALIGN_DOWN(bss_end)), NULL); + + /* Move back to starting brk */ + test_assert(0 == brk((char *)bss_end + 1), NULL); + /* Make sure region is gone */ + test_assert('b' == *(oldbrk - 1), NULL); + assert_fault(char foo = *oldbrk, ""); + assert_fault(char foo = *newbrk, ""); + + /* Move it up, make sure we have new clean region */ + test_assert(0 == brk(oldbrk + 1), NULL); + /* This behavior is undefined, this represents how it + * works on Linux but these need not pass + */ + /*test_assert('\0' == *oldbrk, NULL); + test_assert('\0' == *(newbrk - 1), NULL);*/ + assert_fault(char foo = *newbrk, ""); + + /* Move back and finish */ + test_assert(0 == brk(bss_end), NULL); + test_assert('b' == *(oldbrk - 1), NULL); + + return 0; +} + +static int test_brk_mmap(void) +{ + printf("Testing interactions of brk() and mmap()\n"); + test_assert(bss_end == sbrk(0), "brk should not have moved yet"); + char *oldbrk = PAGE_ALIGN_UP(bss_end); + + /* Put a mapping in the way */ + test_assert(MAP_FAILED != mmap(oldbrk, PAGE_SIZE * 2, PROT_READ, + MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0), + NULL); + /* Mapping is there */ + test_assert('\0' == *oldbrk, NULL); + test_assert('\0' == *(oldbrk - 1), NULL); + + /* Moving brk without getting area is fine */ + test_assert(0 == brk(oldbrk), NULL); + test_assert('\0' == *oldbrk, NULL); + test_assert('\0' == *(oldbrk - 1), NULL); + test_assert(0 == brk((char *)bss_end + 1), NULL); + test_assert('\0' == *oldbrk, NULL); + test_assert('\0' == *(oldbrk - 1), NULL); + + /* But can't move it up at all */ + test_assert(0 != brk(oldbrk + 1), NULL); + test_assert(0 != brk(oldbrk + PAGE_SIZE), NULL); + test_assert(0 != brk(oldbrk + PAGE_SIZE * 2), NULL); + test_assert(0 != brk(oldbrk + PAGE_SIZE * 3), NULL); + + /* Make it smaller */ + test_assert(0 == munmap(oldbrk, PAGE_SIZE), NULL); + /* Region inaccessible */ + assert_fault(char foo = *oldbrk, ""); + assert_fault(char foo = *(oldbrk + PAGE_SIZE - 1), ""); + + /* Expand brk accordingly */ + test_assert(0 == brk(oldbrk + PAGE_SIZE), NULL); + test_assert('\0' == *oldbrk, NULL); + test_assert('\0' == *(oldbrk + PAGE_SIZE - 1), NULL); + *oldbrk = 'a'; + + /* Can't go too far */ + test_assert(0 != brk(oldbrk + PAGE_SIZE + 1), NULL); + test_assert(0 != brk(oldbrk + PAGE_SIZE * 2), NULL); + test_assert(0 != brk(oldbrk + PAGE_SIZE * 3), NULL); + + return 0; +} + +static int test_mmap_fill(void) +{ + printf("Testing filling up virtual address space\n"); + char *hi, *lo, *addr; + /* map something and remove it to find out how high we can go */ + test_assert( + MAP_FAILED != (hi = mmap(NULL, 1, 0, MAP_ANON | MAP_PRIVATE, -1, 0)), + NULL); + test_assert(0 == munmap(hi, 1), NULL); + hi += PAGE_SIZE; + + test_assert(bss_end == sbrk(0), NULL); + lo = PAGE_ALIGN_UP(bss_end); + + /* Fill this up with 2 mappings */ +#define MID_ADDR ((char *)0x80000000) + if (MID_ADDR > lo) + { + test_assert( + MID_ADDR == mmap(NULL, + (size_t)((uintptr_t)hi - (uintptr_t)MID_ADDR), 0, + MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + } + if (MID_ADDR < hi) + { + test_assert( + lo == mmap(NULL, (size_t)((uintptr_t)MID_ADDR - (uintptr_t)lo), 0, + MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + } + + /* mmap file below stack */ + test_assert(MAP_FAILED != (addr = mmap(NULL, 1, PROT_READ, + MAP_ANON | MAP_PRIVATE, -1, 0)), + NULL); + test_assert((uintptr_t)addr < (uintptr_t)&addr, NULL); + test_assert('\0' == *addr, NULL); + /* mmap fixed on top of it */ + test_assert(MAP_FAILED != mmap(addr, 1, PROT_READ, + MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + test_assert('\0' == *addr, NULL); + + /* Try something too big */ + test_assert(MAP_FAILED == + mmap(NULL, (size_t)addr, 0, MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + + /* Fill up the entire remaining space */ +#ifdef old + test_assert( + MAP_FAILED != mmap(NULL, + (size_t)((uintptr_t)addr - (uintptr_t)USER_MEM_LOW), + 0, MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); +#else + // all space beyond break is already taken +#endif + +#ifdef old + /* No space left */ + test_assert(MAP_FAILED == mmap(NULL, 1, 0, MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + + /* Make some space, we should fill it */ + test_assert(0 == munmap(addr, 1), NULL); + test_assert(addr == mmap(NULL, 1, PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + test_assert('\0' == *addr, NULL); + + test_assert(MAP_FAILED == mmap(NULL, 1, 0, MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); +#endif + + /* Clean out some more space */ + test_assert(0 == munmap(MID_ADDR - PAGE_SIZE, PAGE_SIZE * 2), NULL); + test_assert(MID_ADDR - PAGE_SIZE == mmap(NULL, PAGE_SIZE * 2, PROT_READ, + MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + test_assert('\0' == *MID_ADDR, NULL); + test_assert('\0' == *(MID_ADDR - PAGE_SIZE), NULL); + test_assert('\0' == *(MID_ADDR + PAGE_SIZE - 1), NULL); + + /* Cut into pieces, access each of them */ + char *p; + for (p = lo + PAGE_SIZE; p < lo + PAGE_SIZE * 20; p += PAGE_SIZE * 2) + { + test_assert( + MAP_FAILED != mmap(p, 1, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0), + NULL); + test_assert('\0' == *p, NULL); + *p = 'a'; + assert_fault(char foo = *(p + PAGE_SIZE), ""); + } + + // there still should be a few pages available in low memory + test_assert(MAP_FAILED != mmap(NULL, 1, 0, MAP_ANON | MAP_PRIVATE, -1, 0), + NULL); + + /* Try brk too */ + test_assert(0 == brk(lo), NULL); + test_assert(0 != brk(lo + 1), NULL); + + /* Clean it all up */ + test_assert(0 == munmap(lo, (size_t)((uintptr_t)hi - (uintptr_t)lo)), NULL); + return 0; +} + +static int test_mmap_repeat(void) +{ +#define MMAP_REPEAT_FILE "mmaprepeattest" +#define REPEAT_STR "FooFooFoo" + + int fd, i; + char *addrs[10]; + printf("Testing repeated mmap() of same file\n"); + + /* Set up test file */ + test_assert(-1 != (fd = open(MMAP_REPEAT_FILE, O_RDWR | O_CREAT, 0)), NULL); + test_assert(10 == write(fd, REPEAT_STR, 10), NULL); + test_assert(0 == unlink(MMAP_REPEAT_FILE), NULL); + + /* map it private many times */ + for (i = 0; i < 10; i++) + { + test_assert(MAP_FAILED != (addrs[i] = mmap(NULL, PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd, 0)), + NULL); + test_assert(!strcmp(addrs[i], REPEAT_STR), NULL); + } + /* Make sure changes don't propagate */ + *addrs[0] = 'Z'; + *(addrs[0] + PAGE_SIZE - 1) = 'Q'; + for (i = 1; i < 10; i++) + { + test_assert(!strcmp(addrs[i], REPEAT_STR), NULL); + test_assert('\0' == *(addrs[i] + PAGE_SIZE - 1), NULL); + } + + /* map it shared many times */ + for (i = 0; i < 10; i++) + { + test_assert(MAP_FAILED != (addrs[i] = mmap(NULL, PAGE_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)), + NULL); + test_assert(!strcmp(addrs[i], REPEAT_STR), NULL); + } + /* Make sure changes propagate */ + *addrs[3] = 'Z'; + *(addrs[5] + PAGE_SIZE - 1) = 'Q'; + for (i = 0; i < 10; i++) + { + test_assert('Z' == *addrs[i], NULL); + test_assert('Q' == *(addrs[i] + PAGE_SIZE - 1), NULL); + } + + return 0; +} + +static int test_mmap_beyond(void) +{ + /* <insert evil laughter here> */ +#define MMAP_BEYOND_FILE "mmapbeyondtest" +#define BEYOND_STR "FOOBAR!" + + int fd; + char *addr, *addr2; + int status; + + printf("Testing mmap() beyond end of backing object\n"); + + /* Set up test file */ + test_assert(-1 != (fd = open(MMAP_BEYOND_FILE, O_RDWR | O_CREAT, 0)), NULL); + test_assert(8 == write(fd, BEYOND_STR, 8), NULL); + test_assert(0 == unlink(MMAP_BEYOND_FILE), NULL); + + /* Set up test mmap */ + test_assert( + MAP_FAILED != (addr = mmap(NULL, PAGE_SIZE * 10, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)), + NULL); + /* make sure it's there */ + test_assert(!strcmp(addr, BEYOND_STR), NULL); + + /* Do it again, but with private mapping. */ + test_assert(MAP_FAILED != + (addr2 = mmap(NULL, PAGE_SIZE * 10, PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd, 0)), + NULL); + /* make sure it's there */ + test_assert(!strcmp(addr2, BEYOND_STR), NULL); + *addr2 = 'a'; + + /* Can't go too far on either */ + assert_fault(char foo = *(addr + PAGE_SIZE), ""); + assert_fault(char foo = *(addr + PAGE_SIZE * 5), ""); + assert_fault(*((char *)addr + PAGE_SIZE * 5) = 'a', ""); + + assert_fault(char foo = *(addr2 + PAGE_SIZE), ""); + assert_fault(char foo = *(addr2 + PAGE_SIZE * 5), ""); + assert_fault(*(addr2 + PAGE_SIZE * 5) = 'a', ""); + + /* Write more to it */ + test_assert(PAGE_SIZE * 3 == lseek(fd, PAGE_SIZE * 3, SEEK_SET), NULL); + test_assert(8 == write(fd, BEYOND_STR, 8), NULL); + + /* Can go up to new location */ + test_assert(!strcmp(addr, BEYOND_STR), NULL); + test_assert('\0' == *(addr + PAGE_SIZE), NULL); + test_assert('\0' == *(addr + PAGE_SIZE * 2), NULL); + test_assert(!strcmp(addr + PAGE_SIZE * 3, BEYOND_STR), NULL); + + test_assert('a' == *addr2, NULL); + test_assert('\0' == *(addr2 + PAGE_SIZE), NULL); + test_assert('\0' == *(addr2 + PAGE_SIZE * 2), NULL); + + /* Can't go beyond it */ + assert_fault(char foo = *(addr + PAGE_SIZE * 4), ""); + assert_fault(char foo = *(addr + PAGE_SIZE * 8), ""); + assert_fault(*(addr + PAGE_SIZE * 5) = 'a', ""); + + assert_fault(char foo = *(addr2 + PAGE_SIZE * 4), ""); + assert_fault(char foo = *(addr2 + PAGE_SIZE * 8), ""); + assert_fault(*(addr2 + PAGE_SIZE * 5) = 'a', ""); + + return 0; +} + +/* TODO Figure out a way to not have these be repeated. */ +/* Copied from vfstest. Linking stuff prevents use of the same file. */ +static void make_rootdir(void) +{ + int err; + + root_dir[0] = '\0'; + do + { + snprintf(root_dir, sizeof(root_dir), "memtest-%d", rand()); + err = mkdir(root_dir, 0777); + if (err && errno != EEXIST) + { + printf("Failed to make test root directory: %s\n", strerror(errno)); + exit(errno); + } + } while (err != 0); + printf("Created test root directory: ./%s\n", root_dir); +} + +/* Copied from vfstest. Linking stuff prevents use of the same file. */ +static int getdent(const char *dir, dirent_t *dirent) +{ + int ret, fd = -1; + + if (0 > (fd = open(dir, O_RDONLY, 0777))) + { + return -1; + } + + ret = 1; + while (ret != 0) + { + if (0 > (ret = getdents(fd, dirent, sizeof(*dirent)))) + { + return -1; + } + if (0 != strcmp(".", dirent->d_name) && + 0 != strcmp("..", dirent->d_name)) + { + close(fd); + return 1; + } + } + + close(fd); + return 0; +} + +/* Copied from vfstest. Linking stuff prevents use of the same file. */ +static int removeall(const char *dir) +{ + int ret, fd = -1; + dirent_t dirent; + stat_t status; + + if (0 > chdir(dir)) + { + goto error; + } + + ret = 1; + while (ret != 0) + { + if (0 > (ret = getdent(".", &dirent))) + { + goto error; + } + if (0 == ret) + { + break; + } + + if (0 > stat(dirent.d_name, &status)) + { + goto error; + } + + if (S_ISDIR(status.st_mode)) + { + if (0 > removeall(dirent.d_name)) + { + goto error; + } + } + else + { + if (0 > unlink(dirent.d_name)) + { + goto error; + } + } + } + + if (0 > chdir("..")) + { + return errno; + } + + if (0 > rmdir(dir)) + { + return errno; + } + + close(fd); + return 0; + +error: + if (0 <= fd) + { + close(fd); + } + + return errno; +} + +/* Copied from vfstest. Linking stuff prevents use of the same file. */ +static void destroy_rootdir(void) +{ + if (0 != removeall(root_dir)) + { + fprintf(stderr, "ERROR: could not remove testing root %s: %s\n", + root_dir, strerror(errno)); + exit(-1); + } + printf("Removed test root directory: ./%s\n", root_dir); +} + +int main(int argc, char **argv) +{ + if (argc != 1) + { + fprintf(stderr, "USAGE: memtest\n"); + return 1; + } + int status; + + /* Make sure we found out if anything segfaults that shouldn't */ +#define childtest(fun) \ + do \ + { \ + test_fork_begin() { return fun(); } \ + test_fork_end(&status); \ + test_assert(EFAULT != status, "Test process shouldn't segfault!"); \ + test_assert(0 == status, "Test process returned error"); \ + } while (0) + + /* printf("Linker magic: start 0x%p, text end 0x%p, data end 0x%p, bss end + 0x%p\n", text_start, text_end, data_end, bss_end); */ + test_init(); + make_rootdir(); + syscall_success(chdir(root_dir)); + childtest(test_overflow); + childtest(test_mmap_bounds); + childtest(test_brk_bounds); + childtest(test_munmap); + childtest(test_start_brk); + childtest(test_brk_mmap); + // childtest(test_mmap_fill); // [+] TODO UPDATE FOR 64 BIT + childtest(test_mmap_repeat); + childtest(test_mmap_beyond); + syscall_success(chdir("..")); + destroy_rootdir(); + test_fini(); + + return 0; +} diff --git a/user/usr/bin/tests/mm.h b/user/usr/bin/tests/mm.h new file mode 120000 index 0000000..4859984 --- /dev/null +++ b/user/usr/bin/tests/mm.h @@ -0,0 +1 @@ +../../../../kernel/include/mm/mm.h
\ No newline at end of file diff --git a/user/usr/bin/tests/page.h b/user/usr/bin/tests/page.h new file mode 120000 index 0000000..9c8100b --- /dev/null +++ b/user/usr/bin/tests/page.h @@ -0,0 +1 @@ +../../../../kernel/include/mm/page.h
\ No newline at end of file diff --git a/user/usr/bin/tests/pipetest.c b/user/usr/bin/tests/pipetest.c new file mode 100644 index 0000000..e588bd7 --- /dev/null +++ b/user/usr/bin/tests/pipetest.c @@ -0,0 +1,191 @@ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define IMAX 256 +#define JMAX 16 +#define KMAX 16 +#define ISTEP (JMAX * KMAX) + +#define WAIT \ + do \ + { \ + while (waitpid(-1, &status, 0) > 0) \ + { \ + if (status == 0) \ + { \ + passed++; \ + } \ + else \ + { \ + failed++; \ + } \ + } \ + } while (0) + +int main(int argc, char **argv) +{ + int passed = 0; + int failed = 0; + + int status; + int pipefd[2]; + + /* First test: normal operation. + * The writer writes a specific sequence of 64K + * bytes into the pipe, and the reader expects + * the same bytes out. + */ + printf("Testing normal operation...\n"); + int ret = pipe(pipefd); + + if (ret < 0) + { + fprintf(stderr, "pipe: %s\n", strerror(errno)); + exit(1); + } + + int i, j, k; + unsigned char buf[KMAX]; + + if (!fork()) + { + close(pipefd[0]); + for (i = 0; i < IMAX; ++i) + { + for (j = 0; j < JMAX; ++j) + { + for (k = 0; k < KMAX; ++k) + { + buf[k] = i ^ (j * KMAX + k); + } + if (write(pipefd[1], buf, KMAX) < 0) + { + printf("Write to pipe failed\n"); + exit(1); + } + } + } + exit(0); + } + + if (!fork()) + { + close(pipefd[1]); + for (i = 0; i < IMAX; ++i) + { + for (j = 0; j < JMAX; ++j) + { + if (read(pipefd[0], buf, KMAX) == 0) + { + printf("Unexpected end of pipe\n"); + exit(1); + } + for (k = 0; k < KMAX; ++k) + { + if (buf[k] != (i ^ (j * KMAX + k))) + { + printf("Byte %d incorrect (expected %2x, got %2x)\n", + i * ISTEP + j * KMAX + k, (i ^ (j * KMAX + k)), + buf[k]); + exit(1); + } + } + } + } + exit(0); + } + + close(pipefd[0]); + close(pipefd[1]); + + WAIT; + + /* Second test: broken pipe. + * All readers quit immediately. The writer should get EPIPE. + */ + printf("Testing with no readers...\n"); + ret = pipe(pipefd); + + if (ret < 0) + { + fprintf(stderr, "pipe: %s\n", strerror(errno)); + exit(1); + } + + if (!fork()) + { + close(pipefd[0]); + for (i = 0; i < IMAX; ++i) + { + for (j = 0; j < JMAX; ++j) + { + for (k = 0; k < KMAX; ++k) + { + buf[k] = i ^ (j * KMAX + k); + } + if (write(pipefd[1], buf, KMAX) < 0) + { + if (errno == EPIPE) + { + exit(0); + } + else + { + printf("Write to pipe failed\n"); + exit(1); + } + } + } + } + exit(1); + } + + close(pipefd[0]); + close(pipefd[1]); + + WAIT; + + /* Third test: writers quit. + * The readers should get no bytes, and this program + * should not block in the kernel forever. + */ + printf("Testing with no writers...\n"); + ret = pipe(pipefd); + + if (ret < 0) + { + fprintf(stderr, "pipe: %s\n", strerror(errno)); + exit(1); + } + + if (!fork()) + { + close(pipefd[1]); + if (read(pipefd[0], buf, KMAX) == 0) + { + exit(0); + } + exit(1); + } + + if (!fork()) + { + close(pipefd[1]); + if (read(pipefd[0], buf, KMAX) == 0) + { + exit(0); + } + exit(1); + } + + close(pipefd[0]); + close(pipefd[1]); + + WAIT; + + printf("%d passed. %d failed.\n", passed, failed); + return 0; +} diff --git a/user/usr/bin/tests/prime.c b/user/usr/bin/tests/prime.c new file mode 100644 index 0000000..2e00bd1 --- /dev/null +++ b/user/usr/bin/tests/prime.c @@ -0,0 +1,107 @@ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +typedef uint64_t bitmask_word_t; +#define BITMASK_WORD_BITWIDTH 64 +#define BITMASK_WORD_BITWIDTH_LOG2 6 +#define BITMASK_IDX(x) ((x) >> BITMASK_WORD_BITWIDTH_LOG2) +#define BITMASK_POS(x) ((x) & ~(~0UL << BITMASK_WORD_BITWIDTH_LOG2)) + +// 7 6 5 4 3 2 1 0 | 15 14 13 12 11 10 9 8 +#define BITMASK_POS_MASK(x) (1UL << BITMASK_POS(x)) + +#define BITMASK_MAX_IDX(x) (BITMASK_IDX((x)-1UL) + 1UL) +#define BITMASK_SIZE(x) (sizeof(bitmask_word_t) * BITMASK_MAX_IDX(x)) +#define GET_BIT(bitmask, x) \ + (((bitmask_word_t *)bitmask)[BITMASK_IDX(x)] & BITMASK_POS_MASK(x)) +#define SET_BIT(bitmask, x) \ + (((bitmask_word_t *)bitmask)[BITMASK_IDX(x)] |= BITMASK_POS_MASK(x)) +#define UNSET_BIT(bitmask, x) \ + (((bitmask_word_t *)bitmask)[BITMASK_IDX(x)] &= ~BITMASK_POS_MASK(x)) + +inline uint64_t *find_ne64(const uint64_t not, const uint64_t *start, + size_t count) +{ + uint64_t *ret; + __asm__ volatile( + "cld; repe; scasq; xor %%rax, %%rax; setz %%al; add %%rax, %%rcx;" + : "=D"(ret), "=c"(count) + : "A"(not), "D"(start), "c"(count) + : "cc"); + return count ? (ret - 1) : NULL; +} + +size_t next_set_bit(const bitmask_word_t *bitmask, size_t start, + size_t max_idx) +{ + size_t idx = BITMASK_IDX(start); + bitmask_word_t copy = (bitmask[idx++] >> BITMASK_POS(start)) - 1; + if (copy) + { + return start + __builtin_ctzl(copy); + } + if (idx < max_idx) + { + uint64_t *end = find_ne64(0, bitmask + idx, max_idx - idx); + if (end) + return (((end - bitmask) << BITMASK_WORD_BITWIDTH_LOG2) + + (unsigned)__builtin_ctzl(*end)); + } + return ~0UL; +} + +long compute_largest_prime(long n) +{ + if (n <= 1) + return -1; + if (n <= 3) + return n; + n = (n - 1) >> 1; + + bitmask_word_t *bitmask = + malloc(BITMASK_SIZE(n)); // GET_BIT(i) --> is 2 * i + 1 prime? + size_t max_idx = BITMASK_MAX_IDX(n); + memset(bitmask, 0xff, BITMASK_SIZE(n)); + + UNSET_BIT(bitmask, 0); + size_t prime_idx = 1; + + while (1) + { + long increment = (prime_idx << 1) | 1; // prime_idx * 2 + 1 + for (long multiple = prime_idx + increment; multiple <= n; + multiple += increment) + { + UNSET_BIT(bitmask, multiple); + } + // size_t next_prime_idx = prime_idx + 1; + // while (!GET_BIT(bitmask, next_prime_idx) && next_prime_idx <= + // n) + // next_prime_idx++; + + size_t next_prime_idx = next_set_bit(bitmask, prime_idx, max_idx); + + if (next_prime_idx > (size_t)n) + break; + prime_idx = next_prime_idx; + } + + free(bitmask); + return (prime_idx << 1) | 1; +} + +int main(int argc, char *argv[], char *envp[]) +{ + if (argc <= 1) + { + fprintf(stderr, + "USAGE: \"prime <n>\" to compute the largest prime <= n\n"); + exit(1); + } + long n = strtol(argv[1], NULL, 0); + fprintf(stdout, "%ld\n", compute_largest_prime(n)); + return 0; +} diff --git a/user/usr/bin/tests/s5fstest.c b/user/usr/bin/tests/s5fstest.c new file mode 100644 index 0000000..e2441ac --- /dev/null +++ b/user/usr/bin/tests/s5fstest.c @@ -0,0 +1,332 @@ +// +// Tests some edge cases of s5fs +// Ported to user mode by twd in 7/2018 +// + +#ifdef __KERNEL__ + +#include "config.h" +#include "errno.h" +#include "globals.h" +#include "limits.h" + +#include "test/usertest.h" + +#include "util/debug.h" +#include "util/printf.h" +#include "util/string.h" + +#include "fs/fcntl.h" +#include "fs/lseek.h" +#include "fs/s5fs/s5fs.h" +#include "fs/vfs_syscall.h" + +#else + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <test/test.h> +#include <unistd.h> + +#define do_write write +#define do_read read +#define do_lseek lseek +#define do_open(x, y) open((x), (y), 0777) +#define do_link link +#define do_unlink unlink +#define do_mknod mknod +#define do_close close +#define do_mkdir(x) mkdir((x), 0777) +#define do_chdir chdir +#define do_rmdir rmdir + +#define S5_BLOCK_SIZE 4096 +#define S5_MAX_FILE_BLOCKS 1052 + +#define KASSERT(x) test_assert(x, NULL) +#define dbg(code, fmt, args...) printf(fmt, ##args) + +#endif + +#define BUFSIZE 256 +#define BIG_BUFSIZE 2056 + +#define S5_MAX_FILE_SIZE S5_BLOCK_SIZE *S5_MAX_FILE_BLOCKS + +static void get_file_name(char *buf, size_t sz, int fileno) +{ + snprintf(buf, sz, "file%d", fileno); +} + +// Write to a fail forever until it is either filled up or we get an error. +static int write_until_fail(int fd) +{ + size_t total_written = 0; + char buf[BIG_BUFSIZE] = {42}; + while (total_written < S5_MAX_FILE_SIZE) + { + int res = do_write(fd, buf, BIG_BUFSIZE); + if (res < 0) + { + return res; + } + total_written += res; + } + KASSERT(total_written == S5_MAX_FILE_SIZE); + KASSERT(do_lseek(fd, 0, SEEK_END) == S5_MAX_FILE_SIZE); + + return 0; +} + +// Read n bytes from the file, and check they're all 0 +// We do this in increments of big_bufsize because we might want to read +// like a million bytes from the file +static int is_first_n_bytes_zero(int fd, int n) +{ + int total_read = 0; + while (total_read < n) + { + int amt_to_read = MIN(BIG_BUFSIZE, n - total_read); + char buf[BIG_BUFSIZE] = {1}; + int res = do_read(fd, buf, amt_to_read); + if (res != amt_to_read) + { + dbg(DBG_TESTFAIL, "do_read result was %d\n", res); + return 0; + // KASSERT("Could not read from file" && 0); + } + total_read += res; + + // Check everything that we read is indeed 0 + for (int i = 0; i < amt_to_read; ++i) + { + if (buf[i] != 0) + { + dbg(DBG_TESTFAIL, "buf contains char %d\n", (int)buf[i]); + return 0; + } + } + } + + return 1; +} + +static void test_running_out_of_inodes() +{ + // Open a ton of files until we get an error + int res = -1; + int fileno = 0; + char filename[BUFSIZE]; + + // open files til we get an error + while (1) + { + get_file_name(filename, BUFSIZE, fileno); + res = do_open(filename, O_RDONLY | O_CREAT); + if (res >= 0) + { + fileno++; + test_assert(do_close(res) == 0, "couldn't close"); + } + else + { + break; + } + } +#ifdef __KERNEL__ + test_assert(res == -ENOSPC, "Did not get ENOSPC error"); +#else + test_assert(errno == ENOSPC, "Did not get ENOSPC error"); +#endif + + // make sure mkdir fails now that we're out of inodes + test_assert(do_mkdir("directory") < 0, "do_mkdir worked!?"); +#ifdef __KERNEL__ + test_assert(res == -ENOSPC, "unexpected error"); +#else + test_assert(errno == ENOSPC, "unexpected error"); +#endif + +#ifdef __KERNEL__ + test_assert(do_mknod("nod", S_IFCHR, 123) != 0, "mknod worked!?"); + test_assert(res == -ENOSPC, "wrong error code"); +#endif + + // the last file we tried to open failed + fileno--; + + do + { + get_file_name(filename, BUFSIZE, fileno); + res = do_unlink(filename); + test_assert(res == 0, "couldnt unlink"); + fileno--; + } while (fileno >= 0); + + // Now we've freed all the files, try to create another file + int fd = do_open("file", O_RDONLY | O_CREAT); + test_assert(fd >= 0, "Still cannot create files"); + test_assert(do_close(fd) == 0, "Could not do_close fd"); + test_assert(do_unlink("file") == 0, "Could not remove file"); +} + +static void test_filling_file() +{ + int res = 0; + int fd = do_open("hugefile", O_RDWR | O_CREAT); + KASSERT(fd >= 0); + + res = write_until_fail(fd); + test_assert(res == 0, "Did not write to entire file"); + + // make sure all other writes are unsuccessful/dont complete + char buf[BIG_BUFSIZE] = {0}; + res = do_write(fd, buf, sizeof(buf)); + test_assert(res < 0, "Able to write although the file is full"); +#ifdef __KERNEL__ + test_assert(res == -EFBIG || res == -EINVAL, "Wrong error code"); +#else + test_assert(errno == EFBIG || errno == EINVAL, "Wrong error code"); +#endif + + test_assert(do_close(fd) == 0, "couldnt close hugefile"); + test_assert(do_unlink("hugefile") == 0, "couldnt unlink hugefile"); +} + +// Fill up the disk. Apparently to do this, we should need to fill up one +// entire file, then start to fill up another. We should eventually get +// the ENOSPC error +static void test_running_out_of_blocks() +{ + int res = 0; + + int fd1 = do_open("fullfile", O_RDWR | O_CREAT); + + res = write_until_fail(fd1); + test_assert(res == 0, "Ran out of space quicker than we expected"); + + int fd2 = do_open("partiallyfullfile", O_RDWR | O_CREAT); + res = write_until_fail(fd2); +#ifdef __KERNEL__ + test_assert(res == -ENOSPC, "Did not get nospc error"); +#else + test_assert(errno == ENOSPC, "Did not get nospc error"); +#endif + + test_assert(do_close(fd1) == 0, "could not close"); + test_assert(do_close(fd2) == 0, "could not close"); + + test_assert(do_unlink("fullfile") == 0, "couldnt do_unlink file"); + test_assert(do_unlink("partiallyfullfile") == 0, "couldnt do_unlink file"); +} + +// Open a new file, write to some random address in the file, +// and make sure everything up to that is all 0s. +static int test_sparseness_direct_blocks() +{ + const char *filename = "sparsefile"; + int fd = do_open(filename, O_RDWR | O_CREAT); + + // Now write to some random address that'll be in a direct block + const int addr = 10000; + const char *b = "iboros"; + const int sz = strlen(b); + + test_assert(do_lseek(fd, addr, SEEK_SET) == addr, "couldnt seek"); + test_assert(do_write(fd, b, sz) == sz, "couldnt write to random address"); + + test_assert(do_lseek(fd, 0, SEEK_SET) == 0, "couldnt seek back to begin"); + test_assert(is_first_n_bytes_zero(fd, addr) == 1, "sparseness don't work"); + + // Get rid of this file + test_assert(do_close(fd) == 0, "couldn't close file"); + test_assert(do_unlink(filename) == 0, "couldnt unlink file"); + + return 0; +} + +/* + * Fixed by twd to do a better test in 7/2018 + */ +static int test_sparseness_indirect_blocks() +{ + const char *filename = "bigsparsefile"; + int fd = do_open(filename, O_RDWR | O_CREAT); + + // first partially fill the first block of the file + char randomgarbage[4050]; + test_assert(do_write(fd, randomgarbage, 4050) == 4050, + "couldn't write to first block"); + + // Now write to some random address that'll be in an indirect block + const int addr = 1000000; + const char *b = "iboros"; + const int sz = strlen(b); + + test_assert(do_lseek(fd, addr, SEEK_SET) == addr, "couldnt seek"); + test_assert(do_write(fd, b, sz) == sz, "couldnt write to random address"); + + test_assert(do_lseek(fd, 4050, SEEK_SET) == 4050, + "couldnt seek back to begin"); + test_assert(is_first_n_bytes_zero(fd, addr - 4050) == 1, + "sparseness don't work"); + + // Get rid of this file + test_assert(do_close(fd) == 0, "couldn't close file"); + test_assert(do_unlink(filename) == 0, "couldnt unlink file"); + + return 0; +} + +#ifdef __KERNEL__ +extern uint64_t jiffies; +#endif + +static void seed_randomness() +{ +#ifdef __KERNEL__ + srand(jiffies); +#else + srand(time(NULL)); +#endif + rand(); +} + +#ifdef __KERNEL__ +int s5fstest_main() +#else + +int main() +#endif +{ + dbg(DBG_TEST, "Starting S5FS test\n"); + + test_init(); + seed_randomness(); + + KASSERT(do_mkdir("s5fstest") == 0); + KASSERT(do_chdir("s5fstest") == 0); + dbg(DBG_TEST, "Test dir initialized\n"); + + dbg(DBG_TEST, "Testing sparseness for direct blocks\n"); + test_sparseness_direct_blocks(); + dbg(DBG_TEST, "Testing sparseness for indirect blocks\n"); + test_sparseness_indirect_blocks(); + + dbg(DBG_TEST, "Testing running out of inodes\n"); + test_running_out_of_inodes(); + dbg(DBG_TEST, "Testing filling a file to max capacity\n"); + test_filling_file(); + dbg(DBG_TEST, "Testing using all available blocks on disk\n"); + test_running_out_of_blocks(); + + test_assert(do_chdir("..") == 0, ""); + test_assert(do_rmdir("s5fstest") == 0, ""); + + test_fini(); + + return 0; +} diff --git a/user/usr/bin/tests/stress.c b/user/usr/bin/tests/stress.c new file mode 100644 index 0000000..1920fcd --- /dev/null +++ b/user/usr/bin/tests/stress.c @@ -0,0 +1,476 @@ +/* + * File: stress.c + * Date: 16 November 1998 + * Acct: David Powell (dep) + * Desc: Miscellaneous VM tests + */ + +#include "mm.h" +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> + +static void check_failed(const char *cmd) +{ + (void)printf("stress: %s failed: errno %d\n", cmd, errno); + exit(1); +} + +static int myfork() +{ + int result; + + result = fork(); + if (result == -1) + { + (void)printf("Fork failed (errno=%d)\n", errno); + exit(1); + } + + sched_yield(); + return result; +} + +static void fork_test() +{ + (void)printf("-- Fork torture test start\n"); + + (void)printf( + "The final test: forking up a storm.\n" + "If this doesn't crash your kernel, " + "you might be in good shape\n"); + (void)printf("(note that this is running in the background)\n"); + if (!myfork()) + { + for (;;) + { + if (myfork()) + { + exit(0); + } + } + } +} + +static void cow_fork() +{ + int status; + int foo = 0; + + (void)printf("-- COW fork test start\n"); + + if (!myfork()) + { + /* We are in the child process, and should be accessing + * our own memory + */ + foo = 1; + exit(0); + } + + if (wait(&status) == -1) + { + (void)printf("wait failed (errno=%d)\n", errno); + exit(1); + } + + if (foo) + { + (void)printf( + "Data changed in child affected parent.\n" + "Make sure you mark writable private mappings copy-on-write.\n"); + (void)printf("Copy-on-write failed.\n"); + exit(1); + } + + (void)printf("-- COW fork test passed\n"); +} + +static void fault_test() +{ + int status; + + (void)printf("-- fault test start\n"); + + (void)printf( + "Fault test. If this hangs, check your page fault handler...\n"); + (void)printf("Do you properly kill processes that segv? "); + + if (!myfork()) + { + *(int *)0 = 0; + printf("P%d didn't fault!\n", getpid()); + exit(0); + } + + if (wait(&status) == -1) + { + (void)printf("wait failed (errno=%d)\n", errno); + exit(1); + } + if (status) + { + (void)printf("yes\n"); + ; + } + else + { + (void)printf("no\n"); + exit(1); + } + + (void)printf("-- fault test passed\n"); +} + +void mmap_test() +{ + int fd; + void *addr1, *addr2; + const char *str1 = "Coconuts!!!!\n"; + const char *str2 = "Hello there.\n"; + size_t len; + + (void)printf("-- mmap test start\n"); + + /* Create a file with some data. */ + + fd = open("/test/stress0", O_RDWR | O_CREAT, 0); + if (fd < 0) + { + check_failed("open"); + } + + /* Give us some space */ + if (1 > write(fd, "\0", 1)) + { + check_failed("write"); + } + + /* Map the file MAP_PRIVATE */ + + printf("MAP_PRIVATE test\n"); + len = strlen(str1) + 1; + addr1 = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (addr1 == MAP_FAILED) + { + check_failed("mmap"); + } + addr2 = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (addr2 == MAP_FAILED) + { + check_failed("mmap"); + } + + if (close(fd)) + { + check_failed("close"); + } + + printf("writing into %p\n", addr1); + (void)snprintf((char *)addr1, len, "%s", str1); + + /* Read from the private mapped page for good measure + * (this _shouldn't_ do anything) */ + printf("reading from privmap page\n"); + + /* Verify that the string is initially in the mapping. */ + + printf("making sure string in mapping okay\n"); + if (strcmp(str1, (char *)addr1)) + { + (void)printf("stress: write to shared mapping failed\n"); + exit(1); + } + if (strcmp(str1, (char *)addr2)) + { + (void)printf("stress: private mapping prematurely copied\n"); + exit(1); + } + + (void)snprintf((char *)addr2, len, "%s", str2); + + /* Verify that the string has been overwritten in the mapping. */ + + printf("making sure overwriting okay\n"); + if (strcmp(str2, (char *)addr2)) + { + (void)printf("stress: write to private mapping failed\n"); + exit(1); + } + + if (!strcmp(str2, (char *)addr1)) + { + (void)printf("stress: wrote through private mapping!\n"); + exit(1); + } + + printf("unmapping at %p\n", addr1); + if (munmap(addr1, len)) + { + check_failed("munmap"); + } + printf("unmapping at %p\n", addr2); + if (munmap(addr2, len)) + { + check_failed("munmap"); + } + + if (!munmap((void *)USER_MEM_HIGH, 15) || (errno != EINVAL)) + { + printf("munmap bad one fail errno=%d einval=%d\n", errno, EINVAL); + exit(1); + } + + if (!munmap(0, 0) || (errno != EINVAL)) + { + printf("munmap bad two fail errno=%d einval=%d\n", errno, EINVAL); + exit(1); + } + + if (!munmap((void *)137, 100) || (errno != EINVAL)) + { + printf("munmap bad three fail errno=%d einval=%d\n", errno, EINVAL); + exit(1); + } + + (void)printf("-- mmap test passed\n"); +} + +void null_test() +{ + int fd; + int nbytes; + char buf[256]; + + (void)printf("-- null test start\n"); + + fd = open("/dev/null", O_RDWR, 0600); + if (fd < 0) + { + check_failed("open"); + } + + (void)memset(buf, 0xCC, sizeof(buf)); + + /* Try writing to /dev/null. Should return buffer size. + */ + + nbytes = write(fd, buf, sizeof(buf)); + if (nbytes != sizeof(buf)) + { + check_failed("write"); + } + + /* Try reading from /dev/null. Should return zero. + */ + + nbytes = read(fd, buf, sizeof(buf)); + if (nbytes != 0) + { + check_failed("read"); + } + + if (close(fd)) + { + check_failed("close"); + } + + (void)printf("-- null test passed\n"); +} + +void zero_test() +{ + void *addr; + int fd; + char buf[256]; + int nbytes; + unsigned int ii; + size_t len; + unsigned long *lp; + unsigned char *cp; + + (void)printf("-- zero test start\n"); + + fd = open("/dev/zero", O_RDWR, 0600); + if (fd < 0) + { + check_failed("open"); + } + + /* Set buffer to a non-zero value, then read from /dev/zero + * and make sure that the buffer is cleared. + */ + + memset(buf, 0xCC, sizeof(buf)); + + nbytes = read(fd, buf, sizeof(buf)); + if (nbytes != sizeof(buf)) + { + check_failed("read"); + } + + for (ii = 0; ii < sizeof(buf); ii++) + { + if (buf[ii] != 0) + { + printf("read %x not zero\n", buf[ii]); + check_failed("verify read"); + } + } + + /* Map /dev/zero and make sure all pages are initially zero. + */ + + len = 8192 * 5; + + addr = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + { + check_failed("mmap"); + } + + if (close(fd)) + { + check_failed("close"); + } + + cp = (unsigned char *)addr; + for (ii = 0; ii < len; ii++, cp++) + { + if (*cp != 0) + { + check_failed("verify mmap zeros"); + } + } + + /* ... make sure writes are allowed. + */ + + lp = (unsigned long *)addr; + for (ii = 0; ii < (len / sizeof(*lp)); ii++, lp++) + { + *lp = ii; + } + + lp = (unsigned long *)addr; + for (ii = 0; ii < (len / sizeof(*lp)); ii++, lp++) + { + if (*lp != ii) + { + check_failed("verify map write"); + } + } + + if (munmap(addr, len)) + { + check_failed("munmap"); + } + + (void)printf("-- zero test passed\n"); +} + +void wait_test() +{ + int status; + + (void)printf("-- wait test start\n"); + + if (!wait(&status) || (errno != ECHILD)) + { + (void)printf( + "error: wait() didn't return an error of " + "ECHILD when no children existed!\n"); + exit(1); + } + + (void)printf("-- wait test passed\n"); +} + +void brk_test() +{ + void *oldbrk1, *oldbrk2; + const void *brk_failed = (void *)-1; + int len; + unsigned int *tmp; + unsigned int ii; + + (void)printf("-- brk test start\n"); + + /* A length which is not a page multiple, yet a multiple of 8. + */ + len = 8192 * 5 + 128; + + /* Try allocating some memory. + */ + oldbrk1 = sbrk(len); + if (oldbrk1 == brk_failed) + { + check_failed("sbrk alloc"); + } + + /* Try writing to the memory. + */ + printf("writing to memory at %p\n", oldbrk1); + tmp = (unsigned int *)oldbrk1; + for (ii = 0; ii < (len / sizeof(int)); ii++) + { + *tmp++ = ii; + } + + /* Try verifying what we wrote. + */ + printf("verifying memory\n"); + tmp = (unsigned int *)oldbrk1; + for (ii = 0; ii < (len / sizeof(int)); ii++) + { + if (*tmp++ != ii) + { + (void)printf("verify failed at 0x%lx\n", (unsigned long)tmp); + exit(1); + } + } + + /* Try freeing the memory. + */ + printf("freeing memory\n"); + oldbrk2 = sbrk(-len); + if (oldbrk2 == brk_failed) + { + check_failed("sbrk dealloc"); + } + + /* oldbrk2 should be at least "len" greater than oldbrk1. + */ + if ((unsigned long)oldbrk2 < ((unsigned long)oldbrk1 + len)) + { + (void)printf("sbrk didn't return old brk??\n"); + exit(1); + } + + (void)printf("-- brk test passed\n"); +} + +int main(int argc, char **argv) +{ + (void)printf("Congrats! You're running this executable.\n"); + (void)printf("Now let's see how you handle the tests...\n"); + + mmap_test(); + + null_test(); + zero_test(); + brk_test(); + + fault_test(); + + wait_test(); + cow_fork(); + + fork_test(); + + return 0; +} diff --git a/user/usr/bin/tests/vfstest.c b/user/usr/bin/tests/vfstest.c new file mode 100644 index 0000000..ed9ca44 --- /dev/null +++ b/user/usr/bin/tests/vfstest.c @@ -0,0 +1,1172 @@ +#ifdef __KERNEL__ + +#include "config.h" +#include "errno.h" +#include "globals.h" +#include "kernel.h" +#include "limits.h" + +#include "util/debug.h" +#include "util/printf.h" +#include "util/string.h" + +#include "proc/kthread.h" +#include "proc/proc.h" + +#include "fs/dirent.h" +#include "fs/fcntl.h" +#include "fs/lseek.h" +#include "fs/stat.h" +#include "fs/vfs_syscall.h" +#include "mm/kmalloc.h" +#include "mm/mman.h" + +#include "test/usertest.h" +#include "test/vfstest/vfstest.h" + +#undef __VM__ + +#else + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> +#include <weenix/syscall.h> + +#include <test/test.h> + +#endif + +/* Some helpful strings */ +#define LONGNAME "supercalifragilisticexpialidocious" /* Longer than NAME_LEN \ + */ + +#define TESTSTR \ + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " \ + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad " \ + "minim " \ + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " \ + "commodo " \ + "consequat. Duis aute irure dolor in reprehenderit in voluptate velit " \ + "esse cillum " \ + "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " \ + "proident, " \ + "sunt in culpa qui officia deserunt mollit anim id est laborum." + +#define SHORTSTR "Quidquid latine dictum, altum videtur" + +static char root_dir[64]; + +static int makedirs(const char *dir) +{ + int ret = 0; + char *d, *p; + + if (NULL == (d = malloc(strlen(dir) + 1))) + { + return ENOMEM; + } + strcpy(d, dir); + + p = d; + while (NULL != (p = strchr(p + 1, '/'))) + { + *p = '\0'; + if (0 != mkdir(d, 0777) && EEXIST != errno) + { + ret = errno; + goto error; + } + *p = '/'; + } + if (0 != mkdir(d, 0777) && EEXIST != errno) + { + ret = errno; + goto error; + } + +error: + free(d); + return ret; +} + +static int getdent(const char *dir, dirent_t *dirent) +{ + int ret, fd = -1; + + if (0 > (fd = open(dir, O_RDONLY, 0777))) + { + return -1; + } + + ret = 1; + while (ret != 0) + { + if (0 > (ret = getdents(fd, dirent, sizeof(*dirent)))) + { + return -1; + } + if (0 != strcmp(".", dirent->d_name) && + 0 != strcmp("..", dirent->d_name)) + { + close(fd); + return 1; + } + } + + close(fd); + return 0; +} + +static int removeall(const char *dir) +{ + int ret; + dirent_t dirent; + stat_t status; + + if (0 > chdir(dir)) + { + return errno; + } + + ret = 1; + while (ret != 0) + { + if (0 > (ret = getdent(".", &dirent))) + { + return errno; + } + if (0 == ret) + { + break; + } + + if (0 > stat(dirent.d_name, &status)) + { + return errno; + } + + if (S_ISDIR(status.st_mode)) + { + if (0 > removeall(dirent.d_name)) + { + return errno; + } + } + else + { + if (0 > unlink(dirent.d_name)) + { + return errno; + } + } + } + + if (0 > chdir("..")) + { + return errno; + } + + if (0 > rmdir(dir)) + { + return errno; + } + + return 0; +} + +static void vfstest_start(void) +{ + int err; + + root_dir[0] = '\0'; + do + { + snprintf(root_dir, sizeof(root_dir), "vfstest-%d-%d", getpid(), rand()); + err = mkdir(root_dir, 0777); + + if (errno == EEXIST) + { + break; + } + + if (err && errno != EEXIST) + { + printf("Failed to make test root directory: %s\n", strerror(errno)); + exit(errno); + } + } while (err != 0); + printf("Created test root directory: ./%s\n", root_dir); +} + +/* + * Terminates the testing environment + */ +static void vfstest_term(void) +{ + if (0 != removeall(root_dir)) + { + fprintf(stderr, "ERROR: could not remove testing root %s: %s\n", + root_dir, strerror(errno)); + exit(-1); + } + printf("Removed test root directory: ./%s\n", root_dir); +} + +#define paths_equal(p1, p2) \ + do \ + { \ + int __r; \ + stat_t __s1, __s2; \ + if (__r = makedirs(p1), !test_assert(0 == __r, "makedirs(\"%s\"): %s", \ + p1, test_errstr(__r))) \ + break; \ + if (__r = stat(p1, &__s1), !test_assert(0 == __r, "stat(\"%s\"): %s", \ + p1, test_errstr(errno))) \ + break; \ + if (__r = stat(p2, &__s2), !test_assert(0 == __r, "stat(\"%s\"): %s", \ + p2, test_errstr(errno))) \ + break; \ + test_assert(__s1.st_ino == __s2.st_ino, \ + "paths_equals(\"%s\" (ino %d), \"%s\" (ino %d))", p1, \ + __s1.st_ino, p2, __s2.st_ino); \ + } while (0); + +#define syscall_fail(expr, err) \ + (test_assert((errno = 0, -1 == (expr)), \ + "\nunexpected success, wanted %s (%d)", test_errstr(err), \ + err) \ + ? test_assert((expr, errno == err), \ + "\nexpected %s (%d)" \ + "\ngot %s (%d)", \ + test_errstr(err), err, test_errstr(errno), errno) \ + : 0) +#define syscall_success(expr) \ + test_assert(0 <= (expr), "\nunexpected error: %s (%d)", \ + test_errstr(errno), errno) + +#define create_file(file) \ + do \ + { \ + int __fd; \ + if (syscall_success(__fd = open((file), O_RDONLY | O_CREAT, 0777))) \ + { \ + syscall_success(close(__fd)); \ + } \ + } while (0); +#define read_fd(fd, size, goal) \ + do \ + { \ + char __buf[64]; \ + test_assert((ssize_t)strlen(goal) == read(fd, __buf, size), \ + "\nread unexpected number of bytes"); \ + test_assert(0 == memcmp(__buf, goal, strlen(goal)), \ + "\nread data incorrect"); \ + } while (0); +#define test_fpos(fd, exp) \ + do \ + { \ + int __g, __e = (exp); \ + syscall_success(__g = lseek(fd, 0, SEEK_CUR)); \ + test_assert((__g == __e), "fd %d fpos at %d, expected %d", fd, __g, \ + __e); \ + } while (0); + +static void vfstest_notdir(void) +{ + int fd; + stat_t s; + syscall_success(mkdir("realdir", 0)); + syscall_success(fd = open("realdir/file", O_RDWR | O_CREAT, 0)); + syscall_success(close(fd)); + syscall_success(fd = open("realdir/file2", O_RDWR | O_CREAT, 0)); + syscall_success(close(fd)); + + syscall_fail(open("realdir/file/nope", O_CREAT | O_RDWR, 0), ENOTDIR); + syscall_fail(link("realdir/file2", "realdir/file/nope"), ENOTDIR); + syscall_fail(link("realdir/file/nope", "realdir/file3"), ENOTDIR); + syscall_fail(unlink("realdir/file/nope"), ENOTDIR); + syscall_fail(rmdir("realdir/file/nope"), ENOTDIR); + syscall_fail(stat("realdir/file/nope", &s), ENOTDIR); + syscall_fail(rename("realdir/file2", "realdir/file/nope"), ENOTDIR); + syscall_fail(rename("realdir/file/nope", "realdir/file3"), ENOTDIR); + + /* Cleanup */ + syscall_success(unlink("realdir/file")); + syscall_success(unlink("realdir/file2")); + syscall_success(rmdir("realdir")); +} + +static void vfstest_stat(void) +{ + int fd; + stat_t s; + + syscall_success(mkdir("stat", 0)); + syscall_success(chdir("stat")); + + syscall_success(stat(".", &s)); + test_assert(S_ISDIR(s.st_mode), NULL); + + create_file("file"); + syscall_success(stat("file", &s)); + test_assert(S_ISREG(s.st_mode), NULL); + + /* file size is correct */ + syscall_success(fd = open("file", O_RDWR, 0)); + syscall_success(write(fd, "foobar", 6)); + syscall_success(stat("file", &s)); + test_assert(s.st_size == 6, "unexpected file size"); + syscall_success(close(fd)); + + /* error cases */ +#ifdef __VM__ + syscall_fail(stat(".", NULL), EFAULT); +#endif + syscall_fail(stat("noent", &s), ENOENT); + + syscall_success(chdir("..")); +} + +static void vfstest_mkdir(void) +{ + syscall_success(mkdir("mkdir", 0777)); + syscall_success(chdir("mkdir")); + + /* mkdir an existing file or directory */ + create_file("file"); + syscall_fail(mkdir("file", 0777), EEXIST); + syscall_success(mkdir("dir", 0777)); + syscall_fail(mkdir("dir", 0777), EEXIST); + + /* mkdir an invalid path */ + syscall_fail(mkdir(LONGNAME, 0777), ENAMETOOLONG); + syscall_fail(mkdir("file/dir", 0777), ENOTDIR); + syscall_fail(mkdir("noent/dir", 0777), ENOENT); + syscall_fail(rmdir("file/dir"), ENOTDIR); + syscall_fail(rmdir("noent/dir"), ENOENT); + syscall_fail(rmdir("noent"), ENOENT); + syscall_fail(rmdir("."), EINVAL); + syscall_fail(rmdir(".."), ENOTEMPTY); + syscall_fail(rmdir("dir/."), EINVAL); + syscall_fail(rmdir("dir/.."), ENOTEMPTY); + syscall_fail(rmdir("noent/."), ENOENT); + syscall_fail(rmdir("noent/.."), ENOENT); + + /* unlink and rmdir the inappropriate types */ + syscall_fail(rmdir("file"), ENOTDIR); + syscall_fail(unlink("dir"), EPERM); + + /* remove non-empty directory */ + create_file("dir/file"); + syscall_fail(rmdir("dir"), ENOTEMPTY); + + /* remove empty directory */ + syscall_success(unlink("dir/file")); + syscall_success(rmdir("dir")); + + syscall_success(chdir("..")); +} + +static void vfstest_chdir(void) +{ +#define CHDIR_TEST_DIR "chdir" + + stat_t ssrc, sdest, sparent, sdir; + stat_t rsrc, rdir; + + /* chdir back and forth to CHDIR_TEST_DIR */ + syscall_success(mkdir(CHDIR_TEST_DIR, 0777)); + syscall_success(stat(".", &ssrc)); + syscall_success(stat(CHDIR_TEST_DIR, &sdir)); + + test_assert(ssrc.st_ino != sdir.st_ino, NULL); + + syscall_success(chdir(CHDIR_TEST_DIR)); + syscall_success(stat(".", &sdest)); + syscall_success(stat("..", &sparent)); + + test_assert(sdest.st_ino == sdir.st_ino, NULL); + test_assert(ssrc.st_ino == sparent.st_ino, NULL); + test_assert(ssrc.st_ino != sdest.st_ino, NULL); + + syscall_success(chdir("..")); + syscall_success(stat(".", &rsrc)); + syscall_success(stat(CHDIR_TEST_DIR, &rdir)); + + test_assert(rsrc.st_ino == ssrc.st_ino, NULL); + test_assert(rdir.st_ino == sdir.st_ino, NULL); + + /* can't chdir into non-directory */ + syscall_success(chdir(CHDIR_TEST_DIR)); + create_file("file"); + syscall_fail(chdir("file"), ENOTDIR); + syscall_fail(chdir("noent"), ENOENT); + syscall_success(chdir("..")); +} + +static void vfstest_paths(void) +{ +#define PATHS_TEST_DIR "paths" + + stat_t s; + + syscall_success(mkdir(PATHS_TEST_DIR, 0777)); + syscall_success(chdir(PATHS_TEST_DIR)); + + syscall_fail(stat("", &s), EINVAL); + + paths_equal(".", "."); + paths_equal("1/2/3", "1/2/3"); + paths_equal("4/5/6", "4/5/6"); + + /* root directory */ + paths_equal("/", "/"); + paths_equal("/", "/.."); + paths_equal("/", "/../"); + paths_equal("/", "/../."); + + /* . and .. */ + paths_equal(".", "./."); + paths_equal(".", "1/.."); + paths_equal(".", "1/../"); + paths_equal(".", "1/2/../.."); + paths_equal(".", "1/2/../.."); + paths_equal(".", "1/2/3/../../.."); + paths_equal(".", "1/../1/.."); + paths_equal(".", "1/../4/.."); + paths_equal(".", "1/../1/.."); + paths_equal(".", "1/2/3/../../../4/5/6/../../.."); + paths_equal(".", "1/./2/./3/./.././.././.././4/./5/./6/./.././.././.."); + + /* extra slashes */ + paths_equal("1/2/3", "1/2/3/"); + paths_equal("1/2/3", "1//2/3"); + paths_equal("1/2/3", "1/2//3"); + paths_equal("1/2/3", "1//2//3"); + paths_equal("1/2/3", "1//2//3/"); + paths_equal("1/2/3", "1///2///3///"); + + /* strange names */ + paths_equal("-", "-"); + paths_equal(" ", " "); + paths_equal("\\", "\\"); + paths_equal("0", "0"); + + stat_t st; + + /* error cases */ + syscall_fail(stat("asdf", &st), ENOENT); + syscall_fail(stat("1/asdf", &st), ENOENT); + syscall_fail(stat("1/../asdf", &st), ENOENT); + syscall_fail(stat("1/2/asdf", &st), ENOENT); + + create_file("1/file"); + syscall_fail(open("1/file/other", O_RDONLY, 0777), ENOTDIR); + syscall_fail(open("1/file/other", O_RDONLY | O_CREAT, 0777), ENOTDIR); + + syscall_success(chdir("..")); +} + +static void vfstest_fd(void) +{ +#define FD_BUFSIZE 5 +#define BAD_FD 20 +#define HUGE_FD 9999 + + int fd1, fd2; + char buf[FD_BUFSIZE]; + struct dirent d; + + syscall_success(mkdir("fd", 0)); + syscall_success(chdir("fd")); + + /* read/write/close/getdents/dup nonexistent file descriptors */ + syscall_fail(read(BAD_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(read(HUGE_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(read(-1, buf, FD_BUFSIZE), EBADF); + + syscall_fail(write(BAD_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(write(HUGE_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(write(-1, buf, FD_BUFSIZE), EBADF); + + syscall_fail(close(BAD_FD), EBADF); + syscall_fail(close(HUGE_FD), EBADF); + syscall_fail(close(-1), EBADF); + + syscall_fail(lseek(BAD_FD, 0, SEEK_SET), EBADF); + syscall_fail(lseek(HUGE_FD, 0, SEEK_SET), EBADF); + syscall_fail(lseek(-1, 0, SEEK_SET), EBADF); + + syscall_fail(getdents(BAD_FD, &d, sizeof(d)), EBADF); + syscall_fail(getdents(HUGE_FD, &d, sizeof(d)), EBADF); + syscall_fail(getdents(-1, &d, sizeof(d)), EBADF); + + syscall_fail(dup(BAD_FD), EBADF); + syscall_fail(dup(HUGE_FD), EBADF); + syscall_fail(dup(-1), EBADF); + + syscall_fail(dup2(BAD_FD, 25), EBADF); + syscall_fail(dup2(HUGE_FD, 25), EBADF); + syscall_fail(dup2(-1, 25), EBADF); + + /* dup2 has some extra cases since it takes a second fd */ + syscall_fail(dup2(0, HUGE_FD), EBADF); + syscall_fail(dup2(0, -1), EBADF); + + /* if the fds are equal, but the first is invalid or out of the + * allowed range */ + syscall_fail(dup2(BAD_FD, BAD_FD), EBADF); + syscall_fail(dup2(HUGE_FD, HUGE_FD), EBADF); + syscall_fail(dup2(-1, -1), EBADF); + + /* dup works properly in normal usage */ + create_file("file01"); + syscall_success(fd1 = open("file01", O_RDWR, 0)); + syscall_success(fd2 = dup(fd1)); + test_assert(fd1 < fd2, "dup(%d) returned %d", fd1, fd2); + syscall_success(write(fd2, "hello", 5)); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(lseek(fd2, 0, SEEK_SET)); + test_fpos(fd1, 0); + test_fpos(fd2, 0); + read_fd(fd1, 5, "hello"); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(close(fd2)); + + /* dup2 works properly in normal usage */ + syscall_success(fd2 = dup2(fd1, 25)); + test_assert(25 == fd2, "dup2(%d, 25) returned %d", fd1, fd2); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(lseek(fd2, 0, SEEK_SET)); + test_fpos(fd1, 0); + test_fpos(fd2, 0); + syscall_success(close(fd2)); + + /* dup2-ing a file to itself works */ + syscall_success(fd2 = dup2(fd1, fd1)); + test_assert(fd1 == fd2, "dup2(%d, %d) returned %d", fd1, fd1, fd2); + + /* dup2 closes previous file */ + int fd3; + create_file("file02"); + syscall_success(fd3 = open("file02", O_RDWR, 0)); + syscall_success(fd2 = dup2(fd1, fd3)); + test_assert(fd2 == fd3, "dup2(%d, %d) returned %d", fd1, fd3, fd2); + test_fpos(fd1, 0); + test_fpos(fd2, 0); + syscall_success(lseek(fd2, 5, SEEK_SET)); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(close(fd2)); + syscall_success(close(fd1)); + + syscall_success(chdir("..")); +} + +static void vfstest_memdev(void) +{ + int res, fd; + char def = 'a'; + char buf[4096]; + + res = 1; + + memset(buf, def, sizeof(buf)); + + syscall_success(fd = open("/dev/null", O_RDWR, 0)); + syscall_success(res = write(fd, buf, sizeof(buf))); + test_assert(sizeof(buf) == res, "write of %d bytes /dev/null returned %d", + sizeof(buf), res); + syscall_success(res = read(fd, buf, sizeof(buf))); + test_assert(0 == res, "read of %d bytes /dev/null returned %d", sizeof(buf), + res); + test_assert(buf[sizeof(buf) / 2] == def, + "read from /dev/null changed buffer"); + syscall_success(close(fd)); + + memset(buf, def, sizeof(buf)); + + syscall_success(fd = open("/dev/zero", O_RDWR, 0)); + syscall_success(res = write(fd, buf, sizeof(buf))); + test_assert(sizeof(buf) == res, "write of %d bytes /dev/zero returned %d", + sizeof(buf), res); + syscall_success(res = read(fd, buf, sizeof(buf))); + test_assert(sizeof(buf) == res, "read of %d bytes /dev/zero returned %d", + sizeof(buf), res); + test_assert(buf[sizeof(buf) / 2] == 0, + "read from /dev/zero doesn't zero buffer"); + syscall_success(close(fd)); +} + +static void vfstest_write(void) +{ +#define CHUNK_SIZE 25 +#define NUM_CHUNKS 4 + int fd, i, res; + stat_t s; + const char *str = "hello world"; + + char chunk[CHUNK_SIZE]; + memcpy(chunk, str, strlen(str)); + memset(chunk + strlen(str), 0, 25 - strlen(str)); + + syscall_success(mkdir("write", 0)); + syscall_success(chdir("write")); + + create_file("file"); + syscall_success(fd = open("file", O_RDWR, 0)); + for (i = 0; i < NUM_CHUNKS * CHUNK_SIZE; i += CHUNK_SIZE) + { + syscall_success(lseek(fd, i, SEEK_SET)); + syscall_success(res = write(fd, str, strlen(str))); + test_assert((int)strlen(str) == res, "write of %d bytes returned %d", + strlen(str), res); + } + syscall_success(lseek(fd, 0, SEEK_SET)); + for (i = 0; i < NUM_CHUNKS - 1; ++i) + { + char __buf[64]; + test_assert(CHUNK_SIZE == read(fd, __buf, CHUNK_SIZE), + "\nread unexpected number of bytes"); + test_assert(0 == memcmp(__buf, chunk, CHUNK_SIZE), + "\nread data incorrect"); + } + char __buf[64]; + test_assert((int)strlen(str) == read(fd, __buf, strlen(str)), + "\nread unexpected number of bytes"); + test_assert(0 == memcmp(__buf, chunk, strlen(str)), + "\nread data incorrect"); + + const char *new_str = "testing"; + const int loc = 37; + // writing to middle of file + // make sure file size doesn't change and the write is done at the correct + // location + syscall_success(lseek(fd, loc, SEEK_SET)); + syscall_success(res = write(fd, new_str, strlen(new_str))); + test_assert((int)strlen(new_str) == res, "write of %d bytes returned %d", + strlen(new_str), res); + syscall_success(lseek(fd, loc, SEEK_SET)); + read_fd(fd, strlen(new_str), new_str); + test_assert(lseek(fd, 0, SEEK_END) == + (NUM_CHUNKS - 1) * CHUNK_SIZE + (int)strlen(str), + "file is not the right size"); + + syscall_success(close(fd)); + syscall_success(unlink("file")); + + syscall_success(chdir("..")); + syscall_success(rmdir("write")); +} + +/* These operations should run for a long time and halt when the file + * descriptor overflows. */ +static void vfstest_infinite(void) +{ + int res, fd; + char buf[4096]; + + res = 1; + syscall_success(fd = open("/dev/null", O_WRONLY, 0)); + for (int i = 0; i < 1000000; i++) + { + syscall_success(res = write(fd, buf, sizeof(buf))); + } + syscall_success(close(fd)); + + res = 1; + syscall_success(fd = open("/dev/zero", O_RDONLY, 0)); + while (0 < res) + { + syscall_success(res = read(fd, buf, sizeof(buf))); + } + syscall_success(close(fd)); +} + +/* + * Tests open(), close(), and unlink() + * - Accepts only valid combinations of flags + * - Cannot open nonexistent file without O_CREAT + * - Cannot write to readonly file + * - Cannot read from writeonly file + * - Cannot close non-existent file descriptor + * - Lowest file descriptor is always selected + * - Cannot unlink a directory + # - Cannot unlink a non-existent file + * - Cannot open a directory for writing + * - File descriptors are correctly released when a proc exits + */ +static void vfstest_open(void) +{ +#define OPEN_BUFSIZE 5 + + char buf[OPEN_BUFSIZE]; + int fd, fd2; + stat_t s; + + syscall_success(mkdir("open", 0777)); + syscall_success(chdir("open")); + + /* No invalid combinations of O_RDONLY, O_WRONLY, and O_RDWR. Since + * O_RDONLY is stupidly defined as 0, the only invalid possible + * combination is O_WRONLY|O_RDWR. */ + syscall_fail(open("file01", O_WRONLY | O_RDWR | O_CREAT, 0), EINVAL); + syscall_fail(open("file01", O_RDONLY | O_RDWR | O_WRONLY | O_CREAT, 0), + EINVAL); + + /* Cannot open nonexistent file without O_CREAT */ + syscall_fail(open("file02", O_WRONLY, 0), ENOENT); + syscall_success(fd = open("file02", O_RDONLY | O_CREAT, 0)); + syscall_success(close(fd)); + syscall_success(unlink("file02")); + syscall_fail(stat("file02", &s), ENOENT); + + /* Cannot create invalid files */ + create_file("tmpfile"); + syscall_fail(open("tmpfile/test", O_RDONLY | O_CREAT, 0), ENOTDIR); + syscall_fail(open("noent/test", O_RDONLY | O_CREAT, 0), ENOENT); + syscall_fail(open(LONGNAME, O_RDONLY | O_CREAT, 0), ENAMETOOLONG); + + /* Cannot write to readonly file */ + syscall_success(fd = open("file03", O_RDONLY | O_CREAT, 0)); + syscall_fail(write(fd, "hello", 5), EBADF); + syscall_success(close(fd)); + + /* Cannot read from writeonly file. Note that we do not unlink() it + * from above, so we do not need O_CREAT set. */ + syscall_success(fd = open("file03", O_WRONLY, 0)); + syscall_fail(read(fd, buf, OPEN_BUFSIZE), EBADF); + syscall_success(close(fd)); + syscall_success(unlink("file03")); + syscall_fail(stat("file03", &s), ENOENT); + + /* Lowest file descriptor is always selected. */ + syscall_success(fd = open("file04", O_RDONLY | O_CREAT, 0)); + syscall_success(fd2 = open("file04", O_RDONLY, 0)); + test_assert(fd2 > fd, "open() did not return lowest fd"); + syscall_success(close(fd)); + syscall_success(close(fd2)); + syscall_success(fd2 = open("file04", O_WRONLY, 0)); + test_assert(fd2 == fd, "open() did not return correct fd"); + syscall_success(close(fd2)); + syscall_success(unlink("file04")); + syscall_fail(stat("file04", &s), ENOENT); + + /* Cannot open a directory for writing */ + syscall_success(mkdir("file05", 0)); + syscall_fail(open("file05", O_WRONLY, 0), EISDIR); + syscall_fail(open("file05", O_RDWR, 0), EISDIR); + syscall_success(rmdir("file05")); + + /* Cannot unlink a directory */ + syscall_success(mkdir("file06", 0)); + syscall_fail(unlink("file06"), EPERM); + syscall_success(rmdir("file06")); + syscall_fail(unlink("."), EPERM); + syscall_fail(unlink(".."), EPERM); + + /* Cannot unlink a non-existent file */ + syscall_fail(unlink("file07"), ENOENT); + + /* Cannot open a file as a directory */ + create_file("file08"); + syscall_fail(open("file08/", O_RDONLY, 0), ENOTDIR); + syscall_success(mkdir("dirA", 0777)); + syscall_success(chdir("dirA")); + create_file("file09"); + syscall_success(chdir("..")); + syscall_fail(open("dirA/file09/", O_RDONLY, 0), ENOTDIR); + + /* Succeeds with trailing slash */ + syscall_success(mkdir("dirB", 0777)); + syscall_success(mkdir("dirB/dirC", 0777)); + syscall_success(fd = open("dirB/", O_RDONLY, 0)); + syscall_success(close(fd)); + syscall_success(fd = open("dirB/dirC/", O_RDONLY, 0)); + syscall_success(close(fd)); + + syscall_success(chdir("..")); +} + +static void vfstest_read(void) +{ +#define READ_BUFSIZE 256 + + int fd, ret; + char buf[READ_BUFSIZE]; + stat_t s; + + syscall_success(mkdir("read", 0777)); + syscall_success(chdir("read")); + + /* Can read and write to a file */ + syscall_success(fd = open("file01", O_RDWR | O_CREAT, 0)); + syscall_success(ret = write(fd, "hello", 5)); + test_assert(5 == ret, "write(%d, \"hello\", 5) returned %d", fd, ret); + syscall_success(ret = lseek(fd, 0, SEEK_SET)); + test_assert(0 == ret, "lseek(%d, 0, SEEK_SET) returned %d", fd, ret); + read_fd(fd, READ_BUFSIZE, "hello"); + syscall_success(close(fd)); + + /* cannot read from a directory */ + syscall_success(mkdir("dir01", 0)); + syscall_success(fd = open("dir01", O_RDONLY, 0)); + syscall_fail(read(fd, buf, READ_BUFSIZE), EISDIR); + syscall_success(close(fd)); + + /* Can seek to beginning, middle, and end of file */ + syscall_success(fd = open("file02", O_RDWR | O_CREAT, 0)); + syscall_success(write(fd, "hello", 5)); + +#define test_lseek(expr, res) \ + do \ + { \ + int __r = (expr); \ + test_assert((res) == __r, #expr " returned %d, expected %d", __r, \ + res); \ + } while (0); + + test_lseek(lseek(fd, 0, SEEK_CUR), 5); + read_fd(fd, 10, ""); + test_lseek(lseek(fd, -1, SEEK_CUR), 4); + read_fd(fd, 10, "o"); + test_lseek(lseek(fd, 2, SEEK_CUR), 7); + read_fd(fd, 10, ""); + syscall_fail(lseek(fd, -8, SEEK_CUR), EINVAL); + + test_lseek(lseek(fd, 0, SEEK_SET), 0); + read_fd(fd, 10, "hello"); + test_lseek(lseek(fd, 3, SEEK_SET), 3); + read_fd(fd, 10, "lo"); + test_lseek(lseek(fd, 7, SEEK_SET), 7); + read_fd(fd, 10, ""); + syscall_fail(lseek(fd, -1, SEEK_SET), EINVAL); + + test_lseek(lseek(fd, 0, SEEK_END), 5); + read_fd(fd, 10, ""); + test_lseek(lseek(fd, -2, SEEK_END), 3); + read_fd(fd, 10, "lo"); + test_lseek(lseek(fd, 3, SEEK_END), 8); + read_fd(fd, 10, ""); + syscall_fail(lseek(fd, -8, SEEK_END), EINVAL); + + syscall_fail(lseek(fd, 0, SEEK_SET + SEEK_CUR + SEEK_END), EINVAL); + syscall_success(close(fd)); + + /* O_APPEND works properly */ + create_file("file03"); + syscall_success(fd = open("file03", O_RDWR, 0)); + test_fpos(fd, 0); + syscall_success(write(fd, "hello", 5)); + test_fpos(fd, 5); + syscall_success(close(fd)); + + syscall_success(fd = open("file03", O_RDWR | O_APPEND, 0)); + test_fpos(fd, 0); + syscall_success(write(fd, "hello", 5)); + test_fpos(fd, 10); + + syscall_success(lseek(fd, 0, SEEK_SET)); + test_fpos(fd, 0); + read_fd(fd, 10, "hellohello"); + syscall_success(lseek(fd, 5, SEEK_SET)); + test_fpos(fd, 5); + syscall_success(write(fd, "again", 5)); + test_fpos(fd, 15); + syscall_success(lseek(fd, 0, SEEK_SET)); + test_fpos(fd, 0); + read_fd(fd, 15, "hellohelloagain"); + syscall_success(close(fd)); + + /* seek and write beyond end of file */ + create_file("file04"); + syscall_success(fd = open("file04", O_RDWR, 0)); + syscall_success(write(fd, "hello", 5)); + test_fpos(fd, 5); + test_lseek(lseek(fd, 10, SEEK_SET), 10); + syscall_success(write(fd, "again", 5)); + syscall_success(stat("file04", &s)); + test_assert(s.st_size == 15, "actual size: %d", s.st_size); + test_lseek(lseek(fd, 0, SEEK_SET), 0); + test_assert(15 == read(fd, buf, READ_BUFSIZE), + "unexpected number of bytes read"); + test_assert(0 == memcmp(buf, "hello\0\0\0\0\0again", 15), + "unexpected data read"); + syscall_success(close(fd)); + + syscall_success(chdir("..")); +} + +static void vfstest_getdents(void) +{ + int fd, ret; + dirent_t dirents[4]; + + syscall_success(mkdir("getdents", 0)); + syscall_success(chdir("getdents")); + + /* getdents works */ + syscall_success(mkdir("dir01", 0)); + syscall_success(mkdir("dir01/1", 0)); + create_file("dir01/2"); + + syscall_success(fd = open("dir01", O_RDONLY, 0)); + syscall_success(ret = getdents(fd, dirents, 4 * sizeof(dirent_t))); + test_assert(4 * sizeof(dirent_t) == ret, NULL); + + syscall_success(ret = getdents(fd, dirents, sizeof(dirent_t))); + test_assert(0 == ret, NULL); + + syscall_success(lseek(fd, 0, SEEK_SET)); + test_fpos(fd, 0); + syscall_success(ret = getdents(fd, dirents, 2 * sizeof(dirent_t))); + test_assert(2 * sizeof(dirent_t) == ret, NULL); + syscall_success(ret = getdents(fd, dirents, 2 * sizeof(dirent_t))); + test_assert(2 * sizeof(dirent_t) == ret, NULL); + syscall_success(ret = getdents(fd, dirents, sizeof(dirent_t))); + test_assert(0 == ret, NULL); + syscall_success(close(fd)); + + /* Cannot call getdents on regular file */ + create_file("file01"); + syscall_success(fd = open("file01", O_RDONLY, 0)); + syscall_fail(getdents(fd, dirents, 4 * sizeof(dirent_t)), ENOTDIR); + syscall_success(close(fd)); + + syscall_success(chdir("..")); +} + +#ifdef __VM__ +/* + * Tests link(), rename(), and mmap() (and munmap, and brk). + * These functions are not supported on testfs, and not included in kernel-land + * vfs privtest (hence the name) + */ + +static void vfstest_s5fs_vm(void) +{ + int fd, newfd, ret; + char buf[2048]; + stat_t oldstatbuf, newstatbuf; + void *addr; + memset(&oldstatbuf, '\0', sizeof(stat_t)); + memset(&newstatbuf, '\0', sizeof(stat_t)); + + syscall_success(mkdir("s5fs", 0)); + syscall_success(chdir("s5fs")); + + /* Open some stuff */ + syscall_success(fd = open("oldchld", O_RDWR | O_CREAT, 0)); + syscall_success(mkdir("parent", 0)); + + /* link/unlink tests */ + syscall_success(link("oldchld", "newchld")); + + /* Make sure stats match */ + syscall_success(stat("oldchld", &oldstatbuf)); + syscall_success(stat("newchld", &newstatbuf)); + test_assert(0 == memcmp(&oldstatbuf, &newstatbuf, sizeof(stat_t)), NULL); + + /* Make sure contents match */ + syscall_success(newfd = open("newchld", O_RDWR, 0)); + syscall_success(ret = write(fd, TESTSTR, strlen(TESTSTR))); + test_assert(ret == (int)strlen(TESTSTR), NULL); + syscall_success(ret = read(newfd, buf, strlen(TESTSTR))); + test_assert(ret == (int)strlen(TESTSTR), NULL); + test_assert(0 == strncmp(buf, TESTSTR, strlen(TESTSTR)), + "string is %.*s, expected %s", strlen(TESTSTR), buf, TESTSTR); + + syscall_success(close(fd)); + syscall_success(close(newfd)); + + /* Remove one, make sure the other remains */ + syscall_success(unlink("oldchld")); + syscall_fail(mkdir("newchld", 0), EEXIST); + syscall_success(link("newchld", "oldchld")); + + /* Link/unlink error cases */ + syscall_fail(link("oldchld", "newchld"), EEXIST); + syscall_fail(link("oldchld", LONGNAME), ENAMETOOLONG); + syscall_fail(link("parent", "newchld"), EPERM); + + /* only rename test */ + /*syscall_success(rename("oldchld", "newchld"));*/ + + /* mmap/munmap tests */ + syscall_success(fd = open("newchld", O_RDWR, 0)); + test_assert( + MAP_FAILED != (addr = mmap(0, strlen(TESTSTR), PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd, 0)), + NULL); + /* Check contents of memory */ + test_assert(0 == memcmp(addr, TESTSTR, strlen(TESTSTR)), NULL); + + /* Write to it -> we shouldn't pagefault */ + memcpy(addr, SHORTSTR, strlen(SHORTSTR)); + test_assert(0 == memcmp(addr, SHORTSTR, strlen(SHORTSTR)), NULL); + + /* mmap the same thing on top of it, but shared */ + test_assert( + MAP_FAILED != mmap(addr, strlen(TESTSTR), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, fd, 0), + NULL); + /* Make sure the old contents were restored (the mapping was private) */ + test_assert(0 == memcmp(addr, TESTSTR, strlen(TESTSTR)), NULL); + + /* Now change the contents */ + memcpy(addr, SHORTSTR, strlen(SHORTSTR)); + /* mmap it on, private, on top again */ + test_assert( + MAP_FAILED != mmap(addr, strlen(TESTSTR), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED, fd, 0), + NULL); + /* Make sure it changed */ + test_assert(0 == memcmp(addr, SHORTSTR, strlen(SHORTSTR)), NULL); + + /* Fork and try changing things */ + if (!fork()) + { + /* Child changes private mapping */ + memcpy(addr, TESTSTR, strlen(TESTSTR)); + exit(0); + } + + /* Wait until child is done */ + syscall_success(wait(0)); + + /* Make sure it's actually private */ + test_assert(0 == memcmp(addr, SHORTSTR, strlen(SHORTSTR)), NULL); + + /* Unmap it */ + syscall_success(munmap(addr, 2048)); + + /* mmap errors */ + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE, 12, 0), + NULL); + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE, -1, 0), + NULL); + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, 0, fd, 0), NULL); + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_FIXED, fd, 0), NULL); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_FIXED | MAP_PRIVATE, fd, 0), + NULL); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE, fd, 0x12345), NULL); + test_assert(MAP_FAILED == mmap((void *)0x12345, 1024, PROT_READ, + MAP_PRIVATE | MAP_FIXED, fd, 0), + NULL); + test_assert(MAP_FAILED == mmap(0, 0, PROT_READ, MAP_PRIVATE, fd, 0), NULL); + test_assert(MAP_FAILED == mmap(0, -1, PROT_READ, MAP_PRIVATE, fd, 0), NULL); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, 0), + NULL); + syscall_success(close(fd)); + + syscall_success(fd = open("newchld", O_RDONLY, 0)); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + NULL); + syscall_success(close(fd)); + + /* TODO ENODEV (mmap a terminal) + EOVERFLOW (mmap SO MUCH of /dev/zero that fpointer would overflow) */ + + /* Also should test opening too many file descriptors somewhere */ + + /* munmap errors */ + syscall_fail(munmap((void *)0x12345, 15), EINVAL); + syscall_fail(munmap(0x0, 15), EINVAL); + syscall_fail(munmap(addr, 0), EINVAL); + syscall_fail(munmap(addr, -1), EINVAL); + + /* brk tests */ + /* Set the break, and use the memory in question */ + test_assert((void *)-1 != (addr = sbrk(128)), NULL); + memcpy(addr, TESTSTR, 128); + test_assert(0 == memcmp(addr, TESTSTR, 128), NULL); + + /* Make sure that the brk is being saved properly */ + test_assert((void *)((unsigned long)addr + 128) == sbrk(0), NULL); + /* Knock the break back down */ + syscall_success(brk(addr)); + + /* brk errors */ + syscall_fail(brk((void *)(&"brk")), ENOMEM); + syscall_fail(brk((void *)1), ENOMEM); + syscall_fail(brk((void *)&addr), ENOMEM); + + syscall_success(chdir("..")); +} +#endif + +#ifdef __KERNEL__ +extern uint64_t jiffies; +#endif + +static void seed_randomness() +{ +#ifdef __KERNEL__ + srand(jiffies); +#else + srand(time(NULL)); +#endif + rand(); +} + +/* + * Finally, the main function. + */ +#ifndef __KERNEL__ + +int main(int argc, char **argv) +#else +int vfstest_main(int argc, char **argv) +#endif +{ + if (argc != 1) + { + fprintf(stderr, "USAGE: vfstest\n"); + return 1; + } + + seed_randomness(); + + test_init(); + vfstest_start(); + + syscall_success(chdir(root_dir)); + + vfstest_notdir(); + vfstest_stat(); + vfstest_chdir(); + vfstest_mkdir(); + vfstest_paths(); + vfstest_fd(); + vfstest_open(); + vfstest_read(); + vfstest_getdents(); + vfstest_memdev(); + vfstest_write(); + +#ifdef __VM__ + vfstest_s5fs_vm(); +#endif + + syscall_success(chdir("..")); + + vfstest_term(); + test_fini(); + + return 0; +} |