Files
packfs/include/packfs.h
T
retoorandClaude Sonnet 5 72e3c900f2 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
2026-09-14 05:44:08 +00:00

163 lines
5.7 KiB
C

/*
* packfs.h — public API of PackFS.
*
* PackFS is a statically linked, in-process virtual file system. The design
* rationale for every decision in this header is recorded in concept.md,
* which this implementation follows; concept.md is frozen (see CLAUDE.md)
* and is not re-derived here.
*
* SPDX-License-Identifier: MIT
*/
#ifndef PACKFS_H
#define PACKFS_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* concept.md Section 8 leaves isize/usize as project-defined typedefs. */
typedef ptrdiff_t pfs_isize;
typedef size_t pfs_usize;
typedef struct Vfs Vfs;
typedef struct VfsFile VfsFile;
typedef struct Backend Backend;
typedef enum {
VFS_OK = 0,
VFS_ERR_NOENT = -1, /* path does not exist */
VFS_ERR_EXIST = -2, /* path already exists */
VFS_ERR_NOTDIR = -3, /* path component is not a directory */
VFS_ERR_ISDIR = -4, /* operation not valid on a directory */
VFS_ERR_INVAL = -5, /* invalid argument (includes path escapes, see Section 6) */
VFS_ERR_IO = -6, /* host I/O error */
VFS_ERR_NOSPC = -7, /* out of space / allocation failure */
VFS_ERR_PERM = -8, /* operation not permitted (e.g. write to read-only backend) */
VFS_ERR_NOTEMPTY = -9, /* directory not empty */
VFS_ERR_CORRUPT = -10, /* pack failed integrity validation, Section 7 */
VFS_ERR_NOMOUNT = -11 /* no backend mounted for this path */
} VfsStatus;
typedef enum {
VFS_KIND_FILE = 0,
VFS_KIND_DIR = 1
} VfsEntryKind;
typedef struct VfsStat {
pfs_usize size;
int64_t mtime; /* seconds since epoch */
VfsEntryKind kind;
} VfsStat;
typedef struct VfsDirEntry {
char name[256];
VfsEntryKind kind;
} VfsDirEntry;
typedef struct VfsDir {
VfsDirEntry *entries;
pfs_usize count;
} VfsDir;
#define VFS_O_RDONLY 0x00
#define VFS_O_WRONLY 0x01
#define VFS_O_RDWR 0x02
#define VFS_O_CREAT 0x04
#define VFS_O_TRUNC 0x08
/* --- VFS lifecycle -------------------------------------------------- */
Vfs *vfs_new(void);
void vfs_free(Vfs *v);
/* --- Backend constructors -------------------------------------------
*
* Every backend is a Backend* handed to vfs_mount. Backends not currently
* mounted anywhere may be freed with backend_free; a backend still mounted
* must be unmounted first.
*/
/* mem: default writable layer (Section 3.1). */
Backend *backend_mem_new(void);
/* dir: capability-scoped host directory, containment per Section 6. */
Backend *backend_dir_new(const char *host_path, int *err);
/* pack: read-only image loaded and validated per Section 7. */
Backend *backend_pack_new(const char *pack_path, int *err);
/*
* overlay: copy-on-write overlay per Sections 4-5. `pack_path` is the pack
* image backing the read-only lower layer: if the file exists it is
* loaded and validated (Section 7) at mount time; NULL means "start with
* an empty lower layer" (vfs_sync then requires a non-NULL path to have
* been set — pass one via backend_overlay_new even for a fresh overlay
* that has no pack file yet). `upper` must be a `mem` or `dir` backend,
* not otherwise mounted; the overlay does not take ownership of it — free
* it explicitly after unmounting the overlay. A journal at
* `pack_path` + ".jnl" (Section 4.4) is replayed at mount time if present,
* and rewritten (not appended past its watermark) on every vfs_sync
* (Section 5.3).
*/
Backend *backend_overlay_new(const char *pack_path, Backend *upper, int *err);
void backend_free(Backend *b);
/* --- Mounting --------------------------------------------------------
*
* The mount table is itself part of the atomically-swapped snapshot
* (Section 5.3): vfs_mount/vfs_unmount are structural writes, serialized
* against each other and against every other structural write, and never
* observed mid-change by a concurrent path resolution.
*/
int vfs_mount(Vfs *v, const char *prefix, Backend *b);
int vfs_unmount(Vfs *v, const char *prefix);
/* --- File operations -------------------------------------------------- */
VfsFile *vfs_open(Vfs *v, const char *path, int flags, int *err);
pfs_isize vfs_read(VfsFile *f, void *buf, pfs_usize n);
pfs_isize vfs_write(VfsFile *f, const void *buf, pfs_usize n);
int vfs_close(VfsFile *f);
int vfs_stat(Vfs *v, const char *path, VfsStat *out);
int vfs_readdir(Vfs *v, const char *path, VfsDir *out);
void vfs_dir_free(VfsDir *d);
int vfs_mkdir(Vfs *v, const char *path);
int vfs_unlink(Vfs *v, const char *path);
int vfs_rename(Vfs *v, const char *from, const char *to);
/*
* vfs_sync compacts the overlay mounted exactly at `overlay_prefix`
* (Section 4.1 step 5, Section 5.3): it freezes the current upper
* snapshot, writes a new pack, and replaces the journal with only the
* records written after the watermark (Section 5.3, Section 4.4).
*/
int vfs_sync(Vfs *v, const char *overlay_prefix);
/*
* Optional, opt-in, process-wide Landlock hardening (Section 6.2). This
* is deliberately NOT applied automatically by vfs_mount: Landlock
* restrictions are cumulative, irreversible, and apply to the whole
* calling process/thread, which would be a surprising side effect for an
* embeddable library to trigger on its own. Call it once, after all `dir`
* mounts the process will ever need are in place, if the embedding
* application wants the whole process confined to exactly those
* directories at the kernel level. Returns VFS_OK on success; if the
* running kernel does not support Landlock, it returns VFS_ERR_PERM and
* changes nothing — this is the documented residual-risk fallback of
* Section 6.3, not a fatal error.
*/
int vfs_harden_process_with_landlock(Vfs *v);
#ifdef __cplusplus
}
#endif
#endif /* PACKFS_H */