aboutsummaryrefslogtreecommitdiff
path: root/kernel/test
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/test')
-rw-r--r--kernel/test/Submodules1
-rw-r--r--kernel/test/driverstest.c288
-rw-r--r--kernel/test/kshell/command.c46
-rw-r--r--kernel/test/kshell/command.h20
-rw-r--r--kernel/test/kshell/commands.c404
-rw-r--r--kernel/test/kshell/commands.h32
-rw-r--r--kernel/test/kshell/io.c78
-rw-r--r--kernel/test/kshell/kshell.c504
-rw-r--r--kernel/test/kshell/priv.h43
-rw-r--r--kernel/test/kshell/tokenizer.c74
-rw-r--r--kernel/test/kshell/tokenizer.h39
-rw-r--r--kernel/test/pipes.c133
-rw-r--r--kernel/test/proctest.c57
-rw-r--r--kernel/test/s5fstest.c251
-rw-r--r--kernel/test/usertest.c174
-rw-r--r--kernel/test/vfstest/vfstest.c1173
-rw-r--r--kernel/test/vmtest.c74
17 files changed, 3391 insertions, 0 deletions
diff --git a/kernel/test/Submodules b/kernel/test/Submodules
new file mode 100644
index 0000000..3227c36
--- /dev/null
+++ b/kernel/test/Submodules
@@ -0,0 +1 @@
+kshell
diff --git a/kernel/test/driverstest.c b/kernel/test/driverstest.c
new file mode 100644
index 0000000..0ed5e1d
--- /dev/null
+++ b/kernel/test/driverstest.c
@@ -0,0 +1,288 @@
+#include "errno.h"
+#include "globals.h"
+
+#include "test/usertest.h"
+#include "test/proctest.h"
+
+#include "util/debug.h"
+#include "util/printf.h"
+#include "util/string.h"
+
+#include "proc/proc.h"
+#include "proc/kthread.h"
+#include "proc/sched.h"
+
+#include "drivers/tty/tty.h"
+#include "drivers/dev.h"
+#include "drivers/blockdev.h"
+#include "drivers/keyboard.h"
+
+#define TEST_STR_1 "hello\n"
+#define TEST_STR_2 "different string\n"
+#define TEST_STR_3 "test"
+#define TEST_BUF_SZ 10
+#define NUM_PROCS 3
+#define BLOCK_NUM 0
+
+// TODO: need to change to using the MOD macro
+
+void* kthread_write(long arg1, void* arg2) {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, arg1));
+ tty_t* tty = cd_to_tty(cd);
+
+ int count = 0;
+ while (count < 2) {
+ if (count == 0) {
+ for (size_t i = 0; i < strlen(TEST_STR_1); i++) {
+ ldisc_key_pressed(&tty->tty_ldisc, TEST_STR_1[i]);
+ }
+ } else {
+ for (size_t i = 0; i < strlen(TEST_STR_2); i++) {
+ ldisc_key_pressed(&tty->tty_ldisc, TEST_STR_2[i]);
+ }
+ }
+ count++;
+ }
+ return NULL;
+}
+
+void* kthread_read1(long arg1, void* arg2) {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, arg1));
+ char buf[32];
+ memset(buf, 0, 32);
+ size_t num_bytes = cd->cd_ops->read(cd, 0, buf, strlen(TEST_STR_1));
+ test_assert(num_bytes == strlen(TEST_STR_1), "number of bytes is incorrect");
+ test_assert(!strncmp(buf, TEST_STR_1, strlen(TEST_STR_1)), "resulting strings are not equal");
+
+ return NULL;
+}
+
+void* kthread_read2(long arg1, void* arg2) {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, arg1));
+
+ char buf[32];
+ memset(buf, 0, 32);
+ size_t num_bytes = cd->cd_ops->read(cd, 0, buf, strlen(TEST_STR_2));
+ test_assert(num_bytes == strlen(TEST_STR_2), "number of bytes is incorrect");
+ test_assert(!strncmp(buf, TEST_STR_2, strlen(TEST_STR_2)), "resulting strings are not equal");
+
+ return NULL;
+}
+
+long test_concurrent_reads() {
+ proc_t* proc_write = proc_create("process_write");
+ kthread_t* kt_write = kthread_create(proc_write, kthread_write, 0, NULL);
+
+ proc_t* proc_1 = proc_create("process_1_read");
+ kthread_t* kthread_1 = kthread_create(proc_1, kthread_read1, 0, NULL);
+
+ proc_t* proc_2 = proc_create("process_2_read");
+ kthread_t* kthread_2 = kthread_create(proc_2, kthread_read2, 0, NULL);
+
+ sched_make_runnable(kthread_1);
+ sched_make_runnable(kthread_2);
+ sched_make_runnable(kt_write);
+
+ while (do_waitpid(-1, NULL, 0) != -ECHILD)
+ ;
+
+ return 0;
+}
+
+/**
+ * Function for each kthread to write the order in which they were spawned
+ * to the character device.
+*/
+void* kthread_concurrent_write(long arg1, void* arg2) {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, 0));
+ char buf[32];
+ memset(buf, 0, 32);
+ snprintf(buf, 32, "thread_%d\n", (int)arg1);
+ size_t num_bytes = cd->cd_ops->write(cd, 0, buf, strlen(buf));
+ test_assert(num_bytes == strlen(buf), "number of bytes written is not correct");
+ return NULL;
+}
+
+long test_concurrent_writes() {
+ char proc_name[32];
+ for (int i = 0; i < NUM_PROCS; i++) {
+ memset(proc_name, 0, 32);
+ snprintf(proc_name, 32, "process_concurrent_write_%d", i);
+ proc_t* proc_write = proc_create(proc_name);
+ kthread_t* kt_write = kthread_create(proc_write, kthread_concurrent_write, i, NULL);
+ sched_make_runnable(kt_write);
+ }
+
+ while (do_waitpid(-1, NULL, 0) != -ECHILD)
+ ;
+
+ return 0;
+}
+
+void* kthread_write_disk(long arg1, void* arg2) {
+ // write to disk here
+ void* page_of_data = page_alloc();
+ // memset it to be some random character
+ memset(page_of_data, 'F', BLOCK_SIZE);
+ blockdev_t* bd = blockdev_lookup(MKDEVID(DISK_MAJOR, 0));
+ long ret = bd->bd_ops->write_block(bd, (char*)page_of_data, arg1, 1);
+ test_assert(ret == 0, "the write operation failed");
+
+ return NULL;
+}
+
+void* kthread_read_disk(long arg1, void* arg2) {
+ // read that same block of data here
+ // not going to memset it because we are reading that amount
+ void* page_of_data_to_read = page_alloc_n(2);
+ void* data_expected = page_alloc_n(2);
+ memset(data_expected, 'F', BLOCK_SIZE);
+ blockdev_t* bd = blockdev_lookup(MKDEVID(DISK_MAJOR, 0));
+ test_assert(!PAGE_ALIGNED((char*)page_of_data_to_read+1), "not page aligned");
+ long ret = bd->bd_ops->read_block(bd, (char*)page_of_data_to_read+1, arg1, 1);
+ test_assert(ret == 0, "the read operation failed");
+ test_assert(0 == memcmp((char*)page_of_data_to_read+1, data_expected, BLOCK_SIZE), "bytes are not equal");
+ page_free_n(page_of_data_to_read, 2);
+ page_free_n(data_expected, 2);
+ return NULL;
+}
+
+/*
+ First write to disk and then attempt to read from disk
+*/
+long test_disk_write_and_read() {
+ proc_t* proc_write = proc_create("process_write");
+ kthread_t* kt_write = kthread_create(proc_write, kthread_write_disk, BLOCK_NUM, NULL);
+
+ proc_t* proc_read = proc_create("process_read");
+ kthread_t* kt_read = kthread_create(proc_read, kthread_read_disk, BLOCK_NUM, NULL);
+
+ sched_make_runnable(kt_write);
+ sched_make_runnable(kt_read);
+
+ while (do_waitpid(-1, NULL, 0) != -ECHILD)
+ ;
+
+ return 0;
+}
+
+/*
+ Tests inputting a character and a newline character
+*/
+long test_basic_line_discipline() {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, 0));
+ tty_t* tty = cd_to_tty(cd);
+ ldisc_t* ldisc = &tty->tty_ldisc;
+ ldisc_key_pressed(ldisc, 't');
+
+ test_assert(ldisc->ldisc_buffer[ldisc->ldisc_tail] == 't', "character not inputted into buffer correctly");
+ test_assert(ldisc->ldisc_head != ldisc->ldisc_cooked && ldisc->ldisc_tail != ldisc->ldisc_head, "pointers are updated correctly");
+
+ size_t previous_head_val = ldisc->ldisc_head;
+ ldisc_key_pressed(ldisc, '\n');
+ test_assert(ldisc->ldisc_head == previous_head_val + 1, "ldisc_head should have been incremented past newline character");
+ test_assert(ldisc->ldisc_cooked == ldisc->ldisc_head, "ldisc_cooked should be equal to ldisc_head");
+
+ // reset line discipline for other tests before returning
+ ldisc->ldisc_head = ldisc->ldisc_cooked = ldisc->ldisc_tail = 0;
+ return 0;
+}
+
+/*
+ Tests removing a character
+*/
+long test_backspace() {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, 0));
+ tty_t* tty = cd_to_tty(cd);
+ ldisc_t* ldisc = &tty->tty_ldisc;
+ size_t previous_head_val = ldisc->ldisc_head;
+ ldisc_key_pressed(ldisc, 't');
+ ldisc_key_pressed(ldisc, '\b');
+ test_assert(ldisc->ldisc_head == previous_head_val, "Backspace should move the ldisc_head back by 1");
+
+ // testing there should be no characters to remove
+ ldisc_key_pressed(ldisc, '\b');
+ test_assert(ldisc->ldisc_head == previous_head_val, "This backspace should result in a no-op");
+
+ // reset line discipline for other tests before returning
+ ldisc->ldisc_head = ldisc->ldisc_cooked = ldisc->ldisc_tail = 0;
+ return 0;
+}
+
+void* kthread_wait_for_eot(long arg1, void* arg2) {
+ chardev_t* cd = (chardev_t*)arg2;
+ char buf[32];
+ memset(buf, 0, 32);
+ size_t num_bytes = cd->cd_ops->read(cd, 0, buf, TEST_BUF_SZ);
+ test_assert(num_bytes == strlen(TEST_STR_3), "number of bytes is incorrect");
+ test_assert(!strncmp(buf, TEST_STR_3, strlen(TEST_STR_3)), "resulting strings are not equal");
+ return NULL;
+}
+
+/*
+ Tests the behavior for EOT
+*/
+long test_eot() {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, 0));
+ tty_t* tty = cd_to_tty(cd);
+ ldisc_t* ldisc = &tty->tty_ldisc;
+
+ proc_t* proc_read = proc_create("process_read");
+ kthread_t* kt_read = kthread_create(proc_read, kthread_wait_for_eot, 0, cd);
+ sched_make_runnable(kt_read);
+ // allow the other process to run first so it can block before typing
+ sched_yield();
+
+ size_t prev_tail_value = ldisc->ldisc_tail;
+ for (size_t i = 0; i < strlen(TEST_STR_3); i++) {
+ ldisc_key_pressed(ldisc, TEST_STR_3[i]);
+ }
+ ldisc_key_pressed(ldisc, EOT);
+ test_assert(ldisc->ldisc_head == ldisc->ldisc_cooked, "ldisc_head should be equal to ldisc_cooked");
+
+ // allow the other thread to read
+ while (do_waitpid(-1, NULL, 0) != -ECHILD)
+ ;
+ test_assert(ldisc->ldisc_tail == prev_tail_value + strlen(TEST_STR_3) + 1, "ldisc_tail should be past the EOT char");
+ ldisc->ldisc_head = ldisc->ldisc_tail = ldisc->ldisc_cooked = 0;
+ return 0;
+}
+
+/*
+ Tests the behavior for ETX
+*/
+long test_etx() {
+ chardev_t* cd = chardev_lookup(MKDEVID(TTY_MAJOR, 0));
+ tty_t* tty = cd_to_tty(cd);
+ ldisc_t* ldisc = &tty->tty_ldisc;
+ size_t previous_head_value = ldisc->ldisc_head;
+
+ // "press" two characters
+ ldisc_key_pressed(ldisc, 't');
+ ldisc_key_pressed(ldisc, 'e');
+ ldisc_key_pressed(ldisc, ETX);
+
+ test_assert(previous_head_value + 1 == ldisc->ldisc_head, "ldisc_head should only be one past where it used to be");
+ test_assert(ldisc->ldisc_head == ldisc->ldisc_cooked, "ldisc should be a cooked blank line");
+
+ // reset line discipline for other tests before returning
+ ldisc->ldisc_head = ldisc->ldisc_cooked = ldisc->ldisc_tail = 0;
+ return 0;
+}
+
+long driverstest_main(long arg1, void* arg2)
+{
+ dbg(DBG_TEST, "\nStarting Drivers tests\n");
+ test_init();
+
+ test_basic_line_discipline();
+ test_backspace();
+ test_eot();
+ test_etx();
+ test_concurrent_reads();
+ test_concurrent_writes();
+ test_disk_write_and_read();
+
+ test_fini();
+ return 0;
+} \ No newline at end of file
diff --git a/kernel/test/kshell/command.c b/kernel/test/kshell/command.c
new file mode 100644
index 0000000..836b743
--- /dev/null
+++ b/kernel/test/kshell/command.c
@@ -0,0 +1,46 @@
+#include "command.h"
+
+#include "mm/kmalloc.h"
+
+#include "util/debug.h"
+#include "util/string.h"
+
+kshell_command_t *kshell_command_create(const char *name,
+ kshell_cmd_func_t cmd_func,
+ const char *desc)
+{
+ kshell_command_t *cmd;
+ size_t len;
+
+ KASSERT(NULL != name);
+ KASSERT(NULL != cmd_func);
+
+ cmd = (kshell_command_t *)kmalloc(sizeof(kshell_command_t));
+ if (NULL == cmd)
+ {
+ return NULL;
+ }
+
+ len = strnlen(name, KSH_CMD_NAME_LEN);
+ strncpy(cmd->kc_name, name, len);
+ cmd->kc_name[len] = '\0';
+
+ cmd->kc_cmd_func = cmd_func;
+
+ if (NULL != desc)
+ {
+ len = strnlen(desc, KSH_DESC_LEN);
+ strncpy(cmd->kc_desc, desc, len);
+ cmd->kc_desc[len] = '\0';
+ }
+ else
+ {
+ cmd->kc_desc[0] = '\0';
+ }
+
+ list_link_init(&cmd->kc_commands_link);
+
+ return cmd;
+}
+
+void kshell_command_destroy(kshell_command_t *cmd) { kfree(cmd); }
diff --git a/kernel/test/kshell/command.h b/kernel/test/kshell/command.h
new file mode 100644
index 0000000..96a5cb0
--- /dev/null
+++ b/kernel/test/kshell/command.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "priv.h"
+
+#include "test/kshell/kshell.h"
+
+typedef struct kshell_command
+{
+ char kc_name[KSH_CMD_NAME_LEN + 1];
+ kshell_cmd_func_t kc_cmd_func;
+ char kc_desc[KSH_DESC_LEN + 1];
+
+ list_link_t kc_commands_link;
+} kshell_command_t;
+
+kshell_command_t *kshell_command_create(const char *name,
+ kshell_cmd_func_t cmd_func,
+ const char *desc);
+
+void kshell_command_destroy(kshell_command_t *cmd);
diff --git a/kernel/test/kshell/commands.c b/kernel/test/kshell/commands.c
new file mode 100644
index 0000000..5ad5b11
--- /dev/null
+++ b/kernel/test/kshell/commands.c
@@ -0,0 +1,404 @@
+#include "commands.h"
+#include "errno.h"
+
+#include "command.h"
+
+#ifdef __VFS__
+
+#include "fs/fcntl.h"
+#include "fs/vfs_syscall.h"
+#include "fs/vnode.h"
+
+#endif
+
+#include "test/kshell/io.h"
+
+#include "util/debug.h"
+#include "util/string.h"
+
+list_t kshell_commands_list = LIST_INITIALIZER(kshell_commands_list);
+
+long kshell_help(kshell_t *ksh, size_t argc, char **argv)
+{
+ /* Print a list of available commands */
+ char spaces[KSH_CMD_NAME_LEN];
+ memset(spaces, ' ', KSH_CMD_NAME_LEN);
+
+ kprintf(ksh, "Available commands:\n");
+ list_iterate(&kshell_commands_list, cmd, kshell_command_t,
+ kc_commands_link)
+ {
+ KASSERT(NULL != cmd);
+ size_t namelen = strnlen(cmd->kc_name, KSH_CMD_NAME_LEN);
+ spaces[KSH_CMD_NAME_LEN - namelen] = '\0';
+ kprintf(ksh, "%s%s%s\n", cmd->kc_name, spaces, cmd->kc_desc);
+ spaces[KSH_CMD_NAME_LEN - namelen] = ' ';
+ }
+
+ return 0;
+}
+
+long kshell_exit(kshell_t *ksh, size_t argc, char **argv)
+{
+ panic("kshell: kshell_exit should NEVER be called");
+}
+
+long kshell_clear(kshell_t *ksh, size_t argc, char **argv)
+{
+ kprintf(ksh, "\033[2J\033[1;1H");
+
+ // kprintf(ksh, "\033[10A");
+ return 0;
+}
+
+long kshell_halt(kshell_t *ksh, size_t argc, char **argv)
+{
+ proc_kill_all();
+ return 0;
+}
+
+long kshell_echo(kshell_t *ksh, size_t argc, char **argv)
+{
+ if (argc == 1)
+ {
+ kprintf(ksh, "\n");
+ }
+ else
+ {
+ for (size_t i = 1; i < argc - 1; i++)
+ {
+ kprintf(ksh, "%s ", argv[i]);
+ }
+ kprintf(ksh, "%s\n", argv[argc - 1]);
+ }
+
+ return 0;
+}
+
+#ifdef __VFS__
+
+long kshell_cat(kshell_t *ksh, size_t argc, char **argv)
+{
+ if (argc < 2)
+ {
+ kprintf(ksh, "Usage: cat <files>\n");
+ return 0;
+ }
+
+ char buf[KSH_BUF_SIZE];
+ for (size_t i = 1; i < argc; i++)
+ {
+ int fd = (int)do_open(argv[i], O_RDONLY);
+ if (fd < 0)
+ {
+ kprintf(ksh, "Error opening file: %s\n", argv[i]);
+ continue;
+ }
+
+ long retval;
+ while ((retval = do_read(fd, buf, KSH_BUF_SIZE)) > 0)
+ {
+ retval = kshell_write_all(ksh, buf, (size_t)retval);
+ if (retval < 0)
+ break;
+ }
+ if (retval < 0)
+ {
+ kprintf(ksh, "Error reading or writing %s: %s\n", argv[i], strerror((int)-retval));
+ }
+
+ retval = do_close(fd);
+ if (retval < 0)
+ {
+ panic("kshell: Error closing file %s: %s\n", argv[i],
+ strerror((int)-retval));
+ }
+ }
+
+ return 0;
+}
+
+long kshell_ls(kshell_t *ksh, size_t argc, char **argv)
+{
+ size_t arglen;
+ long ret;
+ int fd;
+ dirent_t dirent;
+ stat_t statbuf;
+ char direntname[KSH_BUF_SIZE];
+
+ memset(direntname, '\0', KSH_BUF_SIZE);
+
+ if (argc > 2)
+ {
+ kprintf(ksh, "Usage: ls <directory>\n");
+ return 0;
+ }
+ else if (argc == 2)
+ {
+ if ((ret = do_stat(argv[1], &statbuf)) < 0)
+ {
+ if (ret == -ENOENT)
+ {
+ kprintf(ksh, "%s does not exist\n", argv[1]);
+ return 0;
+ }
+ else
+ {
+ return ret;
+ }
+ }
+ if (!S_ISDIR(statbuf.st_mode))
+ {
+ kprintf(ksh, "%s is not a directory\n", argv[1]);
+ return 0;
+ }
+
+ fd = (int)do_open(argv[1], O_RDONLY);
+ if (fd < 0)
+ {
+ kprintf(ksh, "Could not find directory: %s\n", argv[1]);
+ return 0;
+ }
+ arglen = strnlen(argv[1], KSH_BUF_SIZE);
+ }
+ else
+ {
+ KASSERT(argc == 1);
+ fd = (int)do_open(".", O_RDONLY);
+ if (fd < 0)
+ {
+ kprintf(ksh, "Could not find directory: .\n");
+ return 0;
+ }
+ arglen = 1;
+ }
+
+ if (argc == 2)
+ memcpy(direntname, argv[1], arglen);
+ else
+ direntname[0] = '.';
+
+ direntname[arglen] = '/';
+ direntname[arglen + NAME_LEN + 1] = '\0';
+
+ while ((ret = do_getdent(fd, &dirent)) == sizeof(dirent_t))
+ {
+ memcpy(direntname + arglen + 1, dirent.d_name, NAME_LEN + 1);
+ ret = do_stat(direntname, &statbuf);
+ if (ret < 0)
+ {
+ kprintf(ksh, "Error stat\'ing `%s`: %s\n", dirent.d_name, strerror((int)-ret));
+ continue;
+ }
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ kprintf(ksh, "%s/\n", dirent.d_name);
+ }
+ else
+ {
+ kprintf(ksh, "%s\n", dirent.d_name);
+ }
+ }
+
+ do_close(fd);
+ return ret;
+}
+
+long kshell_cd(kshell_t *ksh, size_t argc, char **argv)
+{
+ KASSERT(ksh && argc && argv);
+ if (argc < 2)
+ {
+ kprintf(ksh, "Usage: cd <directory>\n");
+ return 0;
+ }
+
+ long ret = do_chdir(argv[1]);
+ if (ret < 0)
+ {
+ kprintf(ksh, "cd: `%s`: %s\n", argv[1], strerror((int)-ret));
+ }
+ return 0;
+}
+
+long kshell_rm(kshell_t *ksh, size_t argc, char **argv)
+{
+ KASSERT(ksh && argc && argv);
+
+ if (argc < 2)
+ {
+ kprintf(ksh, "Usage: rm <file>\n");
+ return 0;
+ }
+
+ long ret = do_unlink(argv[1]);
+ if (ret < 0)
+ {
+ kprintf(ksh, "rm: `%s`: %s\n", argv[1], strerror((int)-ret));
+ }
+
+ return 0;
+}
+
+long kshell_link(kshell_t *ksh, size_t argc, char **argv)
+{
+ KASSERT(ksh && argc && argv);
+
+ if (argc < 3)
+ {
+ kprintf(ksh, "Usage: link <src> <dst>\n");
+ return 0;
+ }
+
+ long ret = do_link(argv[1], argv[2]);
+ if (ret < 0)
+ {
+ kprintf(ksh, "Error linking %s to %s: %s\n", argv[1], argv[2], strerror((int)-ret));
+ }
+
+ return 0;
+}
+
+long kshell_rmdir(kshell_t *ksh, size_t argc, char **argv)
+{
+ KASSERT(ksh && argc && argv);
+ if (argc < 2)
+ {
+ kprintf(ksh, "Usage: rmdir DIRECTORY...\n");
+ return 1;
+ }
+
+ long exit_val = 0;
+ for (size_t i = 1; i < argc; i++)
+ {
+ long ret = do_rmdir(argv[i]);
+ if (ret < 0)
+ {
+ kprintf(ksh, "rmdir: failed to remove directory `%s': %s\n",
+ argv[i], strerror((int)-ret));
+ exit_val = 1;
+ }
+ }
+
+ return exit_val;
+}
+
+long kshell_mkdir(kshell_t *ksh, size_t argc, char **argv)
+{
+ KASSERT(ksh && argc && argv);
+ if (argc < 2)
+ {
+ kprintf(ksh, "Usage: mkdir DIRECTORY...\n");
+ return 1;
+ }
+
+ long exit_val = 0;
+ for (size_t i = 1; i < argc; i++)
+ {
+ long ret = do_mkdir(argv[i]);
+ if (ret < 0)
+ {
+ kprintf(ksh, "mkdir: failed to create directory `%s': %s\n",
+ argv[i], strerror((int)-ret));
+ exit_val = 1;
+ }
+ }
+
+ return exit_val;
+}
+
+static const char *get_file_type_str(int mode)
+{
+ if (S_ISCHR(mode))
+ {
+ return "character special file";
+ }
+ else if (S_ISDIR(mode))
+ {
+ return "directory";
+ }
+ else if (S_ISBLK(mode))
+ {
+ return "block special file";
+ }
+ else if (S_ISREG(mode))
+ {
+ return "regular file";
+ }
+ else if (S_ISLNK(mode))
+ {
+ return "symbolic link";
+ }
+ else
+ {
+ return "unknown";
+ }
+}
+
+long kshell_stat(kshell_t *ksh, size_t argc, char **argv)
+{
+ KASSERT(ksh && argc && argv);
+ long exit_val = 0;
+
+ if (argc < 2)
+ {
+ kprintf(ksh, "Usage: stat FILE...\n");
+ return 1;
+ }
+
+ for (size_t i = 1; i < argc; i++)
+ {
+ stat_t buf;
+ long ret = do_stat(argv[i], &buf);
+ if (ret < 0)
+ {
+ kprintf(ksh, "Cannot stat `%s': %s\n", argv[i],
+ strerror((int)-ret));
+ exit_val = 1;
+ continue;
+ }
+ const char *file_type_str = get_file_type_str(buf.st_mode);
+ kprintf(ksh, "File: `%s'\n", argv[i]);
+ kprintf(ksh, "Size: %d\n", buf.st_size);
+ kprintf(ksh, "Blocks: %d\n", buf.st_blocks);
+ kprintf(ksh, "IO Block: %d\n", buf.st_blksize);
+ kprintf(ksh, "%s\n", file_type_str);
+ kprintf(ksh, "Inode: %d\n", buf.st_ino);
+ kprintf(ksh, "Links: %d\n", buf.st_nlink);
+ }
+
+ return exit_val;
+}
+
+long vfstest_main(int, void *);
+
+long kshell_vfs_test(kshell_t *ksh, size_t argc, char **argv)
+{
+ kprintf(ksh, "TEST VFS: Testing... Please wait.\n");
+
+ long ret = vfstest_main(1, NULL);
+
+ kprintf(ksh, "TEST VFS: testing complete, check console for results\n");
+
+ return ret;
+}
+
+#endif
+
+#ifdef __S5FS__
+
+long s5fstest_main(int, void *);
+
+long kshell_s5fstest(kshell_t *ksh, size_t argc, char **argv)
+{
+ kprintf(ksh, "TEST S5FS: Testing... Please wait.\n");
+
+ long ret = s5fstest_main(1, NULL);
+
+ kprintf(ksh, "TEST S5FS: testing complete, check console for results\n");
+
+ return ret;
+}
+
+#endif
diff --git a/kernel/test/kshell/commands.h b/kernel/test/kshell/commands.h
new file mode 100644
index 0000000..bf0bf1a
--- /dev/null
+++ b/kernel/test/kshell/commands.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "test/kshell/kshell.h"
+
+#define KSHELL_CMD(name) \
+ long kshell_##name(kshell_t *ksh, size_t argc, char **argv)
+
+KSHELL_CMD(help);
+
+KSHELL_CMD(exit);
+
+KSHELL_CMD(halt);
+
+KSHELL_CMD(echo);
+
+KSHELL_CMD(clear);
+
+#ifdef __VFS__
+KSHELL_CMD(cat);
+KSHELL_CMD(ls);
+KSHELL_CMD(cd);
+KSHELL_CMD(rm);
+KSHELL_CMD(link);
+KSHELL_CMD(rmdir);
+KSHELL_CMD(mkdir);
+KSHELL_CMD(stat);
+KSHELL_CMD(vfs_test);
+#endif
+
+#ifdef __S5FS__
+KSHELL_CMD(s5fstest);
+#endif
diff --git a/kernel/test/kshell/io.c b/kernel/test/kshell/io.c
new file mode 100644
index 0000000..65d816d
--- /dev/null
+++ b/kernel/test/kshell/io.c
@@ -0,0 +1,78 @@
+#include "test/kshell/io.h"
+#include "util/debug.h"
+
+#include "priv.h"
+
+#ifndef __VFS__
+
+#include "drivers/chardev.h"
+
+#endif
+
+#ifdef __VFS__
+
+#include "fs/vfs_syscall.h"
+
+#endif
+
+#include "util/printf.h"
+#include "util/string.h"
+
+/*
+ * If VFS is enabled, we can just use the syscalls.
+ *
+ * If VFS is not enabled, then we need to explicitly call the byte
+ * device.
+ */
+
+#ifdef __VFS__
+
+long kshell_write(kshell_t *ksh, const void *buf, size_t nbytes)
+{
+ long retval = do_write(ksh->ksh_out_fd, buf, nbytes);
+ KASSERT(retval < 0 || (size_t)retval == nbytes);
+ return retval;
+}
+
+long kshell_read(kshell_t *ksh, void *buf, size_t nbytes)
+{
+ return do_read(ksh->ksh_in_fd, buf, nbytes);
+}
+
+long kshell_write_all(kshell_t *ksh, void *buf, size_t nbytes)
+{
+ /* See comment in kshell_write */
+ return kshell_write(ksh, buf, nbytes);
+}
+
+#else
+
+long kshell_read(kshell_t *ksh, void *buf, size_t nbytes)
+{
+ return ksh->ksh_cd->cd_ops->read(ksh->ksh_cd, 0, buf, nbytes);
+}
+
+long kshell_write(kshell_t *ksh, const void *buf, size_t nbytes)
+{
+ return ksh->ksh_cd->cd_ops->write(ksh->ksh_cd, 0, buf, nbytes);
+}
+
+#endif
+
+void kprint(kshell_t *ksh, const char *fmt, va_list args)
+{
+ char buf[KSH_BUF_SIZE];
+ size_t count;
+
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ count = strnlen(buf, sizeof(buf));
+ kshell_write(ksh, buf, count);
+}
+
+void kprintf(kshell_t *ksh, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ kprint(ksh, fmt, args);
+ va_end(args);
+}
diff --git a/kernel/test/kshell/kshell.c b/kernel/test/kshell/kshell.c
new file mode 100644
index 0000000..a26c42c
--- /dev/null
+++ b/kernel/test/kshell/kshell.c
@@ -0,0 +1,504 @@
+#include "test/kshell/kshell.h"
+#include <util/printf.h>
+
+#include "config.h"
+
+#include "command.h"
+#include "commands.h"
+#include "tokenizer.h"
+
+#ifndef __VFS__
+
+#include "drivers/chardev.h"
+#include "drivers/tty/tty.h"
+
+#endif
+
+#include "mm/kmalloc.h"
+
+#include "proc/proc.h"
+
+#ifdef __VFS__
+
+#include "fs/fcntl.h"
+#include "fs/open.h"
+#include "fs/vfs_syscall.h"
+
+#endif
+
+#include "test/kshell/io.h"
+
+#include "util/debug.h"
+#include "util/string.h"
+
+void *kshell_proc_run(long tty, void *arg2)
+{
+ // Create kernel shell on given TTY
+ kshell_t *kshell = kshell_create((uint8_t)tty);
+ if (!kshell)
+ {
+ do_exit(-1);
+ }
+
+ while (kshell_execute_next(kshell) > 0)
+ ;
+ kshell_destroy(kshell);
+ return NULL;
+}
+
+void kshell_init()
+{
+ kshell_add_command("help", kshell_help,
+ "prints a list of available commands");
+ kshell_add_command("echo", kshell_echo, "display a line of text");
+ kshell_add_command("clear", kshell_clear, "clears the screen");
+#ifdef __VFS__
+ kshell_add_command("cat", kshell_cat,
+ "concatenate files and print on the standard output");
+ kshell_add_command("ls", kshell_ls, "list directory contents");
+ kshell_add_command("cd", kshell_cd, "change the working directory");
+ kshell_add_command("rm", kshell_rm, "remove files");
+ kshell_add_command("link", kshell_link,
+ "call the link function to create a link to a file");
+ kshell_add_command("rmdir", kshell_rmdir, "remove empty directories");
+ kshell_add_command("mkdir", kshell_mkdir, "make directories");
+ kshell_add_command("stat", kshell_stat, "display file status");
+ kshell_add_command("vfstest", kshell_vfs_test, "runs VFS tests");
+#endif
+
+#ifdef __S5FS__
+ kshell_add_command("s5fstest", kshell_s5fstest, "runs S5FS tests");
+#endif
+
+ kshell_add_command("halt", kshell_halt, "halts the systems");
+ kshell_add_command("exit", kshell_exit, "exits the shell");
+}
+
+void kshell_add_command(const char *name, kshell_cmd_func_t cmd_func,
+ const char *desc)
+{
+ kshell_command_t *cmd;
+
+ cmd = kshell_command_create(name, cmd_func, desc);
+ KASSERT(NULL != cmd);
+ list_insert_tail(&kshell_commands_list, &cmd->kc_commands_link);
+
+ dprintf("Added %s command\n", name);
+}
+
+kshell_t *kshell_create(uint8_t ttyid)
+{
+ kshell_t *ksh;
+
+ ksh = (kshell_t *)kmalloc(sizeof(kshell_t));
+ if (NULL == ksh)
+ {
+ dprintf("Not enough memory to create kshell\n");
+ return NULL;
+ }
+
+#ifdef __VFS__
+ long fd;
+ char tty_path[MAXPATHLEN];
+
+ snprintf(tty_path, sizeof(tty_path), "/dev/tty%u", ttyid);
+ if ((fd = do_open(tty_path, O_RDWR)) < 0)
+ {
+ dprintf("Couldn't open %s\n", tty_path);
+ kfree(ksh);
+ return NULL;
+ }
+ ksh->ksh_out_fd = ksh->ksh_in_fd = ksh->ksh_fd = (int)fd;
+#else
+ chardev_t *cd;
+ cd = chardev_lookup(MKDEVID(TTY_MAJOR, ttyid));
+ if (NULL == cd)
+ {
+ dprintf("Couldn't find TTY with ID %u\n", ttyid);
+ kfree(ksh);
+ return NULL;
+ }
+ ksh->ksh_cd = cd;
+#endif
+
+ dprintf("kshell successfully created on TTY %u\n", ttyid);
+ return ksh;
+}
+
+void kshell_destroy(kshell_t *ksh)
+{
+ KASSERT(NULL != ksh);
+ kprintf(ksh, "Bye!\n");
+#ifdef __VFS__
+ if (do_close(ksh->ksh_fd) < 0)
+ {
+ panic("Error closing TTY file descriptor\n");
+ }
+ dprintf("kshell with file descriptor %d destroyed\n", ksh->ksh_fd);
+#else
+ dprintf("kshell on byte device %u destroyed\n", ksh->ksh_cd->cd_id);
+#endif
+ kfree(ksh);
+}
+
+/**
+ * Removes the token from the input line it came from, replacing it
+ * with spaces.
+ *
+ * @param ksh the kshell
+ * @param token the token to scrub
+ */
+static void kshell_scrub_token(kshell_t *ksh, kshell_token_t *token)
+{
+ KASSERT(NULL != ksh);
+ KASSERT(NULL != token);
+ KASSERT(NULL != token->kt_text);
+
+ memset(token->kt_text, ' ', token->kt_textlen);
+}
+
+/**
+ * Finds the redirection operators ('<' and '>') in the input line,
+ * stores the name of the file to redirect stdout in in redirect_out
+ * and the name of the file to redirect stdin in redirect_in, and
+ * removes any trace of the redirection from the input line.
+ *
+ * @param ksh the kshell
+ * @param line the input line
+ * @param redirect_in buffer to store the name of the file to redirect
+ * stdin from. Buffer size assumed to be at least MAXPATHLEN
+ * @param redirect_out buffer to store the name of the file to stdout
+ * to. Buffer size assumed to be at least MAXPATHLEN
+ * @param append out parameter containing true if the file stdout is
+ * being redirected to should be appeneded to
+ * @return 0 on success and <0 on error
+ */
+static long kshell_find_redirection(kshell_t *ksh, char *line,
+ char *redirect_in, char *redirect_out,
+ int *append)
+{
+ long retval;
+ kshell_token_t token;
+
+ while ((retval = kshell_next_token(ksh, line, &token)) > 0)
+ {
+ KASSERT(token.kt_type != KTT_EOL);
+ line += retval;
+
+ if (token.kt_type == KTT_WORD)
+ {
+ continue;
+ }
+
+ char *redirect = NULL;
+ if (token.kt_type == KTT_REDIRECT_OUT)
+ {
+ redirect = redirect_out;
+ *append = 0;
+ }
+ else if (token.kt_type == KTT_REDIRECT_OUT_APPEND)
+ {
+ redirect = redirect_out;
+ *append = 1;
+ }
+ else if (token.kt_type == KTT_REDIRECT_IN)
+ {
+ redirect = redirect_in;
+ }
+ kshell_scrub_token(ksh, &token);
+
+ if ((retval = kshell_next_token(ksh, line, &token)) == 0)
+ {
+ goto unexpected_token;
+ }
+ KASSERT(retval > 0);
+
+ if (token.kt_type != KTT_WORD)
+ {
+ goto unexpected_token;
+ }
+ strncpy(redirect, token.kt_text, token.kt_textlen);
+ redirect[token.kt_textlen] = '\0';
+ kshell_scrub_token(ksh, &token);
+ }
+ return 0;
+
+unexpected_token:
+ kprintf(ksh, "kshell: Unexpected token '%s'\n",
+ kshell_token_type_str(token.kt_type));
+ return -1;
+}
+
+/**
+ * Ignoring whitespace, finds the next argument from a string.
+ *
+ * @param ksh the kshell
+ * @param line the string to find arguments in
+ * @param arg out parameter which should point to the beginning of the
+ * next argument if any were found
+ * @param arglen the length of the argument if any were found
+ * @return 0 if no argument was found, and the number of bytes read
+ * otherwise
+ */
+static long kshell_find_next_arg(kshell_t *ksh, char *line, char **arg,
+ size_t *arglen)
+{
+ long retval;
+ kshell_token_t token;
+
+ if ((retval = kshell_next_token(ksh, line, &token)) == 0)
+ {
+ KASSERT(token.kt_type == KTT_EOL);
+ return retval;
+ }
+ KASSERT(token.kt_type == KTT_WORD);
+ *arg = token.kt_text;
+ *arglen = token.kt_textlen;
+
+ /*
+ * This is a little hacky, but not awful.
+ *
+ * If we find a '\0', there are no more arguments
+ * left. However, we still need to return a nonzero value to
+ * alert the calling function about the argument we just
+ * found. Since there are no more arguments, we aren't
+ * overwriting anything by setting the next byte to '\0'. We
+ * also know that we aren't writing into invalid memory
+ * because in the struct definition for kshell_t, we declared
+ * ksh_buf to have KSH_BUF_SIZE + 1 bytes.
+ */
+ if (line[retval] == '\0')
+ {
+ line[retval + 1] = '\0';
+ }
+ else
+ {
+ line[retval] = '\0';
+ }
+ return retval;
+}
+
+/**
+ * Finds the arguments of the command just into a kshell. This should
+ * be called directly after returning from a read.
+ *
+ * @param buf the buffer to extract arguments from
+ * @param argv out parameter containing an array of null-terminated
+ * strings, one for each argument
+ * @param max_args the maximum number of arguments to find
+ * @param argc out parameter containing the number of arguments found
+ */
+static void kshell_get_args(kshell_t *ksh, char *buf, char **argv,
+ size_t max_args, size_t *argc)
+{
+ size_t arglen;
+
+ KASSERT(NULL != buf);
+ KASSERT(NULL != argv);
+ KASSERT(max_args > 0);
+ KASSERT(NULL != argc);
+
+ *argc = 0;
+ while (kshell_find_next_arg(ksh, buf, argv + *argc, &arglen) &&
+ *argc < max_args)
+ {
+ buf = argv[*argc] + arglen + 1;
+ ++(*argc);
+ }
+ if (*argc >= max_args)
+ {
+ dprintf("Too many arguments\n");
+ }
+}
+
+kshell_command_t *kshell_lookup_command(const char *name, size_t namelen)
+{
+ if (namelen > KSH_CMD_NAME_LEN)
+ {
+ namelen = KSH_CMD_NAME_LEN;
+ }
+
+ list_iterate(&kshell_commands_list, cmd, kshell_command_t,
+ kc_commands_link)
+ {
+ KASSERT(NULL != cmd);
+ if ((strncmp(cmd->kc_name, name, namelen) == 0) &&
+ (namelen == strnlen(cmd->kc_name, KSH_CMD_NAME_LEN)))
+ {
+ return cmd;
+ }
+ }
+ return NULL;
+}
+
+#ifdef __VFS__
+
+/**
+ * If stdin or stdout has been redirected to a file, closes the file
+ * and directs I/O back to stdin and stdout.
+ *
+ * @param the kshell
+ */
+void kshell_undirect(kshell_t *ksh)
+{
+ KASSERT(NULL != ksh);
+
+ if (ksh->ksh_in_fd != ksh->ksh_fd)
+ {
+ if (do_close(ksh->ksh_in_fd) < 0)
+ {
+ panic("kshell: Error closing file descriptor %d\n", ksh->ksh_in_fd);
+ }
+ ksh->ksh_in_fd = ksh->ksh_fd;
+ }
+ if (ksh->ksh_out_fd != ksh->ksh_fd)
+ {
+ if (do_close(ksh->ksh_out_fd) < 0)
+ {
+ panic("kshell: Error closing file descriptor %d\n",
+ ksh->ksh_out_fd);
+ }
+ ksh->ksh_out_fd = ksh->ksh_fd;
+ }
+}
+
+/**
+ * Redirects stdin and stdout.
+ *
+ * @param ksh the kshell
+ * @param redirect_in the name of the file to redirect stdin from
+ * @param redirect_out the name of the file to redirect stdout to
+ * @param append if true, output will be appended
+ * @return 0 on sucess and <0 on error. If returns with <0, no streams
+ * will be redirected.
+ */
+long kshell_redirect(kshell_t *ksh, const char *redirect_in,
+ const char *redirect_out, int append)
+{
+ long fd;
+
+ KASSERT(NULL != ksh);
+ KASSERT(NULL != redirect_in);
+ KASSERT(NULL != redirect_out);
+
+ if (redirect_in[0] != '\0')
+ {
+ if ((fd = do_open(redirect_in, O_RDONLY | O_CREAT)) < 0)
+ {
+ kprintf(ksh, "kshell: %s: Error opening file\n", redirect_in);
+ goto error;
+ }
+ ksh->ksh_in_fd = (int)fd;
+ }
+ if (redirect_out[0] != '\0')
+ {
+ int flags = append ? O_WRONLY | O_CREAT | O_APPEND : O_WRONLY | O_CREAT | O_TRUNC;
+ if ((fd = do_open(redirect_out, flags)) < 0)
+ {
+ kprintf(ksh, "kshell: %s: Error opening file\n", redirect_out);
+ goto error;
+ }
+ ksh->ksh_out_fd = fd;
+ }
+ return 0;
+
+error:
+ kshell_undirect(ksh);
+ return fd;
+}
+
+#endif
+
+long kshell_execute_next(kshell_t *ksh)
+{
+ static const char *kshell_prompt = "kshell$";
+
+ long nbytes, retval;
+ kshell_command_t *cmd;
+ char *args[KSH_MAX_ARGS];
+ size_t argc;
+ char redirect_in[MAXPATHLEN];
+ char redirect_out[MAXPATHLEN];
+ int append;
+
+ /*
+ * Need that extra byte at the end. See comment in
+ * kshell_find_next_arg.
+ */
+ char buf[KSH_BUF_SIZE + 1];
+
+ KASSERT(NULL != ksh);
+
+ kprintf(ksh, "%s ", kshell_prompt);
+
+ if ((nbytes = kshell_read(ksh, buf, KSH_BUF_SIZE)) <= 0)
+ {
+ return nbytes;
+ }
+ if (nbytes == 1)
+ {
+ return 1;
+ }
+ if (buf[nbytes - 1] == '\n')
+ {
+ /* Overwrite the newline with a null terminator */
+ buf[--nbytes] = '\0';
+ }
+ else
+ {
+ /* Add the null terminator to the end */
+ buf[nbytes] = '\0';
+ }
+
+ /* Even though we can't redirect I/O to files before VFS, we
+ * still want to scrub out any reference to redirection before
+ * passing the line off to kshell_get_args */
+ redirect_in[0] = redirect_out[0] = '\0';
+ if (kshell_find_redirection(ksh, buf, redirect_in, redirect_out, &append) <
+ 0)
+ {
+ goto done;
+ }
+#ifdef __VFS__
+ if ((retval = kshell_redirect(ksh, redirect_in, redirect_out, append)) <
+ 0)
+ {
+ dprintf("Error redirecting I/O\n");
+ goto done;
+ }
+#endif
+
+ kshell_get_args(ksh, buf, args, KSH_MAX_ARGS, &argc);
+ if (argc == 0)
+ {
+ goto done;
+ }
+
+ dprintf("Attempting to execute command '%s'\n", args[0]);
+
+ if (strncmp(args[0], "exit", strlen("exit")) == 0)
+ {
+ nbytes = 0;
+ goto done;
+ }
+
+ if ((cmd = kshell_lookup_command(args[0], strlen(args[0]))) == NULL)
+ {
+ kprintf(ksh, "kshell: %s not a valid command\n", args[0]);
+ }
+ else
+ {
+ if ((retval = cmd->kc_cmd_func(ksh, argc, args)) < 0)
+ {
+ nbytes = retval;
+ goto done;
+ }
+ }
+ goto done;
+
+done:
+#ifdef __VFS__
+ kshell_undirect(ksh);
+#endif
+ return nbytes;
+}
diff --git a/kernel/test/kshell/priv.h b/kernel/test/kshell/priv.h
new file mode 100644
index 0000000..65c9493
--- /dev/null
+++ b/kernel/test/kshell/priv.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "test/kshell/kshell.h"
+
+#include "util/list.h"
+
+#define dprintf(x, args...) dbg(DBG_TEST, x, ##args)
+
+#define KSH_BUF_SIZE \
+ 1024 /* This really just needs to be as large as \
+ * the line discipline buffer */
+#define KSH_CMD_NAME_LEN 16
+#define KSH_MAX_ARGS 128
+#define KSH_DESC_LEN 64
+
+struct chardev;
+struct kshell_command;
+
+struct kshell
+{
+ /* If we have a filesystem, we can write to the file
+ * descriptor. Otherwise, we need to write to a byte device */
+#ifdef __VFS__
+ int ksh_fd;
+
+ /* Used for redirection */
+ int ksh_out_fd;
+ int ksh_in_fd;
+#else
+ struct chardev *ksh_cd;
+#endif
+};
+
+extern list_t kshell_commands_list;
+
+/**
+ * Searches for a shell command with a specified name.
+ *
+ * @param name name of the command to search for
+ * @param namelen length of name
+ * @return the command, if it exists, or NULL
+ */
+struct kshell_command *kshell_lookup_command(const char *name, size_t namelen);
diff --git a/kernel/test/kshell/tokenizer.c b/kernel/test/kshell/tokenizer.c
new file mode 100644
index 0000000..9406668
--- /dev/null
+++ b/kernel/test/kshell/tokenizer.c
@@ -0,0 +1,74 @@
+#include "tokenizer.h"
+
+#include <ctype.h>
+
+#include "util/debug.h"
+
+#define EOL '\0'
+
+const char *ksh_tok_type_str[] = {"text", "<", ">", ">>", "end of line", ""};
+
+long kshell_next_token(kshell_t *ksh, char *line, kshell_token_t *token)
+{
+ KASSERT(NULL != ksh);
+ KASSERT(NULL != line);
+ KASSERT(NULL != token);
+
+ size_t i = 0;
+ while (line[i] != EOL && isspace(line[i]))
+ ++i;
+ token->kt_text = line + i;
+
+ /* Determine the token type */
+ switch (line[i])
+ {
+ case EOL:
+ token->kt_type = KTT_EOL;
+ token->kt_textlen = 0;
+ break;
+ case '<':
+ token->kt_type = KTT_REDIRECT_IN;
+ token->kt_textlen = i = 1;
+ break;
+ case '>':
+ if (line[i + 1] == '>')
+ {
+ token->kt_type = KTT_REDIRECT_OUT_APPEND;
+ token->kt_textlen = i = 2;
+ }
+ else
+ {
+ token->kt_type = KTT_REDIRECT_OUT;
+ token->kt_textlen = i = 1;
+ }
+ break;
+ default:
+ token->kt_type = KTT_WORD;
+ token->kt_textlen = 0;
+ break;
+ }
+
+ switch (token->kt_type)
+ {
+ case KTT_WORD:
+ while (!isspace(line[i]) && line[i] != '<' && line[i] != '>' &&
+ line[i] != EOL)
+ {
+ ++i;
+ ++token->kt_textlen;
+ }
+ break;
+ case KTT_EOL:
+ return 0;
+ default:
+ break;
+ }
+
+ return i;
+}
+
+const char *kshell_token_type_str(kshell_token_type_t type)
+{
+ KASSERT(type < KTT_MAX);
+ return ksh_tok_type_str[type];
+}
diff --git a/kernel/test/kshell/tokenizer.h b/kernel/test/kshell/tokenizer.h
new file mode 100644
index 0000000..9c49026
--- /dev/null
+++ b/kernel/test/kshell/tokenizer.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "types.h"
+
+#include "test/kshell/kshell.h"
+
+typedef enum kshell_token_type
+{
+ KTT_WORD,
+ KTT_REDIRECT_IN, /* '<' */
+ KTT_REDIRECT_OUT, /* '>' */
+ KTT_REDIRECT_OUT_APPEND, /* '>>' */
+ KTT_EOL,
+
+ KTT_MAX /* Number of token types */
+} kshell_token_type_t;
+
+typedef struct kshell_token
+{
+ kshell_token_type_t kt_type;
+ char *kt_text;
+ size_t kt_textlen;
+} kshell_token_t;
+
+/**
+ * Finds the next token in the input line.
+ *
+ * Note: To find multiple tokens from the same line, you increment the
+ * line pointer by the number of bytes processed before the next call
+ * to kshell_next token.
+ *
+ * @param ksh the kshell
+ * @param line the input line to tokenize
+ * @param token out parameter containing the next token found
+ * @return 0 if no more tokens, otherwise, number of bytes processed
+ */
+long kshell_next_token(kshell_t *ksh, char *line, kshell_token_t *token);
+
+const char *kshell_token_type_str(kshell_token_type_t type);
diff --git a/kernel/test/pipes.c b/kernel/test/pipes.c
new file mode 100644
index 0000000..ee4f195
--- /dev/null
+++ b/kernel/test/pipes.c
@@ -0,0 +1,133 @@
+#include "errno.h"
+#include "globals.h"
+
+#include "fs/file.h"
+#include "fs/pipe.h"
+#include "fs/vfs_syscall.h"
+
+#include "test/kshell/io.h"
+#include "test/kshell/kshell.h"
+
+#define IMAX 256
+#define JMAX 16
+#define KMAX 16
+#define ISTEP (JMAX * KMAX)
+
+static kthread_t *make_proc_and_thread(char *name, kthread_func_t func,
+ int arg1, void *arg2)
+{
+ proc_t *proc = proc_create(name);
+ if (!proc)
+ {
+ return NULL;
+ }
+
+ int i;
+ for (i = 0; i < NFILES; ++i)
+ {
+ proc->p_files[i] = curproc->p_files[i];
+ if (proc->p_files[i])
+ {
+ fref(proc->p_files[i]);
+ }
+ }
+ return kthread_create(proc, func, arg1, arg2);
+}
+
+static void *producer(long arg1, void *arg2)
+{
+ int fd = (int)arg1;
+ kshell_t *ksh = (kshell_t *)arg2;
+
+ kprintf(ksh, "Producing bytes...\n");
+
+ unsigned char buf[KMAX];
+ int i, j, k;
+ for (i = 0; i < IMAX; ++i)
+ {
+ for (j = 0; j < JMAX; ++j)
+ {
+ for (k = 0; k < KMAX; ++k)
+ {
+ buf[k] = (unsigned char)(i ^ (j * KMAX + k));
+ }
+ kprintf(ksh, "Writing bytes %d to %d\n", i * ISTEP + j * KMAX,
+ i * ISTEP + (j + 1) * KMAX);
+ if (do_write(fd, buf, KMAX) == -EPIPE)
+ {
+ kprintf(ksh, "Got EPIPE\n");
+ goto out;
+ }
+ }
+ kprintf(ksh, "Wrote %d bytes\n", (i + 1) * ISTEP);
+ }
+out:
+ return NULL;
+}
+
+static void *consumer(long arg1, void *arg2)
+{
+ int fd = (int)arg1;
+ kshell_t *ksh = (kshell_t *)arg2;
+
+ kprintf(ksh, "Consuming bytes...\n");
+ unsigned char buf[KMAX];
+ int i, j, k;
+ for (i = 0; i < IMAX; ++i)
+ {
+ for (j = 0; j < JMAX; ++j)
+ {
+ kprintf(ksh, "Reading bytes %d to %d\n", i * ISTEP + j * KMAX,
+ i * ISTEP + (j + 1) * KMAX);
+ if (do_read(fd, buf, KMAX) == 0)
+ {
+ kprintf(ksh, "End of pipe\n");
+ goto out;
+ }
+ for (k = 0; k < KMAX; ++k)
+ {
+ if (buf[k] != (i ^ (j * KMAX + k)))
+ {
+ kprintf(ksh, "Byte %d incorrect (expected %2x, got %2x)\n",
+ i * ISTEP + j * KMAX + k, (i ^ (j * KMAX + k)),
+ buf[k]);
+ }
+ }
+ }
+ kprintf(ksh, "Read %d bytes\n", (i + 1) * ISTEP);
+ }
+out:
+ return NULL;
+}
+
+static int test_pipes(kshell_t *ksh, int argc, char **argv)
+{
+ int pfds[2];
+ int err = do_pipe(pfds);
+ if (err < 0)
+ {
+ kprintf(ksh, "Failed to create pipe\n");
+ }
+ kprintf(ksh, "Created pipe with read fd %d and write fd %d\n", pfds[0],
+ pfds[1]);
+
+ sched_make_runnable(
+ make_proc_and_thread("producer", producer, pfds[1], ksh));
+ kprintf(ksh, "Created producer process\n");
+ sched_make_runnable(
+ make_proc_and_thread("consumer", consumer, pfds[0], ksh));
+ kprintf(ksh, "Created consumer process\n");
+
+ do_waitpid(-1, 0, 0);
+ do_waitpid(-1, 0, 0);
+ return 0;
+}
+
+#ifdef __PIPES__
+static __attribute__((unused)) void test_pipes_init()
+{
+ kshell_add_command("test_pipes", test_pipes, "run pipe tests");
+}
+init_func(test_pipes_init);
+init_depends(kshell_init);
+#endif /* __PIPES__ */
diff --git a/kernel/test/proctest.c b/kernel/test/proctest.c
new file mode 100644
index 0000000..31067cd
--- /dev/null
+++ b/kernel/test/proctest.c
@@ -0,0 +1,57 @@
+#include "errno.h"
+#include "globals.h"
+
+#include "test/proctest.h"
+#include "test/usertest.h"
+
+#include "util/debug.h"
+#include "util/printf.h"
+#include "util/string.h"
+
+#include "proc/kthread.h"
+#include "proc/proc.h"
+#include "proc/sched.h"
+
+/*
+ * Set up a testing function for the process to execute.
+*/
+void *test_func(long arg1, void *arg2)
+{
+ proc_t *proc_as_arg = (proc_t *)arg2;
+ test_assert(arg1 == proc_as_arg->p_pid, "Arguments are not set up correctly");
+ test_assert(proc_as_arg->p_state == PROC_RUNNING, "Process state is not running");
+ test_assert(list_empty(&proc_as_arg->p_children), "There should be no child processes");
+ return NULL;
+}
+
+void test_termination()
+{
+ int num_procs_created = 0;
+ proc_t *new_proc1 = proc_create("proc test 1");
+ kthread_t *new_kthread1 = kthread_create(new_proc1, test_func, 2, new_proc1);
+ num_procs_created++;
+ sched_make_runnable(new_kthread1);
+
+ int count = 0;
+ int status;
+ while (do_waitpid(-1, &status, 0) != -ECHILD)
+ {
+ test_assert(status == 0, "Returned status not set correctly");
+ count++;
+ }
+ test_assert(count == num_procs_created,
+ "Expected: %d, Actual: %d number of processes have been cleaned up\n", num_procs_created, count);
+}
+
+long proctest_main(long arg1, void *arg2)
+{
+ dbg(DBG_TEST, "\nStarting Procs tests\n");
+ test_init();
+ test_termination();
+
+ // Add more tests here!
+ // We highly recommend looking at section 3.8 on the handout for help!
+
+ test_fini();
+ return 0;
+} \ No newline at end of file
diff --git a/kernel/test/s5fstest.c b/kernel/test/s5fstest.c
new file mode 100644
index 0000000..c60ee32
--- /dev/null
+++ b/kernel/test/s5fstest.c
@@ -0,0 +1,251 @@
+//
+// Tests some edge cases of s5fs
+//
+
+#include "errno.h"
+#include "globals.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"
+
+#define BUFSIZE 256
+#define BIG_BUFSIZE 2056
+
+static void get_file_name(char *buf, size_t sz, long fileno)
+{
+ snprintf(buf, sz, "file%ld", fileno);
+}
+
+// Write to a fail forever until it is either filled up or we get an error.
+static long write_until_fail(int fd)
+{
+ size_t total_written = 0;
+ char buf[BIG_BUFSIZE] = {42};
+ while (total_written < S5_MAX_FILE_SIZE)
+ {
+ long 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 long is_first_n_bytes_zero(int fd, size_t n)
+{
+ size_t total_read = 0;
+ while (total_read < n)
+ {
+ size_t amt_to_read = MIN(BIG_BUFSIZE, n - total_read);
+ char buf[BIG_BUFSIZE] = {1};
+ long res = do_read(fd, buf, amt_to_read);
+ if ((size_t)res != amt_to_read)
+ {
+ dbg(DBG_TESTFAIL, "do_read result was %ld\n", res);
+ return 0;
+ }
+ total_read += res;
+
+ // Check everything that we read is indeed 0
+ // TODO use gcc intrinsic to just scan for first non-zero
+ for (size_t i = 0; i < amt_to_read; i++)
+ {
+ if (buf[i])
+ {
+ dbg(DBG_TESTFAIL, "buf contains char %d\n", buf[i]);
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static void test_running_out_of_inodes()
+{
+ // Open a ton of files until we get an error
+ long res;
+ long 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((int)res) == 0, "couldn't close");
+ }
+ else
+ {
+ break;
+ }
+ }
+ test_assert(res == -ENOSPC, "Did not get ENOSPC error");
+
+ // make sure mkdir fails now that we're out of inodes
+ test_assert(do_mkdir("directory") < 0, "do_mkdir worked!?");
+ test_assert(res == -ENOSPC, "unexpected error");
+
+ test_assert(do_mknod("nod", S_IFCHR, 123) != 0, "mknod worked!?");
+ test_assert(res == -ENOSPC, "wrong error code");
+
+ // 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 = (int)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()
+{
+ long res = 0;
+ int fd = (int)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");
+ test_assert(res == -EFBIG || res == -EINVAL, "Wrong error code");
+
+ 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()
+{
+ long res = 0;
+
+ int fd1 = (int)do_open("fullfile", O_RDWR | O_CREAT);
+
+ res = write_until_fail(fd1);
+ test_assert(res == 0, "Ran out of space quicker than we expected");
+ test_assert(do_close(fd1) == 0, "could not close");
+
+ int fd2 = (int)do_open("partiallyfullfile", O_RDWR | O_CREAT);
+ res = write_until_fail(fd2);
+ test_assert(res == -ENOSPC, "Did not get nospc error");
+
+ 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 = (int)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 size_t sz = strlen(b);
+
+ test_assert(do_lseek(fd, addr, SEEK_SET) == addr, "couldnt seek");
+ test_assert((size_t)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 for direct blocks failed");
+
+ // 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;
+}
+
+static int test_sparseness_indirect_blocks()
+{
+ const char *filename = "bigsparsefile";
+ int fd = (int)do_open(filename, O_RDWR | O_CREAT);
+
+ // Now write to some random address that'll be in an indirect block
+ const int addr = 1000000;
+ const char *b = "iboros";
+ const size_t sz = strlen(b);
+
+ test_assert(do_lseek(fd, addr, SEEK_SET) == addr, "couldnt seek");
+ test_assert((size_t)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 for indirect blocks failed");
+
+ // 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;
+}
+
+long s5fstest_main(int arg0, void *arg1)
+{
+ dbg(DBG_TEST, "\nStarting S5FS test\n");
+
+ test_init();
+
+ 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;
+} \ No newline at end of file
diff --git a/kernel/test/usertest.c b/kernel/test/usertest.c
new file mode 100644
index 0000000..aa3c231
--- /dev/null
+++ b/kernel/test/usertest.c
@@ -0,0 +1,174 @@
+#include "kernel.h"
+#include "stdarg.h"
+
+#include "test/usertest.h"
+
+#include "util/debug.h"
+#include "util/printf.h"
+
+typedef struct test_data
+{
+ int td_passed;
+ int td_failed;
+} test_data_t;
+
+static void _default_test_fail(const char *file, int line, const char *name,
+ const char *fmt, va_list args);
+
+static void _default_test_pass(int val, const char *file, int line,
+ const char *name, const char *fmt, va_list args);
+
+static test_data_t _test_data;
+static test_pass_func_t _pass_func = _default_test_pass;
+static test_fail_func_t _fail_func = _default_test_fail;
+
+void test_init(void)
+{
+ _test_data.td_passed = 0;
+ _test_data.td_failed = 0;
+}
+
+void test_fini(void)
+{
+ dbgq(DBG_TEST, "tests completed:\n");
+ dbgq(DBG_TEST, "\t\t%d passed\n", _test_data.td_passed);
+ dbgq(DBG_TEST, "\t\t%d failed\n", _test_data.td_failed);
+}
+
+const char *test_errstr(int err)
+{
+ switch (err)
+ {
+ case 1:
+ return "EPERM";
+ case 2:
+ return "ENOENT";
+ case 3:
+ return "ESRCH";
+ case 4:
+ return "EINTR";
+ case 5:
+ return "EIO";
+ case 6:
+ return "ENXIO";
+ case 7:
+ return "E2BIG";
+ case 8:
+ return "ENOEXEC";
+ case 9:
+ return "EBADF";
+ case 10:
+ return "ECHILD";
+ case 11:
+ return "EAGAIN";
+ case 12:
+ return "ENOMEM";
+ case 13:
+ return "EACCES";
+ case 14:
+ return "EFAULT";
+ case 15:
+ return "ENOTBLK";
+ case 16:
+ return "EBUSY";
+ case 17:
+ return "EEXIST";
+ case 18:
+ return "EXDEV";
+ case 19:
+ return "ENODEV";
+ case 20:
+ return "ENOTDIR";
+ case 21:
+ return "EISDIR";
+ case 22:
+ return "EINVAL";
+ case 23:
+ return "ENFILE";
+ case 24:
+ return "EMFILE";
+ case 25:
+ return "ENOTTY";
+ case 26:
+ return "ETXTBSY";
+ case 27:
+ return "EFBIG";
+ case 28:
+ return "ENOSPC";
+ case 29:
+ return "ESPIPE";
+ case 30:
+ return "EROFS";
+ case 31:
+ return "EMLINK";
+ case 32:
+ return "EPIPE";
+ case 33:
+ return "EDOM";
+ case 34:
+ return "ERANGE";
+ case 35:
+ return "EDEADLK";
+ case 36:
+ return "ENAMETOOLONG";
+ case 37:
+ return "ENOLCK";
+ case 38:
+ return "ENOSYS";
+ case 39:
+ return "ENOTEMPTY";
+ case 40:
+ return "ELOOP";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void _default_test_fail(const char *file, int line, const char *name,
+ const char *fmt, va_list args)
+{
+ _test_data.td_failed++;
+ if (NULL == fmt)
+ {
+ dbgq(DBG_TEST, "FAILED: %s(%d): %s\n", file, line, name);
+ }
+ else
+ {
+ char buf[2048];
+ vsnprintf(buf, sizeof(buf), fmt, args);
+ buf[2047] = '\0';
+ dbgq(DBG_TEST, "FAILED: %s(%d): %s: %s\n", file, line, name, buf);
+ }
+}
+
+static void _default_test_pass(int val, const char *file, int line,
+ const char *name, const char *fmt,
+ va_list args)
+{
+ _test_data.td_passed++;
+}
+
+int _test_assert(int val, const char *file, int line, const char *name,
+ const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ if (0 == val)
+ {
+ if (NULL != _fail_func)
+ {
+ _fail_func(file, line, name, fmt, args);
+ }
+ }
+ else
+ {
+ if (NULL != _pass_func)
+ {
+ _pass_func(val, file, line, name, fmt, args);
+ }
+ }
+
+ va_end(args);
+ return val;
+}
diff --git a/kernel/test/vfstest/vfstest.c b/kernel/test/vfstest/vfstest.c
new file mode 100644
index 0000000..dba2ff4
--- /dev/null
+++ b/kernel/test/vfstest/vfstest.c
@@ -0,0 +1,1173 @@
+#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", 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));
+ while (0 < res)
+ {
+ 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/kernel/test/vmtest.c b/kernel/test/vmtest.c
new file mode 100644
index 0000000..9ffa4c6
--- /dev/null
+++ b/kernel/test/vmtest.c
@@ -0,0 +1,74 @@
+#include "errno.h"
+#include "globals.h"
+
+#include "test/usertest.h"
+#include "test/proctest.h"
+
+#include "util/debug.h"
+#include "util/printf.h"
+#include "util/string.h"
+
+#include "mm/mm.h"
+#include "mm/page.h"
+#include "mm/slab.h"
+#include "mm/kmalloc.h"
+#include "vm/vmmap.h"
+
+long test_vmmap() {
+ vmmap_t *map = curproc->p_vmmap;
+
+ // Make sure we start out cleanly
+ KASSERT(vmmap_is_range_empty(map, ADDR_TO_PN(USER_MEM_LOW), ADDR_TO_PN(USER_MEM_HIGH - USER_MEM_LOW)));
+
+ // Go through the address space, make sure we find nothing
+ for (size_t i = USER_MEM_LOW; i < ADDR_TO_PN(USER_MEM_HIGH); i += PAGE_SIZE) {
+ KASSERT(!vmmap_lookup(map, i));
+ }
+
+ // You can probably change this.
+ size_t num_vmareas = 5;
+ // Probably shouldn't change this to anything that's not a power of two.
+ size_t num_pages_per_vmarea = 16;
+
+ size_t prev_start = ADDR_TO_PN(USER_MEM_HIGH);
+ for (size_t i = 0; i < num_vmareas; i++) {
+ ssize_t start = vmmap_find_range(map, num_pages_per_vmarea, VMMAP_DIR_HILO);
+ test_assert(start + num_pages_per_vmarea == prev_start, "Incorrect return value from vmmap_find_range");
+
+ vmarea_t *vma = kmalloc(sizeof(vmarea_t));
+ KASSERT(vma && "Unable to alloc the vmarea");
+ memset(vma, 0, sizeof(vmarea_t));
+
+ vma->vma_start = start;
+ vma->vma_end = start + num_pages_per_vmarea;
+ vmmap_insert(map, vma);
+
+ prev_start = start;
+ }
+
+ // Now, our address space should look like:
+ // EMPTY EMPTY EMPTY [ ][ ][ ][ ][ ]
+ // ^LP
+ // ^HP
+ // ^section_start
+ // HP --> the highest possible userland page number
+ // LP --> the lowest possible userland page number
+ // section start --> HP - (num_vmareas * num_pages_per_vmarea)
+
+ list_iterate(&map->vmm_list, vma, vmarea_t, vma_plink) {
+ list_remove(&vma->vma_plink);
+ kfree(vma);
+ }
+
+ return 0;
+}
+
+long vmtest_main(long arg1, void* arg2) {
+ test_init();
+ test_vmmap();
+
+ // Write your own tests here!
+
+ test_fini();
+ return 0;
+}