Implements the core design: a mount table published as an atomically- swapped snapshot; mem/dir/pack/overlay backends; copy-on-write overlay with copy-up and whiteout deletion; a checksummed append journal; compaction with exact-duplicate elimination; single-writer/wait-free- reader concurrency with a structural/content write split; openat2/ Landlock path containment for dir mounts; and load-time pack integrity validation. Zero required third-party dependencies. Sanitizer testing (ASan/UBSan) caught and led to fixing a genuine heap-use-after-free in the snapshot-reclamation path: the textbook "load pointer, then increment its refcount" pattern left a gap a concurrent writer could free through. Closed with a small reclaim_gate rwlock, documented in internal.h and CLAUDE.md since it's a pattern every refcounted structure in the codebase now follows. zip/tar import/export backends, recommended in concept.md Section 11, will not be built — a permanent project decision recorded in CLAUDE.md since concept.md itself is frozen and cannot be edited to reflect it. Includes a runnable demo (examples/demo.c, `make demo`) exercising the library end to end and proving cross-run persistence through the pack file, plus open-source scaffolding: MIT license, README, CONTRIBUTING, and a CI workflow running the test suite under ASan/UBSan/TSan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
97 lines
3.6 KiB
C
97 lines
3.6 KiB
C
/* test_mem.c — mem backend: basic CRUD, directory markers (Section 9.3),
|
|
* rename, readdir. */
|
|
|
|
#include "packfs.h"
|
|
#include "test_harness.h"
|
|
|
|
int main(void) {
|
|
Vfs *v = vfs_new();
|
|
Backend *mem = backend_mem_new();
|
|
CHECK_EQ_INT(vfs_mount(v, "/", mem), VFS_OK);
|
|
|
|
/* create + write + read back */
|
|
int err = 0;
|
|
VfsFile *f = vfs_open(v, "/hello.txt", VFS_O_WRONLY | VFS_O_CREAT, &err);
|
|
CHECK(f != NULL);
|
|
CHECK_EQ_INT(vfs_write(f, "hello", 5), 5);
|
|
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
|
|
|
f = vfs_open(v, "/hello.txt", VFS_O_RDONLY, &err);
|
|
CHECK(f != NULL);
|
|
char buf[16] = {0};
|
|
CHECK_EQ_INT(vfs_read(f, buf, sizeof(buf)), 5);
|
|
CHECK_STR_EQ(buf, "hello");
|
|
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
|
|
|
VfsStat st;
|
|
CHECK_EQ_INT(vfs_stat(v, "/hello.txt", &st), VFS_OK);
|
|
CHECK_EQ_INT(st.size, 5);
|
|
CHECK_EQ_INT(st.kind, VFS_KIND_FILE);
|
|
|
|
/* nonexistent path */
|
|
CHECK_EQ_INT(vfs_stat(v, "/nope.txt", &st), VFS_ERR_NOENT);
|
|
|
|
/* path escape rejected (Section 6.1) */
|
|
f = vfs_open(v, "/../etc/passwd", VFS_O_RDONLY, &err);
|
|
CHECK(f == NULL);
|
|
CHECK_EQ_INT(err, VFS_ERR_INVAL);
|
|
|
|
/* directories: explicit empty dir (Section 9.3) and implicit dir from a file */
|
|
CHECK_EQ_INT(vfs_mkdir(v, "/empty"), VFS_OK);
|
|
CHECK_EQ_INT(vfs_stat(v, "/empty", &st), VFS_OK);
|
|
CHECK_EQ_INT(st.kind, VFS_KIND_DIR);
|
|
|
|
f = vfs_open(v, "/nested/deep/file.txt", VFS_O_WRONLY | VFS_O_CREAT, &err);
|
|
CHECK(f != NULL);
|
|
CHECK_EQ_INT(vfs_write(f, "x", 1), 1);
|
|
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
|
CHECK_EQ_INT(vfs_stat(v, "/nested", &st), VFS_OK);
|
|
CHECK_EQ_INT(st.kind, VFS_KIND_DIR);
|
|
CHECK_EQ_INT(vfs_stat(v, "/nested/deep", &st), VFS_OK);
|
|
CHECK_EQ_INT(st.kind, VFS_KIND_DIR);
|
|
|
|
/* readdir at root: hello.txt, empty, nested */
|
|
VfsDir dir;
|
|
CHECK_EQ_INT(vfs_readdir(v, "/", &dir), VFS_OK);
|
|
int saw_hello = 0, saw_empty = 0, saw_nested = 0;
|
|
for (pfs_usize i = 0; i < dir.count; i++) {
|
|
if (strcmp(dir.entries[i].name, "hello.txt") == 0) { saw_hello = 1; CHECK_EQ_INT(dir.entries[i].kind, VFS_KIND_FILE); }
|
|
if (strcmp(dir.entries[i].name, "empty") == 0) { saw_empty = 1; CHECK_EQ_INT(dir.entries[i].kind, VFS_KIND_DIR); }
|
|
if (strcmp(dir.entries[i].name, "nested") == 0) { saw_nested = 1; CHECK_EQ_INT(dir.entries[i].kind, VFS_KIND_DIR); }
|
|
}
|
|
CHECK(saw_hello && saw_empty && saw_nested);
|
|
vfs_dir_free(&dir);
|
|
|
|
/* rename */
|
|
CHECK_EQ_INT(vfs_rename(v, "/hello.txt", "/hello2.txt"), VFS_OK);
|
|
CHECK_EQ_INT(vfs_stat(v, "/hello.txt", &st), VFS_ERR_NOENT);
|
|
CHECK_EQ_INT(vfs_stat(v, "/hello2.txt", &st), VFS_OK);
|
|
|
|
/* unlink + rmdir-of-nonempty rejected */
|
|
CHECK_EQ_INT(vfs_unlink(v, "/hello2.txt"), VFS_OK);
|
|
CHECK_EQ_INT(vfs_unlink(v, "/nested"), VFS_ERR_NOTEMPTY);
|
|
|
|
/* buffer growth across the 64-byte initial capacity (Section 5.7) */
|
|
f = vfs_open(v, "/big.bin", VFS_O_WRONLY | VFS_O_CREAT, &err);
|
|
CHECK(f != NULL);
|
|
unsigned char chunk[100];
|
|
for (int i = 0; i < 100; i++) chunk[i] = (unsigned char)i;
|
|
for (int i = 0; i < 50; i++) CHECK_EQ_INT(vfs_write(f, chunk, sizeof(chunk)), sizeof(chunk));
|
|
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
|
CHECK_EQ_INT(vfs_stat(v, "/big.bin", &st), VFS_OK);
|
|
CHECK_EQ_INT(st.size, 5000);
|
|
f = vfs_open(v, "/big.bin", VFS_O_RDONLY, &err);
|
|
unsigned char rb[5000];
|
|
CHECK_EQ_INT(vfs_read(f, rb, sizeof(rb)), 5000);
|
|
CHECK_EQ_INT(memcmp(rb, rb, 0), 0); /* smoke */
|
|
int ok = 1;
|
|
for (int i = 0; i < 5000; i++) if (rb[i] != (unsigned char)(i % 100)) ok = 0;
|
|
CHECK(ok);
|
|
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
|
|
|
vfs_unmount(v, "/");
|
|
backend_free(mem);
|
|
vfs_free(v);
|
|
TEST_MAIN_END();
|
|
}
|