#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ASSERT_TRUE(cond, fmt, ...) \ do { \ if (!(cond)) { \ fprintf(stderr, "[FAIL] %s:%d " fmt "\n", __func__, __LINE__, \ ##__VA_ARGS__); \ return -1; \ } \ } while (0) #define ASSERT_SYS_OK(expr) \ do { \ if ((expr) < 0) { \ fprintf(stderr, "[FAIL] %s:%d %s: %s\n", __func__, __LINE__, \ #expr, strerror(errno)); \ return -1; \ } \ } while (0) static int join_path(char *out, size_t out_sz, const char *dir, const char *name) { int n = snprintf(out, out_sz, "%s/%s", dir, name); if (n < 0 || (size_t)n >= out_sz) { errno = ENAMETOOLONG; return -1; } return 0; } static int test_basic_rw_seek_stat(const char *workdir) { char path[PATH_MAX]; ASSERT_SYS_OK(join_path(path, sizeof(path), workdir, "basic_rw.txt")); int fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644); ASSERT_SYS_OK(fd); const char *init = "abcdef"; ssize_t nr = write(fd, init, 6); ASSERT_TRUE(nr == 6, "write expected 6, got %zd", nr); off_t off = lseek(fd, 0, SEEK_SET); ASSERT_TRUE(off == 0, "lseek expected 0, got %lld", (long long)off); char buf[16] = {0}; nr = read(fd, buf, 6); ASSERT_TRUE(nr == 6, "read expected 6, got %zd", nr); ASSERT_TRUE(memcmp(buf, "abcdef", 6) == 0, "read content mismatch"); nr = pwrite(fd, "XYZ", 3, 3); ASSERT_TRUE(nr == 3, "pwrite expected 3, got %zd", nr); memset(buf, 0, sizeof(buf)); nr = pread(fd, buf, 6, 0); ASSERT_TRUE(nr == 6, "pread expected 6, got %zd", nr); ASSERT_TRUE(memcmp(buf, "abcXYZ", 6) == 0, "pread content mismatch"); struct stat st; ASSERT_SYS_OK(fstat(fd, &st)); ASSERT_TRUE(st.st_size == 6, "fstat size expected 6, got %lld", (long long)st.st_size); ASSERT_SYS_OK(ftruncate(fd, 4)); ASSERT_SYS_OK(fstat(fd, &st)); ASSERT_TRUE(st.st_size == 4, "ftruncate size expected 4, got %lld", (long long)st.st_size); ASSERT_SYS_OK(fdatasync(fd)); ASSERT_SYS_OK(fsync(fd)); ASSERT_SYS_OK(close(fd)); ASSERT_SYS_OK(unlink(path)); return 0; } static int test_openat_rename_unlink(const char *workdir) { char subdir[PATH_MAX]; ASSERT_SYS_OK(join_path(subdir, sizeof(subdir), workdir, "openat_dir")); ASSERT_SYS_OK(mkdir(subdir, 0755)); int dfd = open(subdir, O_RDONLY | O_DIRECTORY); ASSERT_SYS_OK(dfd); int fd = openat(dfd, "a.txt", O_CREAT | O_TRUNC | O_RDWR, 0644); ASSERT_SYS_OK(fd); ssize_t nr = write(fd, "hello", 5); ASSERT_TRUE(nr == 5, "write expected 5, got %zd", nr); ASSERT_SYS_OK(close(fd)); struct stat st; ASSERT_SYS_OK(fstatat(dfd, "a.txt", &st, 0)); ASSERT_TRUE(st.st_size == 5, "fstatat size expected 5, got %lld", (long long)st.st_size); ASSERT_SYS_OK(renameat(dfd, "a.txt", dfd, "b.txt")); ASSERT_SYS_OK(fstatat(dfd, "b.txt", &st, 0)); ASSERT_TRUE(st.st_size == 5, "renamed file size expected 5, got %lld", (long long)st.st_size); ASSERT_SYS_OK(unlinkat(dfd, "b.txt", 0)); errno = 0; ASSERT_TRUE(fstatat(dfd, "b.txt", &st, 0) == -1 && errno == ENOENT, "fstatat after unlink should be ENOENT"); ASSERT_SYS_OK(close(dfd)); ASSERT_SYS_OK(rmdir(subdir)); return 0; } static int test_dup_fcntl_ioctl(const char *workdir) { char path[PATH_MAX]; ASSERT_SYS_OK(join_path(path, sizeof(path), workdir, "dup_fcntl.txt")); int fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644); ASSERT_SYS_OK(fd); ASSERT_TRUE(write(fd, "0123456789", 10) == 10, "write expected 10 bytes"); ASSERT_SYS_OK(lseek(fd, 0, SEEK_SET)); int fd2 = dup(fd); bool dup_supported = true; if (fd2 < 0 && (errno == ENOTSUP || errno == EOPNOTSUPP || errno == ENOSYS)) { dup_supported = false; fprintf(stderr, "[INFO] dup on this backend is unsupported, skip shared-offset check\n"); } else { ASSERT_SYS_OK(fd2); } char buf[4] = {0}; if (dup_supported) { ASSERT_TRUE(read(fd, buf, 2) == 2, "read(fd) expected 2 bytes"); ASSERT_TRUE(memcmp(buf, "01", 2) == 0, "first read mismatch"); memset(buf, 0, sizeof(buf)); ASSERT_TRUE(read(fd2, buf, 2) == 2, "read(fd2) expected 2 bytes"); ASSERT_TRUE(memcmp(buf, "23", 2) == 0, "dup offset should be shared, expected \"23\""); } int fd_flags = fcntl(fd, F_GETFD); ASSERT_SYS_OK(fd_flags); ASSERT_SYS_OK(fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC)); int fd_flags_after = fcntl(fd, F_GETFD); ASSERT_SYS_OK(fd_flags_after); ASSERT_TRUE((fd_flags_after & FD_CLOEXEC) != 0, "FD_CLOEXEC should be set"); int file_flags = fcntl(fd, F_GETFL); ASSERT_SYS_OK(file_flags); ASSERT_SYS_OK(fcntl(fd, F_SETFL, file_flags | O_APPEND)); int file_flags_after = fcntl(fd, F_GETFL); ASSERT_SYS_OK(file_flags_after); ASSERT_TRUE((file_flags_after & O_APPEND) != 0, "O_APPEND should be set"); int avail = -1; ASSERT_SYS_OK(ioctl(dup_supported ? fd2 : fd, FIONREAD, &avail)); if (dup_supported) { ASSERT_TRUE(avail == 6, "FIONREAD expected 6, got %d", avail); ASSERT_SYS_OK(close(fd2)); } else { ASSERT_TRUE(avail == 10, "FIONREAD expected 10, got %d", avail); } ASSERT_SYS_OK(close(fd)); ASSERT_SYS_OK(unlink(path)); return 0; } static int test_readv_writev_pwritev(const char *workdir) { char path[PATH_MAX]; ASSERT_SYS_OK(join_path(path, sizeof(path), workdir, "iov.txt")); int fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644); ASSERT_SYS_OK(fd); struct iovec wiov[3]; wiov[0].iov_base = "ab"; wiov[0].iov_len = 2; wiov[1].iov_base = "cd"; wiov[1].iov_len = 2; wiov[2].iov_base = "ef"; wiov[2].iov_len = 2; ssize_t nr = writev(fd, wiov, 3); ASSERT_TRUE(nr == 6, "writev expected 6, got %zd", nr); ASSERT_SYS_OK(lseek(fd, 0, SEEK_SET)); char a[2] = {0}, b[2] = {0}, c[2] = {0}; struct iovec riov[3]; riov[0].iov_base = a; riov[0].iov_len = 2; riov[1].iov_base = b; riov[1].iov_len = 2; riov[2].iov_base = c; riov[2].iov_len = 2; nr = readv(fd, riov, 3); ASSERT_TRUE(nr == 6, "readv expected 6, got %zd", nr); ASSERT_TRUE(memcmp(a, "ab", 2) == 0 && memcmp(b, "cd", 2) == 0 && memcmp(c, "ef", 2) == 0, "readv content mismatch"); struct iovec pwiov[2]; pwiov[0].iov_base = "12"; pwiov[0].iov_len = 2; pwiov[1].iov_base = "34"; pwiov[1].iov_len = 2; nr = pwritev(fd, pwiov, 2, 1); ASSERT_TRUE(nr == 4, "pwritev expected 4, got %zd", nr); char out[8] = {0}; nr = pread(fd, out, 6, 0); ASSERT_TRUE(nr == 6, "pread expected 6, got %zd", nr); ASSERT_TRUE(memcmp(out, "a1234f", 6) == 0, "pwritev content mismatch"); ASSERT_SYS_OK(close(fd)); ASSERT_SYS_OK(unlink(path)); return 0; } typedef int (*test_fn)(const char *workdir); struct test_case { const char *name; test_fn fn; }; static int run_test(const struct test_case *tc, const char *workdir) { int rc = tc->fn(workdir); if (rc == 0) { printf("[PASS] %s\n", tc->name); return 0; } printf("[FAIL] %s\n", tc->name); return -1; } int main(void) { const char *base = getenv("ZVFS_TEST_ROOT"); if (!base || base[0] == '\0') base = "/tmp"; char workdir[PATH_MAX]; int n = snprintf(workdir, sizeof(workdir), "%s/zvfs-hook-api-XXXXXX", base); if (n < 0 || (size_t)n >= sizeof(workdir)) { fprintf(stderr, "workdir template too long\n"); return 2; } if (!mkdtemp(workdir)) { fprintf(stderr, "mkdtemp(%s) failed: %s\n", workdir, strerror(errno)); return 2; } printf("workdir=%s\n", workdir); printf("hint: set ZVFS_TEST_ROOT=/zvfs when validating LD_PRELOAD hook path.\n"); struct test_case tests[] = { {"basic_rw_seek_stat", test_basic_rw_seek_stat}, {"openat_rename_unlink", test_openat_rename_unlink}, {"dup_fcntl_ioctl", test_dup_fcntl_ioctl}, {"readv_writev_pwritev", test_readv_writev_pwritev}, }; int failed = 0; for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) { if (run_test(&tests[i], workdir) != 0) failed++; } const char *keep = getenv("ZVFS_TEST_KEEP"); if (!keep || strcmp(keep, "1") != 0) { if (rmdir(workdir) < 0) { fprintf(stderr, "warning: failed to remove workdir %s: %s\n", workdir, strerror(errno)); } } else { printf("kept workdir=%s\n", workdir); } if (failed == 0) { printf("ALL TESTS PASSED\n"); return 0; } printf("FAILED=%d\n", failed); return 1; }