Add PackFS v0: statically linked in-process VFS implementing concept.md
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
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/* test_concurrency.c — exercises Section 5.3's core promise: readers
|
||||
* never observe a torn snapshot while writers race structural changes,
|
||||
* and Section 5.7's buffer-growth guard doesn't crash or corrupt under
|
||||
* concurrent grow-and-read. Not a formal proof, but real thread
|
||||
* interleavings under a sanitizer-friendly build catch real races. */
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "packfs.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#define WRITER_THREADS 4
|
||||
#define READER_THREADS 4
|
||||
#define ITERATIONS 500
|
||||
|
||||
static Vfs *g_v;
|
||||
|
||||
static void *writer_thread(void *arg) {
|
||||
long id = (long)arg;
|
||||
char path[64];
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
snprintf(path, sizeof(path), "/t%ld/f%d.txt", id, i % 8);
|
||||
int err = 0;
|
||||
VfsFile *f = vfs_open(g_v, path, VFS_O_WRONLY | VFS_O_CREAT | VFS_O_TRUNC, &err);
|
||||
if (!f) continue;
|
||||
char payload[128];
|
||||
int n = snprintf(payload, sizeof(payload), "thread=%ld iter=%d %.*s", id, i, (i % 50), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
vfs_write(f, payload, (pfs_usize)n);
|
||||
vfs_close(f);
|
||||
if (i % 37 == 0) vfs_unlink(g_v, path);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *reader_thread(void *arg) {
|
||||
(void)arg;
|
||||
char path[64];
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
for (long id = 0; id < WRITER_THREADS; id++) {
|
||||
snprintf(path, sizeof(path), "/t%ld/f%d.txt", id, i % 8);
|
||||
VfsStat st;
|
||||
if (vfs_stat(g_v, path, &st) != VFS_OK) continue;
|
||||
int err = 0;
|
||||
VfsFile *f = vfs_open(g_v, path, VFS_O_RDONLY, &err);
|
||||
if (!f) continue;
|
||||
char buf[256];
|
||||
pfs_isize r = vfs_read(f, buf, sizeof(buf) - 1);
|
||||
/* No CHECK on content here: a concurrent writer may legitimately
|
||||
* replace the file between stat and open/read (Section 5.3
|
||||
* guarantees a consistent snapshot per call, not across calls).
|
||||
* What we're really testing is the absence of a crash, a
|
||||
* negative-but-uncaught read length, or a hang. */
|
||||
CHECK(r >= 0);
|
||||
vfs_close(f);
|
||||
}
|
||||
VfsDir dir;
|
||||
if (vfs_readdir(g_v, "/", &dir) == VFS_OK) vfs_dir_free(&dir);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
g_v = vfs_new();
|
||||
Backend *mem = backend_mem_new();
|
||||
CHECK_EQ_INT(vfs_mount(g_v, "/", mem), VFS_OK);
|
||||
|
||||
pthread_t writers[WRITER_THREADS], readers[READER_THREADS];
|
||||
for (long i = 0; i < WRITER_THREADS; i++) pthread_create(&writers[i], NULL, writer_thread, (void *)i);
|
||||
for (long i = 0; i < READER_THREADS; i++) pthread_create(&readers[i], NULL, reader_thread, (void *)i);
|
||||
for (int i = 0; i < WRITER_THREADS; i++) pthread_join(writers[i], NULL);
|
||||
for (int i = 0; i < READER_THREADS; i++) pthread_join(readers[i], NULL);
|
||||
|
||||
/* sanity: the store is still fully consistent after the race */
|
||||
VfsDir dir;
|
||||
CHECK_EQ_INT(vfs_readdir(g_v, "/", &dir), VFS_OK);
|
||||
CHECK(dir.count > 0);
|
||||
vfs_dir_free(&dir);
|
||||
|
||||
vfs_unmount(g_v, "/");
|
||||
backend_free(mem);
|
||||
vfs_free(g_v);
|
||||
TEST_MAIN_END();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/* test_dir.c — dir backend: host passthrough + path containment
|
||||
* (Section 6.2): a normalized ".." can't reach it (blocked in the
|
||||
* virtual namespace, Section 6.1), and an absolute symlink planted
|
||||
* inside the mount cannot be used to read outside it. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "packfs.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
int main(void) {
|
||||
char root[] = "/tmp/packfs_test_dir_XXXXXX";
|
||||
CHECK(mkdtemp(root) != NULL);
|
||||
char outside_dir[480];
|
||||
snprintf(outside_dir, sizeof(outside_dir), "/tmp/packfs_secret_%d", (int)getpid());
|
||||
mkdir(outside_dir, 0755);
|
||||
char secret_file[512];
|
||||
snprintf(secret_file, sizeof(secret_file), "%s/secret.txt", outside_dir);
|
||||
FILE *sf = fopen(secret_file, "w");
|
||||
CHECK(sf != NULL);
|
||||
fputs("top secret", sf);
|
||||
fclose(sf);
|
||||
|
||||
/* a symlink INSIDE root pointing OUTSIDE it */
|
||||
char link_path[512];
|
||||
snprintf(link_path, sizeof(link_path), "%s/escape", root);
|
||||
CHECK(symlink(outside_dir, link_path) == 0);
|
||||
|
||||
Vfs *v = vfs_new();
|
||||
int derr = 0;
|
||||
Backend *dir = backend_dir_new(root, &derr);
|
||||
CHECK(dir != NULL);
|
||||
CHECK_EQ_INT(vfs_mount(v, "/", dir), VFS_OK);
|
||||
|
||||
int err = 0;
|
||||
VfsFile *f = vfs_open(v, "/a.txt", VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||||
CHECK(f != NULL);
|
||||
CHECK_EQ_INT(vfs_write(f, "abc", 3), 3);
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
f = vfs_open(v, "/a.txt", VFS_O_RDONLY, &err);
|
||||
char buf[8] = {0};
|
||||
CHECK_EQ_INT(vfs_read(f, buf, sizeof(buf)), 3);
|
||||
CHECK_STR_EQ(buf, "abc");
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
/* virtual-namespace ".." escape: rejected before any backend is reached */
|
||||
f = vfs_open(v, "/../etc/shadow", VFS_O_RDONLY, &err);
|
||||
CHECK(f == NULL);
|
||||
CHECK_EQ_INT(err, VFS_ERR_INVAL);
|
||||
|
||||
/* symlink escape through the dir backend itself: openat2/O_NOFOLLOW
|
||||
* containment (Section 6.2) must refuse to follow it. */
|
||||
f = vfs_open(v, "/escape/secret.txt", VFS_O_RDONLY, &err);
|
||||
CHECK(f == NULL);
|
||||
|
||||
vfs_unmount(v, "/");
|
||||
backend_free(dir);
|
||||
vfs_free(v);
|
||||
TEST_MAIN_END();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* test_harness.h — minimal assertion-based test harness.
|
||||
*
|
||||
* No third-party test framework, consistent with the project's
|
||||
* static-linking / zero-required-dependency constraint (Section 11.1).
|
||||
*/
|
||||
#ifndef PACKFS_TEST_HARNESS_H
|
||||
#define PACKFS_TEST_HARNESS_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int pfs_test_failures = 0;
|
||||
|
||||
#define CHECK(cond) do { \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||||
pfs_test_failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_EQ_INT(a, b) do { \
|
||||
long long _a = (long long)(a), _b = (long long)(b); \
|
||||
if (_a != _b) { \
|
||||
fprintf(stderr, "FAIL %s:%d: %s (%lld) != %s (%lld)\n", __FILE__, __LINE__, #a, _a, #b, _b); \
|
||||
pfs_test_failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_STR_EQ(a, b) do { \
|
||||
const char *_a = (a), *_b = (b); \
|
||||
if (strcmp(_a, _b) != 0) { \
|
||||
fprintf(stderr, "FAIL %s:%d: %s (\"%s\") != %s (\"%s\")\n", __FILE__, __LINE__, #a, _a, #b, _b); \
|
||||
pfs_test_failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TEST_MAIN_END() \
|
||||
do { \
|
||||
if (pfs_test_failures) { fprintf(stderr, "%d check(s) failed\n", pfs_test_failures); return 1; } \
|
||||
fprintf(stderr, "OK\n"); \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
/* 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();
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/* test_pack_overlay.c — overlay backend: copy-up, whiteout deletion of a
|
||||
* lower-layer entry (Section 4.2), compaction (Section 4.1/5.3),
|
||||
* cross-restart durability via journal replay (Section 4.4), and pack
|
||||
* integrity validation rejecting a corrupted file (Section 7). */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "packfs.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
static char *tmp_pack_path(void) {
|
||||
static char path[512];
|
||||
snprintf(path, sizeof(path), "/tmp/packfs_test_pack_%d.img", (int)getpid());
|
||||
return path;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *pack_path = tmp_pack_path();
|
||||
unlink(pack_path);
|
||||
char jpath[600];
|
||||
snprintf(jpath, sizeof(jpath), "%s.jnl", pack_path);
|
||||
unlink(jpath);
|
||||
|
||||
/* --- phase 1: build an initial pack directly (as if shipped) --- */
|
||||
Vfs *v = vfs_new();
|
||||
Backend *mem = backend_mem_new();
|
||||
int oerr = 0;
|
||||
Backend *ov = backend_overlay_new(pack_path, mem, &oerr);
|
||||
CHECK(ov != NULL);
|
||||
CHECK_EQ_INT(vfs_mount(v, "/", ov), VFS_OK);
|
||||
|
||||
int err = 0;
|
||||
VfsFile *f = vfs_open(v, "/a.txt", VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||||
CHECK(f != NULL);
|
||||
CHECK_EQ_INT(vfs_write(f, "AAAA", 4), 4);
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
f = vfs_open(v, "/dup.txt", VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||||
CHECK(f != NULL);
|
||||
CHECK_EQ_INT(vfs_write(f, "AAAA", 4), 4); /* same content as a.txt: exercises dedup (Section 9.2) */
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
f = vfs_open(v, "/b.txt", VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||||
CHECK(f != NULL);
|
||||
CHECK_EQ_INT(vfs_write(f, "BBBBBB", 6), 6);
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
CHECK_EQ_INT(vfs_sync(v, "/"), VFS_OK); /* compaction */
|
||||
|
||||
/* read-only open straight from the freshly-compacted pack */
|
||||
f = vfs_open(v, "/a.txt", VFS_O_RDONLY, &err);
|
||||
CHECK(f != NULL);
|
||||
char buf[16] = {0};
|
||||
CHECK_EQ_INT(vfs_read(f, buf, sizeof(buf)), 4);
|
||||
CHECK_STR_EQ(buf, "AAAA");
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
/* --- copy-up on write intent, whiteout on delete (Section 4.2) --- */
|
||||
f = vfs_open(v, "/a.txt", VFS_O_RDWR, &err); /* triggers copy-up */
|
||||
CHECK(f != NULL);
|
||||
CHECK_EQ_INT(vfs_write(f, "ZZZZ", 4), 4);
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
f = vfs_open(v, "/a.txt", VFS_O_RDONLY, &err);
|
||||
memset(buf, 0, sizeof(buf));
|
||||
CHECK_EQ_INT(vfs_read(f, buf, sizeof(buf)), 4);
|
||||
CHECK_STR_EQ(buf, "ZZZZ");
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
CHECK_EQ_INT(vfs_unlink(v, "/b.txt"), VFS_OK); /* whiteout: b.txt only existed in the pack */
|
||||
VfsStat st;
|
||||
CHECK_EQ_INT(vfs_stat(v, "/b.txt", &st), VFS_ERR_NOENT);
|
||||
|
||||
/* --- durability: a fresh Vfs replays the journal without compacting --- */
|
||||
vfs_unmount(v, "/");
|
||||
backend_free(ov);
|
||||
backend_free(mem);
|
||||
vfs_free(v);
|
||||
|
||||
Vfs *v2 = vfs_new();
|
||||
Backend *mem2 = backend_mem_new();
|
||||
Backend *ov2 = backend_overlay_new(pack_path, mem2, &oerr);
|
||||
CHECK(ov2 != NULL);
|
||||
CHECK_EQ_INT(vfs_mount(v2, "/", ov2), VFS_OK);
|
||||
|
||||
CHECK_EQ_INT(vfs_stat(v2, "/b.txt", &st), VFS_ERR_NOENT); /* whiteout replayed */
|
||||
f = vfs_open(v2, "/a.txt", VFS_O_RDONLY, &err);
|
||||
CHECK(f != NULL);
|
||||
memset(buf, 0, sizeof(buf));
|
||||
CHECK_EQ_INT(vfs_read(f, buf, sizeof(buf)), 4);
|
||||
CHECK_STR_EQ(buf, "ZZZZ"); /* the post-compaction write survived via the journal */
|
||||
CHECK_EQ_INT(vfs_close(f), VFS_OK);
|
||||
|
||||
CHECK_EQ_INT(vfs_sync(v2, "/"), VFS_OK); /* compact again to fold the journal in */
|
||||
vfs_unmount(v2, "/");
|
||||
backend_free(ov2);
|
||||
backend_free(mem2);
|
||||
vfs_free(v2);
|
||||
|
||||
/* --- pack integrity validation (Section 7): a corrupted pack is rejected --- */
|
||||
int fd = open(pack_path, O_RDWR);
|
||||
CHECK(fd >= 0);
|
||||
unsigned char one;
|
||||
CHECK_EQ_INT(pread(fd, &one, 1, 40), 1); /* somewhere inside the header/offsets region */
|
||||
one = (unsigned char)(one ^ 0xFF);
|
||||
CHECK_EQ_INT(pwrite(fd, &one, 1, 40), 1);
|
||||
close(fd);
|
||||
|
||||
Vfs *v3 = vfs_new();
|
||||
Backend *mem3 = backend_mem_new();
|
||||
int oerr3 = 0;
|
||||
Backend *ov3 = backend_overlay_new(pack_path, mem3, &oerr3);
|
||||
CHECK(ov3 == NULL);
|
||||
CHECK_EQ_INT(oerr3, VFS_ERR_CORRUPT);
|
||||
backend_free(mem3);
|
||||
vfs_free(v3);
|
||||
|
||||
unlink(pack_path);
|
||||
unlink(jpath);
|
||||
TEST_MAIN_END();
|
||||
}
|
||||
Reference in New Issue
Block a user