#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 #include #include #include #include #include #include #include #include #include #include #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; }