diff options
Diffstat (limited to 'user/usr/bin/tests/vfstest.c')
-rw-r--r-- | user/usr/bin/tests/vfstest.c | 1172 |
1 files changed, 1172 insertions, 0 deletions
diff --git a/user/usr/bin/tests/vfstest.c b/user/usr/bin/tests/vfstest.c new file mode 100644 index 0000000..ed9ca44 --- /dev/null +++ b/user/usr/bin/tests/vfstest.c @@ -0,0 +1,1172 @@ +#ifdef __KERNEL__ + +#include "config.h" +#include "errno.h" +#include "globals.h" +#include "kernel.h" +#include "limits.h" + +#include "util/debug.h" +#include "util/printf.h" +#include "util/string.h" + +#include "proc/kthread.h" +#include "proc/proc.h" + +#include "fs/dirent.h" +#include "fs/fcntl.h" +#include "fs/lseek.h" +#include "fs/stat.h" +#include "fs/vfs_syscall.h" +#include "mm/kmalloc.h" +#include "mm/mman.h" + +#include "test/usertest.h" +#include "test/vfstest/vfstest.h" + +#undef __VM__ + +#else + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> +#include <weenix/syscall.h> + +#include <test/test.h> + +#endif + +/* Some helpful strings */ +#define LONGNAME "supercalifragilisticexpialidocious" /* Longer than NAME_LEN \ + */ + +#define TESTSTR \ + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " \ + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad " \ + "minim " \ + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " \ + "commodo " \ + "consequat. Duis aute irure dolor in reprehenderit in voluptate velit " \ + "esse cillum " \ + "dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " \ + "proident, " \ + "sunt in culpa qui officia deserunt mollit anim id est laborum." + +#define SHORTSTR "Quidquid latine dictum, altum videtur" + +static char root_dir[64]; + +static int makedirs(const char *dir) +{ + int ret = 0; + char *d, *p; + + if (NULL == (d = malloc(strlen(dir) + 1))) + { + return ENOMEM; + } + strcpy(d, dir); + + p = d; + while (NULL != (p = strchr(p + 1, '/'))) + { + *p = '\0'; + if (0 != mkdir(d, 0777) && EEXIST != errno) + { + ret = errno; + goto error; + } + *p = '/'; + } + if (0 != mkdir(d, 0777) && EEXIST != errno) + { + ret = errno; + goto error; + } + +error: + free(d); + return ret; +} + +static int getdent(const char *dir, dirent_t *dirent) +{ + int ret, fd = -1; + + if (0 > (fd = open(dir, O_RDONLY, 0777))) + { + return -1; + } + + ret = 1; + while (ret != 0) + { + if (0 > (ret = getdents(fd, dirent, sizeof(*dirent)))) + { + return -1; + } + if (0 != strcmp(".", dirent->d_name) && + 0 != strcmp("..", dirent->d_name)) + { + close(fd); + return 1; + } + } + + close(fd); + return 0; +} + +static int removeall(const char *dir) +{ + int ret; + dirent_t dirent; + stat_t status; + + if (0 > chdir(dir)) + { + return errno; + } + + ret = 1; + while (ret != 0) + { + if (0 > (ret = getdent(".", &dirent))) + { + return errno; + } + if (0 == ret) + { + break; + } + + if (0 > stat(dirent.d_name, &status)) + { + return errno; + } + + if (S_ISDIR(status.st_mode)) + { + if (0 > removeall(dirent.d_name)) + { + return errno; + } + } + else + { + if (0 > unlink(dirent.d_name)) + { + return errno; + } + } + } + + if (0 > chdir("..")) + { + return errno; + } + + if (0 > rmdir(dir)) + { + return errno; + } + + return 0; +} + +static void vfstest_start(void) +{ + int err; + + root_dir[0] = '\0'; + do + { + snprintf(root_dir, sizeof(root_dir), "vfstest-%d-%d", getpid(), rand()); + err = mkdir(root_dir, 0777); + + if (errno == EEXIST) + { + break; + } + + if (err && errno != EEXIST) + { + printf("Failed to make test root directory: %s\n", strerror(errno)); + exit(errno); + } + } while (err != 0); + printf("Created test root directory: ./%s\n", root_dir); +} + +/* + * Terminates the testing environment + */ +static void vfstest_term(void) +{ + if (0 != removeall(root_dir)) + { + fprintf(stderr, "ERROR: could not remove testing root %s: %s\n", + root_dir, strerror(errno)); + exit(-1); + } + printf("Removed test root directory: ./%s\n", root_dir); +} + +#define paths_equal(p1, p2) \ + do \ + { \ + int __r; \ + stat_t __s1, __s2; \ + if (__r = makedirs(p1), !test_assert(0 == __r, "makedirs(\"%s\"): %s", \ + p1, test_errstr(__r))) \ + break; \ + if (__r = stat(p1, &__s1), !test_assert(0 == __r, "stat(\"%s\"): %s", \ + p1, test_errstr(errno))) \ + break; \ + if (__r = stat(p2, &__s2), !test_assert(0 == __r, "stat(\"%s\"): %s", \ + p2, test_errstr(errno))) \ + break; \ + test_assert(__s1.st_ino == __s2.st_ino, \ + "paths_equals(\"%s\" (ino %d), \"%s\" (ino %d))", p1, \ + __s1.st_ino, p2, __s2.st_ino); \ + } while (0); + +#define syscall_fail(expr, err) \ + (test_assert((errno = 0, -1 == (expr)), \ + "\nunexpected success, wanted %s (%d)", test_errstr(err), \ + err) \ + ? test_assert((expr, errno == err), \ + "\nexpected %s (%d)" \ + "\ngot %s (%d)", \ + test_errstr(err), err, test_errstr(errno), errno) \ + : 0) +#define syscall_success(expr) \ + test_assert(0 <= (expr), "\nunexpected error: %s (%d)", \ + test_errstr(errno), errno) + +#define create_file(file) \ + do \ + { \ + int __fd; \ + if (syscall_success(__fd = open((file), O_RDONLY | O_CREAT, 0777))) \ + { \ + syscall_success(close(__fd)); \ + } \ + } while (0); +#define read_fd(fd, size, goal) \ + do \ + { \ + char __buf[64]; \ + test_assert((ssize_t)strlen(goal) == read(fd, __buf, size), \ + "\nread unexpected number of bytes"); \ + test_assert(0 == memcmp(__buf, goal, strlen(goal)), \ + "\nread data incorrect"); \ + } while (0); +#define test_fpos(fd, exp) \ + do \ + { \ + int __g, __e = (exp); \ + syscall_success(__g = lseek(fd, 0, SEEK_CUR)); \ + test_assert((__g == __e), "fd %d fpos at %d, expected %d", fd, __g, \ + __e); \ + } while (0); + +static void vfstest_notdir(void) +{ + int fd; + stat_t s; + syscall_success(mkdir("realdir", 0)); + syscall_success(fd = open("realdir/file", O_RDWR | O_CREAT, 0)); + syscall_success(close(fd)); + syscall_success(fd = open("realdir/file2", O_RDWR | O_CREAT, 0)); + syscall_success(close(fd)); + + syscall_fail(open("realdir/file/nope", O_CREAT | O_RDWR, 0), ENOTDIR); + syscall_fail(link("realdir/file2", "realdir/file/nope"), ENOTDIR); + syscall_fail(link("realdir/file/nope", "realdir/file3"), ENOTDIR); + syscall_fail(unlink("realdir/file/nope"), ENOTDIR); + syscall_fail(rmdir("realdir/file/nope"), ENOTDIR); + syscall_fail(stat("realdir/file/nope", &s), ENOTDIR); + syscall_fail(rename("realdir/file2", "realdir/file/nope"), ENOTDIR); + syscall_fail(rename("realdir/file/nope", "realdir/file3"), ENOTDIR); + + /* Cleanup */ + syscall_success(unlink("realdir/file")); + syscall_success(unlink("realdir/file2")); + syscall_success(rmdir("realdir")); +} + +static void vfstest_stat(void) +{ + int fd; + stat_t s; + + syscall_success(mkdir("stat", 0)); + syscall_success(chdir("stat")); + + syscall_success(stat(".", &s)); + test_assert(S_ISDIR(s.st_mode), NULL); + + create_file("file"); + syscall_success(stat("file", &s)); + test_assert(S_ISREG(s.st_mode), NULL); + + /* file size is correct */ + syscall_success(fd = open("file", O_RDWR, 0)); + syscall_success(write(fd, "foobar", 6)); + syscall_success(stat("file", &s)); + test_assert(s.st_size == 6, "unexpected file size"); + syscall_success(close(fd)); + + /* error cases */ +#ifdef __VM__ + syscall_fail(stat(".", NULL), EFAULT); +#endif + syscall_fail(stat("noent", &s), ENOENT); + + syscall_success(chdir("..")); +} + +static void vfstest_mkdir(void) +{ + syscall_success(mkdir("mkdir", 0777)); + syscall_success(chdir("mkdir")); + + /* mkdir an existing file or directory */ + create_file("file"); + syscall_fail(mkdir("file", 0777), EEXIST); + syscall_success(mkdir("dir", 0777)); + syscall_fail(mkdir("dir", 0777), EEXIST); + + /* mkdir an invalid path */ + syscall_fail(mkdir(LONGNAME, 0777), ENAMETOOLONG); + syscall_fail(mkdir("file/dir", 0777), ENOTDIR); + syscall_fail(mkdir("noent/dir", 0777), ENOENT); + syscall_fail(rmdir("file/dir"), ENOTDIR); + syscall_fail(rmdir("noent/dir"), ENOENT); + syscall_fail(rmdir("noent"), ENOENT); + syscall_fail(rmdir("."), EINVAL); + syscall_fail(rmdir(".."), ENOTEMPTY); + syscall_fail(rmdir("dir/."), EINVAL); + syscall_fail(rmdir("dir/.."), ENOTEMPTY); + syscall_fail(rmdir("noent/."), ENOENT); + syscall_fail(rmdir("noent/.."), ENOENT); + + /* unlink and rmdir the inappropriate types */ + syscall_fail(rmdir("file"), ENOTDIR); + syscall_fail(unlink("dir"), EPERM); + + /* remove non-empty directory */ + create_file("dir/file"); + syscall_fail(rmdir("dir"), ENOTEMPTY); + + /* remove empty directory */ + syscall_success(unlink("dir/file")); + syscall_success(rmdir("dir")); + + syscall_success(chdir("..")); +} + +static void vfstest_chdir(void) +{ +#define CHDIR_TEST_DIR "chdir" + + stat_t ssrc, sdest, sparent, sdir; + stat_t rsrc, rdir; + + /* chdir back and forth to CHDIR_TEST_DIR */ + syscall_success(mkdir(CHDIR_TEST_DIR, 0777)); + syscall_success(stat(".", &ssrc)); + syscall_success(stat(CHDIR_TEST_DIR, &sdir)); + + test_assert(ssrc.st_ino != sdir.st_ino, NULL); + + syscall_success(chdir(CHDIR_TEST_DIR)); + syscall_success(stat(".", &sdest)); + syscall_success(stat("..", &sparent)); + + test_assert(sdest.st_ino == sdir.st_ino, NULL); + test_assert(ssrc.st_ino == sparent.st_ino, NULL); + test_assert(ssrc.st_ino != sdest.st_ino, NULL); + + syscall_success(chdir("..")); + syscall_success(stat(".", &rsrc)); + syscall_success(stat(CHDIR_TEST_DIR, &rdir)); + + test_assert(rsrc.st_ino == ssrc.st_ino, NULL); + test_assert(rdir.st_ino == sdir.st_ino, NULL); + + /* can't chdir into non-directory */ + syscall_success(chdir(CHDIR_TEST_DIR)); + create_file("file"); + syscall_fail(chdir("file"), ENOTDIR); + syscall_fail(chdir("noent"), ENOENT); + syscall_success(chdir("..")); +} + +static void vfstest_paths(void) +{ +#define PATHS_TEST_DIR "paths" + + stat_t s; + + syscall_success(mkdir(PATHS_TEST_DIR, 0777)); + syscall_success(chdir(PATHS_TEST_DIR)); + + syscall_fail(stat("", &s), EINVAL); + + paths_equal(".", "."); + paths_equal("1/2/3", "1/2/3"); + paths_equal("4/5/6", "4/5/6"); + + /* root directory */ + paths_equal("/", "/"); + paths_equal("/", "/.."); + paths_equal("/", "/../"); + paths_equal("/", "/../."); + + /* . and .. */ + paths_equal(".", "./."); + paths_equal(".", "1/.."); + paths_equal(".", "1/../"); + paths_equal(".", "1/2/../.."); + paths_equal(".", "1/2/../.."); + paths_equal(".", "1/2/3/../../.."); + paths_equal(".", "1/../1/.."); + paths_equal(".", "1/../4/.."); + paths_equal(".", "1/../1/.."); + paths_equal(".", "1/2/3/../../../4/5/6/../../.."); + paths_equal(".", "1/./2/./3/./.././.././.././4/./5/./6/./.././.././.."); + + /* extra slashes */ + paths_equal("1/2/3", "1/2/3/"); + paths_equal("1/2/3", "1//2/3"); + paths_equal("1/2/3", "1/2//3"); + paths_equal("1/2/3", "1//2//3"); + paths_equal("1/2/3", "1//2//3/"); + paths_equal("1/2/3", "1///2///3///"); + + /* strange names */ + paths_equal("-", "-"); + paths_equal(" ", " "); + paths_equal("\\", "\\"); + paths_equal("0", "0"); + + stat_t st; + + /* error cases */ + syscall_fail(stat("asdf", &st), ENOENT); + syscall_fail(stat("1/asdf", &st), ENOENT); + syscall_fail(stat("1/../asdf", &st), ENOENT); + syscall_fail(stat("1/2/asdf", &st), ENOENT); + + create_file("1/file"); + syscall_fail(open("1/file/other", O_RDONLY, 0777), ENOTDIR); + syscall_fail(open("1/file/other", O_RDONLY | O_CREAT, 0777), ENOTDIR); + + syscall_success(chdir("..")); +} + +static void vfstest_fd(void) +{ +#define FD_BUFSIZE 5 +#define BAD_FD 20 +#define HUGE_FD 9999 + + int fd1, fd2; + char buf[FD_BUFSIZE]; + struct dirent d; + + syscall_success(mkdir("fd", 0)); + syscall_success(chdir("fd")); + + /* read/write/close/getdents/dup nonexistent file descriptors */ + syscall_fail(read(BAD_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(read(HUGE_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(read(-1, buf, FD_BUFSIZE), EBADF); + + syscall_fail(write(BAD_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(write(HUGE_FD, buf, FD_BUFSIZE), EBADF); + syscall_fail(write(-1, buf, FD_BUFSIZE), EBADF); + + syscall_fail(close(BAD_FD), EBADF); + syscall_fail(close(HUGE_FD), EBADF); + syscall_fail(close(-1), EBADF); + + syscall_fail(lseek(BAD_FD, 0, SEEK_SET), EBADF); + syscall_fail(lseek(HUGE_FD, 0, SEEK_SET), EBADF); + syscall_fail(lseek(-1, 0, SEEK_SET), EBADF); + + syscall_fail(getdents(BAD_FD, &d, sizeof(d)), EBADF); + syscall_fail(getdents(HUGE_FD, &d, sizeof(d)), EBADF); + syscall_fail(getdents(-1, &d, sizeof(d)), EBADF); + + syscall_fail(dup(BAD_FD), EBADF); + syscall_fail(dup(HUGE_FD), EBADF); + syscall_fail(dup(-1), EBADF); + + syscall_fail(dup2(BAD_FD, 25), EBADF); + syscall_fail(dup2(HUGE_FD, 25), EBADF); + syscall_fail(dup2(-1, 25), EBADF); + + /* dup2 has some extra cases since it takes a second fd */ + syscall_fail(dup2(0, HUGE_FD), EBADF); + syscall_fail(dup2(0, -1), EBADF); + + /* if the fds are equal, but the first is invalid or out of the + * allowed range */ + syscall_fail(dup2(BAD_FD, BAD_FD), EBADF); + syscall_fail(dup2(HUGE_FD, HUGE_FD), EBADF); + syscall_fail(dup2(-1, -1), EBADF); + + /* dup works properly in normal usage */ + create_file("file01"); + syscall_success(fd1 = open("file01", O_RDWR, 0)); + syscall_success(fd2 = dup(fd1)); + test_assert(fd1 < fd2, "dup(%d) returned %d", fd1, fd2); + syscall_success(write(fd2, "hello", 5)); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(lseek(fd2, 0, SEEK_SET)); + test_fpos(fd1, 0); + test_fpos(fd2, 0); + read_fd(fd1, 5, "hello"); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(close(fd2)); + + /* dup2 works properly in normal usage */ + syscall_success(fd2 = dup2(fd1, 25)); + test_assert(25 == fd2, "dup2(%d, 25) returned %d", fd1, fd2); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(lseek(fd2, 0, SEEK_SET)); + test_fpos(fd1, 0); + test_fpos(fd2, 0); + syscall_success(close(fd2)); + + /* dup2-ing a file to itself works */ + syscall_success(fd2 = dup2(fd1, fd1)); + test_assert(fd1 == fd2, "dup2(%d, %d) returned %d", fd1, fd1, fd2); + + /* dup2 closes previous file */ + int fd3; + create_file("file02"); + syscall_success(fd3 = open("file02", O_RDWR, 0)); + syscall_success(fd2 = dup2(fd1, fd3)); + test_assert(fd2 == fd3, "dup2(%d, %d) returned %d", fd1, fd3, fd2); + test_fpos(fd1, 0); + test_fpos(fd2, 0); + syscall_success(lseek(fd2, 5, SEEK_SET)); + test_fpos(fd1, 5); + test_fpos(fd2, 5); + syscall_success(close(fd2)); + syscall_success(close(fd1)); + + syscall_success(chdir("..")); +} + +static void vfstest_memdev(void) +{ + int res, fd; + char def = 'a'; + char buf[4096]; + + res = 1; + + memset(buf, def, sizeof(buf)); + + syscall_success(fd = open("/dev/null", O_RDWR, 0)); + syscall_success(res = write(fd, buf, sizeof(buf))); + test_assert(sizeof(buf) == res, "write of %d bytes /dev/null returned %d", + sizeof(buf), res); + syscall_success(res = read(fd, buf, sizeof(buf))); + test_assert(0 == res, "read of %d bytes /dev/null returned %d", sizeof(buf), + res); + test_assert(buf[sizeof(buf) / 2] == def, + "read from /dev/null changed buffer"); + syscall_success(close(fd)); + + memset(buf, def, sizeof(buf)); + + syscall_success(fd = open("/dev/zero", O_RDWR, 0)); + syscall_success(res = write(fd, buf, sizeof(buf))); + test_assert(sizeof(buf) == res, "write of %d bytes /dev/zero returned %d", + sizeof(buf), res); + syscall_success(res = read(fd, buf, sizeof(buf))); + test_assert(sizeof(buf) == res, "read of %d bytes /dev/zero returned %d", + sizeof(buf), res); + test_assert(buf[sizeof(buf) / 2] == 0, + "read from /dev/zero doesn't zero buffer"); + syscall_success(close(fd)); +} + +static void vfstest_write(void) +{ +#define CHUNK_SIZE 25 +#define NUM_CHUNKS 4 + int fd, i, res; + stat_t s; + const char *str = "hello world"; + + char chunk[CHUNK_SIZE]; + memcpy(chunk, str, strlen(str)); + memset(chunk + strlen(str), 0, 25 - strlen(str)); + + syscall_success(mkdir("write", 0)); + syscall_success(chdir("write")); + + create_file("file"); + syscall_success(fd = open("file", O_RDWR, 0)); + for (i = 0; i < NUM_CHUNKS * CHUNK_SIZE; i += CHUNK_SIZE) + { + syscall_success(lseek(fd, i, SEEK_SET)); + syscall_success(res = write(fd, str, strlen(str))); + test_assert((int)strlen(str) == res, "write of %d bytes returned %d", + strlen(str), res); + } + syscall_success(lseek(fd, 0, SEEK_SET)); + for (i = 0; i < NUM_CHUNKS - 1; ++i) + { + char __buf[64]; + test_assert(CHUNK_SIZE == read(fd, __buf, CHUNK_SIZE), + "\nread unexpected number of bytes"); + test_assert(0 == memcmp(__buf, chunk, CHUNK_SIZE), + "\nread data incorrect"); + } + char __buf[64]; + test_assert((int)strlen(str) == read(fd, __buf, strlen(str)), + "\nread unexpected number of bytes"); + test_assert(0 == memcmp(__buf, chunk, strlen(str)), + "\nread data incorrect"); + + const char *new_str = "testing"; + const int loc = 37; + // writing to middle of file + // make sure file size doesn't change and the write is done at the correct + // location + syscall_success(lseek(fd, loc, SEEK_SET)); + syscall_success(res = write(fd, new_str, strlen(new_str))); + test_assert((int)strlen(new_str) == res, "write of %d bytes returned %d", + strlen(new_str), res); + syscall_success(lseek(fd, loc, SEEK_SET)); + read_fd(fd, strlen(new_str), new_str); + test_assert(lseek(fd, 0, SEEK_END) == + (NUM_CHUNKS - 1) * CHUNK_SIZE + (int)strlen(str), + "file is not the right size"); + + syscall_success(close(fd)); + syscall_success(unlink("file")); + + syscall_success(chdir("..")); + syscall_success(rmdir("write")); +} + +/* These operations should run for a long time and halt when the file + * descriptor overflows. */ +static void vfstest_infinite(void) +{ + int res, fd; + char buf[4096]; + + res = 1; + syscall_success(fd = open("/dev/null", O_WRONLY, 0)); + for (int i = 0; i < 1000000; i++) + { + syscall_success(res = write(fd, buf, sizeof(buf))); + } + syscall_success(close(fd)); + + res = 1; + syscall_success(fd = open("/dev/zero", O_RDONLY, 0)); + while (0 < res) + { + syscall_success(res = read(fd, buf, sizeof(buf))); + } + syscall_success(close(fd)); +} + +/* + * Tests open(), close(), and unlink() + * - Accepts only valid combinations of flags + * - Cannot open nonexistent file without O_CREAT + * - Cannot write to readonly file + * - Cannot read from writeonly file + * - Cannot close non-existent file descriptor + * - Lowest file descriptor is always selected + * - Cannot unlink a directory + # - Cannot unlink a non-existent file + * - Cannot open a directory for writing + * - File descriptors are correctly released when a proc exits + */ +static void vfstest_open(void) +{ +#define OPEN_BUFSIZE 5 + + char buf[OPEN_BUFSIZE]; + int fd, fd2; + stat_t s; + + syscall_success(mkdir("open", 0777)); + syscall_success(chdir("open")); + + /* No invalid combinations of O_RDONLY, O_WRONLY, and O_RDWR. Since + * O_RDONLY is stupidly defined as 0, the only invalid possible + * combination is O_WRONLY|O_RDWR. */ + syscall_fail(open("file01", O_WRONLY | O_RDWR | O_CREAT, 0), EINVAL); + syscall_fail(open("file01", O_RDONLY | O_RDWR | O_WRONLY | O_CREAT, 0), + EINVAL); + + /* Cannot open nonexistent file without O_CREAT */ + syscall_fail(open("file02", O_WRONLY, 0), ENOENT); + syscall_success(fd = open("file02", O_RDONLY | O_CREAT, 0)); + syscall_success(close(fd)); + syscall_success(unlink("file02")); + syscall_fail(stat("file02", &s), ENOENT); + + /* Cannot create invalid files */ + create_file("tmpfile"); + syscall_fail(open("tmpfile/test", O_RDONLY | O_CREAT, 0), ENOTDIR); + syscall_fail(open("noent/test", O_RDONLY | O_CREAT, 0), ENOENT); + syscall_fail(open(LONGNAME, O_RDONLY | O_CREAT, 0), ENAMETOOLONG); + + /* Cannot write to readonly file */ + syscall_success(fd = open("file03", O_RDONLY | O_CREAT, 0)); + syscall_fail(write(fd, "hello", 5), EBADF); + syscall_success(close(fd)); + + /* Cannot read from writeonly file. Note that we do not unlink() it + * from above, so we do not need O_CREAT set. */ + syscall_success(fd = open("file03", O_WRONLY, 0)); + syscall_fail(read(fd, buf, OPEN_BUFSIZE), EBADF); + syscall_success(close(fd)); + syscall_success(unlink("file03")); + syscall_fail(stat("file03", &s), ENOENT); + + /* Lowest file descriptor is always selected. */ + syscall_success(fd = open("file04", O_RDONLY | O_CREAT, 0)); + syscall_success(fd2 = open("file04", O_RDONLY, 0)); + test_assert(fd2 > fd, "open() did not return lowest fd"); + syscall_success(close(fd)); + syscall_success(close(fd2)); + syscall_success(fd2 = open("file04", O_WRONLY, 0)); + test_assert(fd2 == fd, "open() did not return correct fd"); + syscall_success(close(fd2)); + syscall_success(unlink("file04")); + syscall_fail(stat("file04", &s), ENOENT); + + /* Cannot open a directory for writing */ + syscall_success(mkdir("file05", 0)); + syscall_fail(open("file05", O_WRONLY, 0), EISDIR); + syscall_fail(open("file05", O_RDWR, 0), EISDIR); + syscall_success(rmdir("file05")); + + /* Cannot unlink a directory */ + syscall_success(mkdir("file06", 0)); + syscall_fail(unlink("file06"), EPERM); + syscall_success(rmdir("file06")); + syscall_fail(unlink("."), EPERM); + syscall_fail(unlink(".."), EPERM); + + /* Cannot unlink a non-existent file */ + syscall_fail(unlink("file07"), ENOENT); + + /* Cannot open a file as a directory */ + create_file("file08"); + syscall_fail(open("file08/", O_RDONLY, 0), ENOTDIR); + syscall_success(mkdir("dirA", 0777)); + syscall_success(chdir("dirA")); + create_file("file09"); + syscall_success(chdir("..")); + syscall_fail(open("dirA/file09/", O_RDONLY, 0), ENOTDIR); + + /* Succeeds with trailing slash */ + syscall_success(mkdir("dirB", 0777)); + syscall_success(mkdir("dirB/dirC", 0777)); + syscall_success(fd = open("dirB/", O_RDONLY, 0)); + syscall_success(close(fd)); + syscall_success(fd = open("dirB/dirC/", O_RDONLY, 0)); + syscall_success(close(fd)); + + syscall_success(chdir("..")); +} + +static void vfstest_read(void) +{ +#define READ_BUFSIZE 256 + + int fd, ret; + char buf[READ_BUFSIZE]; + stat_t s; + + syscall_success(mkdir("read", 0777)); + syscall_success(chdir("read")); + + /* Can read and write to a file */ + syscall_success(fd = open("file01", O_RDWR | O_CREAT, 0)); + syscall_success(ret = write(fd, "hello", 5)); + test_assert(5 == ret, "write(%d, \"hello\", 5) returned %d", fd, ret); + syscall_success(ret = lseek(fd, 0, SEEK_SET)); + test_assert(0 == ret, "lseek(%d, 0, SEEK_SET) returned %d", fd, ret); + read_fd(fd, READ_BUFSIZE, "hello"); + syscall_success(close(fd)); + + /* cannot read from a directory */ + syscall_success(mkdir("dir01", 0)); + syscall_success(fd = open("dir01", O_RDONLY, 0)); + syscall_fail(read(fd, buf, READ_BUFSIZE), EISDIR); + syscall_success(close(fd)); + + /* Can seek to beginning, middle, and end of file */ + syscall_success(fd = open("file02", O_RDWR | O_CREAT, 0)); + syscall_success(write(fd, "hello", 5)); + +#define test_lseek(expr, res) \ + do \ + { \ + int __r = (expr); \ + test_assert((res) == __r, #expr " returned %d, expected %d", __r, \ + res); \ + } while (0); + + test_lseek(lseek(fd, 0, SEEK_CUR), 5); + read_fd(fd, 10, ""); + test_lseek(lseek(fd, -1, SEEK_CUR), 4); + read_fd(fd, 10, "o"); + test_lseek(lseek(fd, 2, SEEK_CUR), 7); + read_fd(fd, 10, ""); + syscall_fail(lseek(fd, -8, SEEK_CUR), EINVAL); + + test_lseek(lseek(fd, 0, SEEK_SET), 0); + read_fd(fd, 10, "hello"); + test_lseek(lseek(fd, 3, SEEK_SET), 3); + read_fd(fd, 10, "lo"); + test_lseek(lseek(fd, 7, SEEK_SET), 7); + read_fd(fd, 10, ""); + syscall_fail(lseek(fd, -1, SEEK_SET), EINVAL); + + test_lseek(lseek(fd, 0, SEEK_END), 5); + read_fd(fd, 10, ""); + test_lseek(lseek(fd, -2, SEEK_END), 3); + read_fd(fd, 10, "lo"); + test_lseek(lseek(fd, 3, SEEK_END), 8); + read_fd(fd, 10, ""); + syscall_fail(lseek(fd, -8, SEEK_END), EINVAL); + + syscall_fail(lseek(fd, 0, SEEK_SET + SEEK_CUR + SEEK_END), EINVAL); + syscall_success(close(fd)); + + /* O_APPEND works properly */ + create_file("file03"); + syscall_success(fd = open("file03", O_RDWR, 0)); + test_fpos(fd, 0); + syscall_success(write(fd, "hello", 5)); + test_fpos(fd, 5); + syscall_success(close(fd)); + + syscall_success(fd = open("file03", O_RDWR | O_APPEND, 0)); + test_fpos(fd, 0); + syscall_success(write(fd, "hello", 5)); + test_fpos(fd, 10); + + syscall_success(lseek(fd, 0, SEEK_SET)); + test_fpos(fd, 0); + read_fd(fd, 10, "hellohello"); + syscall_success(lseek(fd, 5, SEEK_SET)); + test_fpos(fd, 5); + syscall_success(write(fd, "again", 5)); + test_fpos(fd, 15); + syscall_success(lseek(fd, 0, SEEK_SET)); + test_fpos(fd, 0); + read_fd(fd, 15, "hellohelloagain"); + syscall_success(close(fd)); + + /* seek and write beyond end of file */ + create_file("file04"); + syscall_success(fd = open("file04", O_RDWR, 0)); + syscall_success(write(fd, "hello", 5)); + test_fpos(fd, 5); + test_lseek(lseek(fd, 10, SEEK_SET), 10); + syscall_success(write(fd, "again", 5)); + syscall_success(stat("file04", &s)); + test_assert(s.st_size == 15, "actual size: %d", s.st_size); + test_lseek(lseek(fd, 0, SEEK_SET), 0); + test_assert(15 == read(fd, buf, READ_BUFSIZE), + "unexpected number of bytes read"); + test_assert(0 == memcmp(buf, "hello\0\0\0\0\0again", 15), + "unexpected data read"); + syscall_success(close(fd)); + + syscall_success(chdir("..")); +} + +static void vfstest_getdents(void) +{ + int fd, ret; + dirent_t dirents[4]; + + syscall_success(mkdir("getdents", 0)); + syscall_success(chdir("getdents")); + + /* getdents works */ + syscall_success(mkdir("dir01", 0)); + syscall_success(mkdir("dir01/1", 0)); + create_file("dir01/2"); + + syscall_success(fd = open("dir01", O_RDONLY, 0)); + syscall_success(ret = getdents(fd, dirents, 4 * sizeof(dirent_t))); + test_assert(4 * sizeof(dirent_t) == ret, NULL); + + syscall_success(ret = getdents(fd, dirents, sizeof(dirent_t))); + test_assert(0 == ret, NULL); + + syscall_success(lseek(fd, 0, SEEK_SET)); + test_fpos(fd, 0); + syscall_success(ret = getdents(fd, dirents, 2 * sizeof(dirent_t))); + test_assert(2 * sizeof(dirent_t) == ret, NULL); + syscall_success(ret = getdents(fd, dirents, 2 * sizeof(dirent_t))); + test_assert(2 * sizeof(dirent_t) == ret, NULL); + syscall_success(ret = getdents(fd, dirents, sizeof(dirent_t))); + test_assert(0 == ret, NULL); + syscall_success(close(fd)); + + /* Cannot call getdents on regular file */ + create_file("file01"); + syscall_success(fd = open("file01", O_RDONLY, 0)); + syscall_fail(getdents(fd, dirents, 4 * sizeof(dirent_t)), ENOTDIR); + syscall_success(close(fd)); + + syscall_success(chdir("..")); +} + +#ifdef __VM__ +/* + * Tests link(), rename(), and mmap() (and munmap, and brk). + * These functions are not supported on testfs, and not included in kernel-land + * vfs privtest (hence the name) + */ + +static void vfstest_s5fs_vm(void) +{ + int fd, newfd, ret; + char buf[2048]; + stat_t oldstatbuf, newstatbuf; + void *addr; + memset(&oldstatbuf, '\0', sizeof(stat_t)); + memset(&newstatbuf, '\0', sizeof(stat_t)); + + syscall_success(mkdir("s5fs", 0)); + syscall_success(chdir("s5fs")); + + /* Open some stuff */ + syscall_success(fd = open("oldchld", O_RDWR | O_CREAT, 0)); + syscall_success(mkdir("parent", 0)); + + /* link/unlink tests */ + syscall_success(link("oldchld", "newchld")); + + /* Make sure stats match */ + syscall_success(stat("oldchld", &oldstatbuf)); + syscall_success(stat("newchld", &newstatbuf)); + test_assert(0 == memcmp(&oldstatbuf, &newstatbuf, sizeof(stat_t)), NULL); + + /* Make sure contents match */ + syscall_success(newfd = open("newchld", O_RDWR, 0)); + syscall_success(ret = write(fd, TESTSTR, strlen(TESTSTR))); + test_assert(ret == (int)strlen(TESTSTR), NULL); + syscall_success(ret = read(newfd, buf, strlen(TESTSTR))); + test_assert(ret == (int)strlen(TESTSTR), NULL); + test_assert(0 == strncmp(buf, TESTSTR, strlen(TESTSTR)), + "string is %.*s, expected %s", strlen(TESTSTR), buf, TESTSTR); + + syscall_success(close(fd)); + syscall_success(close(newfd)); + + /* Remove one, make sure the other remains */ + syscall_success(unlink("oldchld")); + syscall_fail(mkdir("newchld", 0), EEXIST); + syscall_success(link("newchld", "oldchld")); + + /* Link/unlink error cases */ + syscall_fail(link("oldchld", "newchld"), EEXIST); + syscall_fail(link("oldchld", LONGNAME), ENAMETOOLONG); + syscall_fail(link("parent", "newchld"), EPERM); + + /* only rename test */ + /*syscall_success(rename("oldchld", "newchld"));*/ + + /* mmap/munmap tests */ + syscall_success(fd = open("newchld", O_RDWR, 0)); + test_assert( + MAP_FAILED != (addr = mmap(0, strlen(TESTSTR), PROT_READ | PROT_WRITE, + MAP_PRIVATE, fd, 0)), + NULL); + /* Check contents of memory */ + test_assert(0 == memcmp(addr, TESTSTR, strlen(TESTSTR)), NULL); + + /* Write to it -> we shouldn't pagefault */ + memcpy(addr, SHORTSTR, strlen(SHORTSTR)); + test_assert(0 == memcmp(addr, SHORTSTR, strlen(SHORTSTR)), NULL); + + /* mmap the same thing on top of it, but shared */ + test_assert( + MAP_FAILED != mmap(addr, strlen(TESTSTR), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, fd, 0), + NULL); + /* Make sure the old contents were restored (the mapping was private) */ + test_assert(0 == memcmp(addr, TESTSTR, strlen(TESTSTR)), NULL); + + /* Now change the contents */ + memcpy(addr, SHORTSTR, strlen(SHORTSTR)); + /* mmap it on, private, on top again */ + test_assert( + MAP_FAILED != mmap(addr, strlen(TESTSTR), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED, fd, 0), + NULL); + /* Make sure it changed */ + test_assert(0 == memcmp(addr, SHORTSTR, strlen(SHORTSTR)), NULL); + + /* Fork and try changing things */ + if (!fork()) + { + /* Child changes private mapping */ + memcpy(addr, TESTSTR, strlen(TESTSTR)); + exit(0); + } + + /* Wait until child is done */ + syscall_success(wait(0)); + + /* Make sure it's actually private */ + test_assert(0 == memcmp(addr, SHORTSTR, strlen(SHORTSTR)), NULL); + + /* Unmap it */ + syscall_success(munmap(addr, 2048)); + + /* mmap errors */ + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE, 12, 0), + NULL); + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE, -1, 0), + NULL); + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, 0, fd, 0), NULL); + test_assert(MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_FIXED, fd, 0), NULL); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_FIXED | MAP_PRIVATE, fd, 0), + NULL); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE, fd, 0x12345), NULL); + test_assert(MAP_FAILED == mmap((void *)0x12345, 1024, PROT_READ, + MAP_PRIVATE | MAP_FIXED, fd, 0), + NULL); + test_assert(MAP_FAILED == mmap(0, 0, PROT_READ, MAP_PRIVATE, fd, 0), NULL); + test_assert(MAP_FAILED == mmap(0, -1, PROT_READ, MAP_PRIVATE, fd, 0), NULL); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, 0), + NULL); + syscall_success(close(fd)); + + syscall_success(fd = open("newchld", O_RDONLY, 0)); + test_assert( + MAP_FAILED == mmap(0, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + NULL); + syscall_success(close(fd)); + + /* TODO ENODEV (mmap a terminal) + EOVERFLOW (mmap SO MUCH of /dev/zero that fpointer would overflow) */ + + /* Also should test opening too many file descriptors somewhere */ + + /* munmap errors */ + syscall_fail(munmap((void *)0x12345, 15), EINVAL); + syscall_fail(munmap(0x0, 15), EINVAL); + syscall_fail(munmap(addr, 0), EINVAL); + syscall_fail(munmap(addr, -1), EINVAL); + + /* brk tests */ + /* Set the break, and use the memory in question */ + test_assert((void *)-1 != (addr = sbrk(128)), NULL); + memcpy(addr, TESTSTR, 128); + test_assert(0 == memcmp(addr, TESTSTR, 128), NULL); + + /* Make sure that the brk is being saved properly */ + test_assert((void *)((unsigned long)addr + 128) == sbrk(0), NULL); + /* Knock the break back down */ + syscall_success(brk(addr)); + + /* brk errors */ + syscall_fail(brk((void *)(&"brk")), ENOMEM); + syscall_fail(brk((void *)1), ENOMEM); + syscall_fail(brk((void *)&addr), ENOMEM); + + syscall_success(chdir("..")); +} +#endif + +#ifdef __KERNEL__ +extern uint64_t jiffies; +#endif + +static void seed_randomness() +{ +#ifdef __KERNEL__ + srand(jiffies); +#else + srand(time(NULL)); +#endif + rand(); +} + +/* + * Finally, the main function. + */ +#ifndef __KERNEL__ + +int main(int argc, char **argv) +#else +int vfstest_main(int argc, char **argv) +#endif +{ + if (argc != 1) + { + fprintf(stderr, "USAGE: vfstest\n"); + return 1; + } + + seed_randomness(); + + test_init(); + vfstest_start(); + + syscall_success(chdir(root_dir)); + + vfstest_notdir(); + vfstest_stat(); + vfstest_chdir(); + vfstest_mkdir(); + vfstest_paths(); + vfstest_fd(); + vfstest_open(); + vfstest_read(); + vfstest_getdents(); + vfstest_memdev(); + vfstest_write(); + +#ifdef __VM__ + vfstest_s5fs_vm(); +#endif + + syscall_success(chdir("..")); + + vfstest_term(); + test_fini(); + + return 0; +} |