diff options
author | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
---|---|---|
committer | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
commit | c63f340d90800895f007de64b7d2d14624263331 (patch) | |
tree | 2c0849fa597dd6da831c8707b6f2603403778d7b /kernel/test |
Created student weenix repository
Diffstat (limited to 'kernel/test')
-rw-r--r-- | kernel/test/Submodules | 1 | ||||
-rw-r--r-- | kernel/test/driverstest.c | 288 | ||||
-rw-r--r-- | kernel/test/kshell/command.c | 46 | ||||
-rw-r--r-- | kernel/test/kshell/command.h | 20 | ||||
-rw-r--r-- | kernel/test/kshell/commands.c | 404 | ||||
-rw-r--r-- | kernel/test/kshell/commands.h | 32 | ||||
-rw-r--r-- | kernel/test/kshell/io.c | 78 | ||||
-rw-r--r-- | kernel/test/kshell/kshell.c | 504 | ||||
-rw-r--r-- | kernel/test/kshell/priv.h | 43 | ||||
-rw-r--r-- | kernel/test/kshell/tokenizer.c | 74 | ||||
-rw-r--r-- | kernel/test/kshell/tokenizer.h | 39 | ||||
-rw-r--r-- | kernel/test/pipes.c | 133 | ||||
-rw-r--r-- | kernel/test/proctest.c | 57 | ||||
-rw-r--r-- | kernel/test/s5fstest.c | 251 | ||||
-rw-r--r-- | kernel/test/usertest.c | 174 | ||||
-rw-r--r-- | kernel/test/vfstest/vfstest.c | 1173 | ||||
-rw-r--r-- | kernel/test/vmtest.c | 74 |
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; +} |