aboutsummaryrefslogtreecommitdiff
path: root/user/usr/bin
diff options
context:
space:
mode:
Diffstat (limited to 'user/usr/bin')
-rw-r--r--user/usr/bin/args.c36
-rw-r--r--user/usr/bin/hello.c17
-rw-r--r--user/usr/bin/kshell.c8
-rw-r--r--user/usr/bin/segfault.S12
-rw-r--r--user/usr/bin/spin.c10
-rw-r--r--user/usr/bin/tests/eatinodes.c90
-rw-r--r--user/usr/bin/tests/eatmem.c190
-rw-r--r--user/usr/bin/tests/elf_test-64.c38
-rw-r--r--user/usr/bin/tests/forkbomb.c84
-rw-r--r--user/usr/bin/tests/forktest.c19
-rw-r--r--user/usr/bin/tests/linkermagic.h232
-rw-r--r--user/usr/bin/tests/memtest.c816
l---------user/usr/bin/tests/mm.h1
l---------user/usr/bin/tests/page.h1
-rw-r--r--user/usr/bin/tests/pipetest.c191
-rw-r--r--user/usr/bin/tests/prime.c107
-rw-r--r--user/usr/bin/tests/s5fstest.c332
-rw-r--r--user/usr/bin/tests/stress.c476
-rw-r--r--user/usr/bin/tests/vfstest.c1172
-rw-r--r--user/usr/bin/wc.c114
20 files changed, 3946 insertions, 0 deletions
diff --git a/user/usr/bin/args.c b/user/usr/bin/args.c
new file mode 100644
index 0000000..e10e821
--- /dev/null
+++ b/user/usr/bin/args.c
@@ -0,0 +1,36 @@
+/*
+ * Does some basic checks to make sure arguments are
+ * being passed to userland programs correctly.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char **argv, char **envp)
+{
+ int i;
+ char buf[100];
+
+ open("/dev/tty0", O_RDONLY, 0);
+ open("/dev/tty0", O_WRONLY, 0);
+
+ snprintf(buf, sizeof(buf), "Arguments: (argc = %d, argv = %p)\n", argc,
+ argv);
+ write(1, buf, strlen(buf));
+ for (i = 0; argv[i]; i++)
+ {
+ snprintf(buf, sizeof(buf), " %d \"%s\"\n", i, argv[i]);
+ write(1, buf, strlen(buf));
+ }
+ snprintf(buf, sizeof(buf), "Environment: (envp = %p)\n", envp);
+ write(1, buf, strlen(buf));
+ for (i = 0; envp[i]; i++)
+ {
+ snprintf(buf, sizeof(buf), " %d \"%s\"\n", i, envp[i]);
+ write(1, buf, strlen(buf));
+ }
+
+ return 0;
+}
diff --git a/user/usr/bin/hello.c b/user/usr/bin/hello.c
new file mode 100644
index 0000000..894f062
--- /dev/null
+++ b/user/usr/bin/hello.c
@@ -0,0 +1,17 @@
+/*
+ * The classic hello world program.
+ * If you can run this successfully, you should high-five
+ * everyone nearby. You can also shout loudly. People
+ * will understand.
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+int main(int argc, char **argv)
+{
+ open("/dev/tty0", O_RDONLY, 0);
+ open("/dev/tty0", O_WRONLY, 0);
+ write(1, "Hello, world!\n", 14);
+ return 0;
+}
diff --git a/user/usr/bin/kshell.c b/user/usr/bin/kshell.c
new file mode 100644
index 0000000..c22cff0
--- /dev/null
+++ b/user/usr/bin/kshell.c
@@ -0,0 +1,8 @@
+/*
+ * Runs the in-kernel shell.
+ */
+
+#include "weenix/syscall.h"
+#include "weenix/trap.h"
+
+int main(int argc, char **argv) { return (int)trap(SYS_kshell, (uint32_t)0); }
diff --git a/user/usr/bin/segfault.S b/user/usr/bin/segfault.S
new file mode 100644
index 0000000..e37f39a
--- /dev/null
+++ b/user/usr/bin/segfault.S
@@ -0,0 +1,12 @@
+/*
+ * An extremely simple test binary. It relies on your
+ * process text and stack being mapped in (and hence vmmap_map
+ * code working) but otherwise has even fewer dependencies than
+ * hello. It should almost immediately segfault.
+ */
+
+.globl main
+main:
+ xor %eax, %eax;
+ mov (%eax), %eax;
+ ret;
diff --git a/user/usr/bin/spin.c b/user/usr/bin/spin.c
new file mode 100644
index 0000000..e0ff1df
--- /dev/null
+++ b/user/usr/bin/spin.c
@@ -0,0 +1,10 @@
+/*
+ * Spins.
+ */
+
+int main(int argc, char **argv)
+{
+ while (1)
+ ;
+ return 0;
+}
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;
+}
diff --git a/user/usr/bin/wc.c b/user/usr/bin/wc.c
new file mode 100644
index 0000000..f622614
--- /dev/null
+++ b/user/usr/bin/wc.c
@@ -0,0 +1,114 @@
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define BUFFER_SIZE 1024
+
+typedef struct count_results
+{
+ unsigned long long n_chars;
+ unsigned long long n_words;
+ unsigned long long n_lines;
+} count_results_t;
+
+char buf[BUFFER_SIZE];
+
+void print_counts(count_results_t *results, char *name)
+{
+ if (name)
+ {
+ printf("%10llu %10llu %10llu %10s\n", results->n_lines,
+ results->n_words, results->n_chars, name);
+ }
+ else
+ {
+ printf("%10llu %10llu %10llu\n", results->n_lines, results->n_words,
+ results->n_chars);
+ }
+}
+
+void count(int fd, char *name, count_results_t *results)
+{
+ size_t bytes_read;
+ unsigned int in_word, i;
+
+ in_word = 0;
+ while ((bytes_read = read(fd, buf, BUFFER_SIZE)) > 0)
+ {
+ for (i = 0; i < bytes_read; ++i)
+ {
+ if (isspace(buf[i]))
+ {
+ if (in_word)
+ {
+ results->n_words++;
+ in_word = 0;
+ }
+ }
+ else
+ {
+ in_word = 1;
+ }
+
+ if (buf[i] == '\n')
+ {
+ results->n_lines++;
+ }
+ }
+
+ results->n_chars += bytes_read;
+ }
+
+ print_counts(results, name);
+}
+
+int main(int argc, char **argv)
+{
+ int f, fd;
+ count_results_t total_counts = {.n_chars = 0, .n_words = 0, .n_lines = 0};
+ count_results_t local_counts = {.n_chars = 0, .n_words = 0, .n_lines = 0};
+
+ if (argc == 1)
+ {
+ /* Reading from standard input. */
+ count(0, 0, &total_counts);
+ }
+ else
+ {
+ /* Reading files, not standard input. */
+ for (f = 1; f < argc; ++f)
+ {
+ fd = open(argv[f], O_RDONLY, 0);
+ if (fd < 0)
+ {
+ /* Error opening file. */
+ fprintf(stderr, "wc: %s: open: %s\n", argv[f], strerror(errno));
+ }
+ else
+ {
+ /* Opened the file. */
+ count(fd, argv[f], &local_counts);
+
+ total_counts.n_chars += local_counts.n_chars;
+ total_counts.n_words += local_counts.n_words;
+ total_counts.n_lines += local_counts.n_lines;
+
+ /* Reset the local counts. */
+ local_counts.n_chars = local_counts.n_words =
+ local_counts.n_lines = 0;
+
+ close(fd);
+ }
+ }
+
+ if (argc > 2)
+ {
+ /* They provided multiple files. We should print the total too. */
+ print_counts(&total_counts, "total");
+ }
+ }
+
+ return 0;
+}