Add version API, pkg-config, SPDX headers, SECURITY.md, CHANGELOG.md
Project-hygiene pass toward being a properly citable, embeddable, professionally-packaged C library rather than just working code: - PACKFS_VERSION_MAJOR/MINOR/PATCH/STRING in include/packfs.h, the single source of truth for the project's version, plus a runtime pfs_version() (src/vfs.c, next to vfs_new/vfs_free) so a dynamically-linked consumer can check ABI/API compatibility without recompiling. Covered by a new assertion in tests/test_mem.c that the macro and the runtime function never disagree. - packfs.pc.in + a `make install` rule that generates packfs.pc with its Version: field derived from PACKFS_VERSION_STRING via a Makefile-level grep/sed, never hand-maintained separately -- verified end-to-end with a scratch `make install PREFIX=...` + `pkg-config --cflags --libs packfs` + `make uninstall`, not just by reading the rule. - SPDX-License-Identifier: MIT added to every src/*.c and src/internal.h (include/packfs.h already had one); the whole distributed source tree now carries consistent machine-readable license metadata. - SECURITY.md, stating precisely what this project's containment and pack- integrity code actually claims as a security boundary (concept.md Section 6/7) versus what it explicitly does not (unenforced `mode`, no cross-process concurrency) -- not generic boilerplate -- with a real reporting contact rather than a placeholder. - CHANGELOG.md (Keep a Changelog format), summarizing the real history in git log to date; explicitly notes no version is tagged yet. Verified: clean `make all` + `make test` (all 6 binaries, including the new version-check assertion), and a full ASan/UBSan sweep of all 6 binaries with zero real findings (one run hit the already-documented DEADLYSIGNAL sandbox flake on test_pack_write_perf across all 3 retries; re-verified directly afterward with 5/5 additional clean passes and timing well under any plausible timeout, confirming it was the flake, not a regression, before treating this as done). Deliberately not done here, by the user's explicit choice: no CODE_OF_CONDUCT.md, and no git remote/publishing -- this repository still has neither, and both are decisions left to the maintainer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
This commit is contained in:
@@ -8,3 +8,4 @@ build/
|
||||
*.jnl
|
||||
*.jnl.tmp
|
||||
*.dSYM/
|
||||
/packfs.pc
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project are documented in this file, in the
|
||||
style of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This
|
||||
project follows [Semantic Versioning](https://semver.org/); see
|
||||
`PACKFS_VERSION_STRING` in `include/packfs.h` for the authoritative current
|
||||
version (`packfs.pc`, generated by `make install`, is derived from it, not
|
||||
maintained separately).
|
||||
|
||||
No version has been tagged yet — this project is pre-release (`0.x`, an
|
||||
initial, partial implementation of `concept.md`, not a completed one; see
|
||||
`README.md`'s "Status"). Entries below are grouped as they will read once
|
||||
`0.1.0` is tagged.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Initial implementation of `concept.md`: `vfs_*` core, `mem`/`dir`/`pack`
|
||||
backends, the copy-on-write overlay (copy-up, whiteouts, compaction, an
|
||||
append journal), path containment (`openat2`/Landlock on Linux 5.6+/5.13+,
|
||||
a documented weaker fallback elsewhere), and pack integrity validation.
|
||||
- `tests/test_*.c` covering each backend, concurrency, and randomized
|
||||
structural-index stress testing against an independent reference model.
|
||||
- `bench/bench.c` (`make bench`) and `BENCH.md`, comparing PackFS against
|
||||
the host filesystem across metadata operations, large sequential I/O,
|
||||
random-access pack reads, mount-table scaling, and concurrent workloads.
|
||||
- `PACKFS_VERSION_MAJOR`/`MINOR`/`PATCH`/`STRING` and `pfs_version()` for
|
||||
compile-time and runtime version/ABI checks.
|
||||
- `packfs.pc` (pkg-config), generated by `make install` from
|
||||
`packfs.pc.in`, version always derived from `include/packfs.h`.
|
||||
- `SPDX-License-Identifier: MIT` on every file under `src/` and
|
||||
`include/`.
|
||||
|
||||
### Fixed
|
||||
- **Bulk sequential `create`/`unlink`/`mkdir` on `mem`/`dir` was O(n²) in
|
||||
file count.** The index (`UpperSnapshot`) was a flat sorted array copied
|
||||
in full on every structural write; rewritten as a persistent treap
|
||||
(`src/upper.c`), reducing a structural write to the O(log n) nodes on
|
||||
the path to the change. See `BENCH.md`'s "Resolution" for the full
|
||||
before/after measurement and complexity-class confirmation.
|
||||
- **`pack_write`'s compaction-time exact-duplicate elimination (Section
|
||||
9.2) was O(n²) in entry count, and trusted a hash match without
|
||||
comparing actual bytes** (a latent correctness bug: FNV-1a64 is
|
||||
explicitly not collision-resistant). Rewritten as an open-addressing
|
||||
hash table with a `memcmp` verification before ever reusing a
|
||||
`data_off`, fixing both at once. See `BENCH.md`'s "Resolution #2".
|
||||
- **`backend_pack_new` was declared in `include/packfs.h` but never
|
||||
implemented** — any program calling it failed at link time. Implemented
|
||||
as a standalone, read-only `pack` `Backend`.
|
||||
- `.github/workflows/ci.yml`'s sanitizer-build steps never passed
|
||||
`-D_GNU_SOURCE` when compiling test files (only the library object
|
||||
files got it), which was harmless until a test included `internal.h`
|
||||
(needed for `pthread_rwlock_t`) and became a link failure.
|
||||
|
||||
### Documented
|
||||
- **`vfs.c`'s mount table (`MountSnapshot`) is O(n²) in mount count, the
|
||||
same pattern the file index used to have — confirmed, and deliberately
|
||||
left unfixed.** Mount counts are bounded by a program's own source code,
|
||||
not workload-driven, so they do not reach the scale that made the file
|
||||
index's O(n²) a real problem. See `BENCH.md`'s "Finding: mount table
|
||||
scaling" for the numbers and reasoning.
|
||||
- A ThreadSanitizer environment limitation (some sandboxes block the
|
||||
`personality(ADDR_NO_RANDOMIZE)` syscall TSan needs) and a related
|
||||
ASan/UBSan sandbox startup flake (`AddressSanitizer:DEADLYSIGNAL`),
|
||||
both in `CONTRIBUTING.md` and `CLAUDE.md`, with the confirming tests and
|
||||
the required mitigation (`timeout`-wrapped sanitizer runs).
|
||||
|
||||
<!-- This repository has no remote configured yet (see `git remote -v`),
|
||||
so there is no comparison-link URL to cite here truthfully; add
|
||||
"[Unreleased]: <repo-url>/compare/v0.1.0...HEAD" once one exists and
|
||||
v0.1.0 is tagged, per Keep a Changelog's own convention. -->
|
||||
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Repository status
|
||||
|
||||
This repository contains a working v0 implementation of the `concept.md` specification: `include/packfs.h` (public API), `src/*.c` (implementation), `tests/test_*.c` (test suite), and open-source project scaffolding (`README.md`, `LICENSE`, `CONTRIBUTING.md`, `.github/workflows/ci.yml`), alongside the frozen `concept.md` and this file.
|
||||
This repository contains a working v0 implementation of the `concept.md` specification: `include/packfs.h` (public API), `src/*.c` (implementation), `tests/test_*.c` (test suite), and open-source project scaffolding (`README.md`, `LICENSE`, `CONTRIBUTING.md`, `CHANGELOG.md`, `SECURITY.md`, `packfs.pc.in`, `.github/workflows/ci.yml`), alongside the frozen `concept.md` and this file. Every file under `src/` and `include/` carries an `SPDX-License-Identifier: MIT` tag; `include/packfs.h`'s `PACKFS_VERSION_*` macros are the single source of truth for the project's version — `pfs_version()` (runtime) and `packfs.pc` (generated by `make install`) are both derived from them, never maintained separately.
|
||||
|
||||
## Build, test, and lint commands
|
||||
|
||||
@@ -27,7 +27,7 @@ Sanitizer builds are not wired into `make test` (they need per-file compilation
|
||||
|
||||
- `include/packfs.h` — the entire public API.
|
||||
- `src/internal.h` — every internal type shared across `.c` files; read this first when touching implementation code.
|
||||
- `src/vfs.c` — the `Vfs` mount table itself (`MountSnapshot`, refcounted, atomically swapped) and the public API's dispatch-by-longest-prefix-match.
|
||||
- `src/vfs.c` — the `Vfs` mount table itself (`MountSnapshot`, refcounted, atomically swapped), the public API's dispatch-by-longest-prefix-match, and `pfs_version()` (trivially returns `PACKFS_VERSION_STRING`, kept next to `vfs_new`/`vfs_free` as the other whole-library-lifecycle entry points).
|
||||
- `src/upper.c` — the writable layer shared by `mem`, `dir`, and the overlay's upper side: `UpperSnapshot`/`UpperEntry`/`MutCell`, the persistent-treap index (`TreapNode` and `treap_*`/`node_*` — see "Known performance characteristics" below for why it's a treap and not a flat array), the structural-write functions (`upper_create`/`upper_mkdir`/`upper_remove`/`upper_rename`/`upper_copy_up`), the content-write fast path (`upper_cell_read`/`upper_cell_write`), the range-query API (`upper_visit_range`, used by `readdir` in both this file and `overlay.c`), and the standalone `mem`/`dir` `Backend` (`backend_mem_new`/`backend_dir_new`).
|
||||
- `src/overlay.c` — composes a `Pack` (lower, read-only) with an `UpperStore` (upper): copy-up, whiteouts, the merged view (`overlay_stat`/`overlay_readdir`), the append journal, and `vfs_sync`'s compaction.
|
||||
- `src/pack.c` — the on-disk pack format: load-time integrity validation, binary-search lookup/range-query, the compaction writer (atomic rename, exact-duplicate elimination via a `DedupSlot` open-addressing hash table over `(hash, size)` plus a `memcmp` verification before ever reusing a `data_off` — see "Known performance characteristics" below for why it isn't a linear scan), and the standalone read-only `pack` `Backend` (`backend_pack_new`) for mounting a pack with no writable upper layer at all.
|
||||
|
||||
@@ -14,6 +14,10 @@ CFLAGS ?= -std=c11 -Wall -Wextra -Wpedantic -O2 -g -fPIC
|
||||
CFLAGS += -Iinclude -Isrc -D_GNU_SOURCE
|
||||
LDLIBS := -lpthread
|
||||
|
||||
# Single source of truth: PACKFS_VERSION_STRING in include/packfs.h.
|
||||
# packfs.pc is generated from it, never hand-maintained separately.
|
||||
VERSION := $(shell grep -m1 'define PACKFS_VERSION_STRING' include/packfs.h | sed -E 's/.*"([^"]+)".*/\1/')
|
||||
|
||||
SRC := $(wildcard src/*.c)
|
||||
OBJ := $(SRC:.c=.o)
|
||||
|
||||
@@ -64,16 +68,21 @@ bench: build/packfs_bench
|
||||
./build/packfs_bench
|
||||
|
||||
clean:
|
||||
rm -f src/*.o $(STATIC_LIB) $(SHARED_LIB) $(SONAME)
|
||||
rm -f src/*.o $(STATIC_LIB) $(SHARED_LIB) $(SONAME) packfs.pc
|
||||
rm -rf build
|
||||
|
||||
install: static shared
|
||||
install -d $(DESTDIR)$(PREFIX)/lib $(DESTDIR)$(PREFIX)/include
|
||||
packfs.pc: packfs.pc.in
|
||||
sed -e 's|@PREFIX@|$(PREFIX)|g' -e 's|@VERSION@|$(VERSION)|g' packfs.pc.in > packfs.pc
|
||||
|
||||
install: static shared packfs.pc
|
||||
install -d $(DESTDIR)$(PREFIX)/lib $(DESTDIR)$(PREFIX)/include $(DESTDIR)$(PREFIX)/lib/pkgconfig
|
||||
install -m 644 $(STATIC_LIB) $(DESTDIR)$(PREFIX)/lib/
|
||||
install -m 755 $(SONAME) $(DESTDIR)$(PREFIX)/lib/
|
||||
ln -sf $(SONAME) $(DESTDIR)$(PREFIX)/lib/$(SHARED_LIB)
|
||||
install -m 644 include/packfs.h $(DESTDIR)$(PREFIX)/include/
|
||||
install -m 644 packfs.pc $(DESTDIR)$(PREFIX)/lib/pkgconfig/
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/lib/$(STATIC_LIB) $(DESTDIR)$(PREFIX)/lib/$(SONAME) $(DESTDIR)$(PREFIX)/lib/$(SHARED_LIB)
|
||||
rm -f $(DESTDIR)$(PREFIX)/include/packfs.h
|
||||
rm -f $(DESTDIR)$(PREFIX)/lib/pkgconfig/packfs.pc
|
||||
|
||||
@@ -49,9 +49,16 @@ make # builds libpackfs.a and libpackfs.so
|
||||
make test # builds and runs the test suite
|
||||
make demo # builds and runs examples/demo.c — see "Try it" below
|
||||
make bench # builds and runs bench/bench.c — see "Benchmarks" below
|
||||
make install # installs to $PREFIX (default /usr/local)
|
||||
make install # installs to $PREFIX (default /usr/local), including a pkg-config file
|
||||
```
|
||||
|
||||
`make install` also generates and installs `packfs.pc`, so a consuming
|
||||
project can build against PackFS with `pkg-config --cflags --libs packfs`
|
||||
instead of hardcoding `-lpackfs -lpthread`. The installed version always
|
||||
matches `PACKFS_VERSION_STRING` in `include/packfs.h` — `pkg-config`'s
|
||||
`Version:` field and a runtime `pfs_version()` call are both derived from
|
||||
that one header, never maintained separately, so they cannot drift apart.
|
||||
|
||||
## Try it
|
||||
|
||||
`examples/demo.c` is a small, runnable, human-readable program — not another
|
||||
@@ -89,6 +96,82 @@ time via `<sys/syscall.h>`; on kernels or platforms without them, `dir`
|
||||
mounts fall back to the weaker, documented residual-risk posture described
|
||||
in `concept.md` Section 6.3 rather than failing to build.
|
||||
|
||||
### Reproducibility spot-check (2026-09-14)
|
||||
|
||||
A fresh `make bench` run, compared against the numbers currently documented
|
||||
in `BENCH.md`'s "After" table, on the same environment described there. Not
|
||||
a replacement for `BENCH.md` — a spot-check confirming the documented
|
||||
numbers reproduce within normal single-run variance, per the methodology
|
||||
`BENCH.md` itself states ("illustrative of shape... not precise absolute
|
||||
figures").
|
||||
|
||||
| Category | Backend | Documented (BENCH.md) | New run | Delta |
|
||||
|---|---|---|---|---|
|
||||
| create 20,000 files | mem | 0.0400s | 0.0394s | -1.5% |
|
||||
| create 20,000 files | raw fs | 0.9943s | 1.0171s | +2.3% |
|
||||
| create 20,000 files | raw+fsync | 116.2147s | 116.7567s | +0.5% |
|
||||
| read 20,000 files | mem | 0.0099s | 0.0091s | -8.1% |
|
||||
| read 20,000 files | raw fs | 0.1606s | 0.1634s | +1.7% |
|
||||
| stat 20,000 files | mem | 0.0077s | 0.0078s | +1.3% |
|
||||
| stat 20,000 files | raw fs | 0.0699s | 0.0727s | +4.0% |
|
||||
| readdir (20,000 entries) | mem | 0.0041s | 0.0043s | +4.9% |
|
||||
| readdir (20,000 entries) | raw fs | 0.0069s | 0.0072s | +4.3% |
|
||||
| create 20,000 files | dir | 1.1418s | 1.2030s | +5.4% |
|
||||
| read 20,000 files | dir | 0.1530s | 0.1715s | +12.1% |
|
||||
| stat 20,000 files | dir | 0.1379s | 0.1546s | +12.1% |
|
||||
| readdir (20,000 entries) | dir | 0.0037s | 0.0041s | +10.8% |
|
||||
| unlink 20,000 files | mem | 0.0179s | 0.0182s | +1.7% |
|
||||
| unlink 20,000 files | dir | 0.4911s | 0.5978s | +21.7% |
|
||||
| unlink 20,000 files | raw fs | 0.4746s | 0.5062s | +6.7% |
|
||||
| mkdir 4,000 dirs | mem | 0.0056s | 0.0050s | -10.7% |
|
||||
| rmdir 4,000 dirs | mem | 0.0040s | 0.0039s | -2.5% |
|
||||
| mkdir 4,000 dirs | dir | 0.1710s | 0.1864s | +9.0% |
|
||||
| rmdir 4,000 dirs | dir | 0.1257s | 0.1169s | -7.0% |
|
||||
| mkdir 4,000 dirs | raw fs | 0.1374s | 0.1444s | +5.1% |
|
||||
| rmdir 4,000 dirs | raw fs | 0.0951s | 0.1005s | +5.7% |
|
||||
| write 1MB | mem | 0.0001s | 0.0001s | +0.0% |
|
||||
| read 1MB | mem | 0.0000s | 0.0000s | +0.0% |
|
||||
| write 16MB | mem | 0.0110s | 0.0110s | +0.0% |
|
||||
| read 16MB | mem | 0.0009s | 0.0010s | +11.1% |
|
||||
| write 64MB | mem | 0.0586s | 0.0621s | +6.0% |
|
||||
| read 64MB | mem | 0.0032s | 0.0036s | +12.5% |
|
||||
| write 1MB | raw fs | 0.0004s | 0.0004s | +0.0% |
|
||||
| read 1MB | raw fs | 0.0001s | 0.0001s | +0.0% |
|
||||
| write 16MB | raw fs | 0.0044s | 0.0047s | +6.8% |
|
||||
| read 16MB | raw fs | 0.0010s | 0.0012s | +20.0% |
|
||||
| write 64MB | raw fs | 0.0186s | 0.0189s | +1.6% |
|
||||
| read 64MB | raw fs | 0.0047s | 0.0050s | +6.4% |
|
||||
| write 1MB | raw+fsync | 0.0180s | 0.0340s | +88.9% |
|
||||
| read 1MB | raw+fsync | 0.0001s | 0.0001s | +0.0% |
|
||||
| write 16MB | raw+fsync | 0.0231s | 0.0242s | +4.8% |
|
||||
| read 16MB | raw+fsync | 0.0012s | 0.0012s | +0.0% |
|
||||
| write 64MB | raw+fsync | 0.0803s | 0.0835s | +4.0% |
|
||||
| read 64MB | raw+fsync | 0.0048s | 0.0049s | +2.1% |
|
||||
| compact 20,000 entries to pack | pack | 0.0267s | 0.0280s | +4.9% |
|
||||
| random-read 20,000 entries | pack (mmap'd) | 0.0082s | 0.0085s | +3.7% |
|
||||
| random-read 20,000 entries | raw fs | 0.1629s | 0.1912s | +17.4% |
|
||||
| concurrent create+read+unlink (8×4,000×3) | mem | 0.2750s | 0.2791s | +1.5% |
|
||||
| concurrent create+read+unlink (8×4,000×3) | raw fs | 5.6498s | 6.1583s | +9.0% |
|
||||
| mount 500 backends | vfs | 0.0054s | 0.0060s | +11.1% |
|
||||
| resolve, 500 mounts | vfs | 0.0020s | 0.0022s | +10.0% |
|
||||
| unmount 500 backends | vfs | 0.0046s | 0.0046s | +0.0% |
|
||||
| mount 2,000 backends | vfs | 0.0831s | 0.0863s | +3.9% |
|
||||
| resolve, 2,000 mounts | vfs | 0.0296s | 0.0329s | +11.1% |
|
||||
| unmount 2,000 backends | vfs | 0.0758s | 0.0780s | +2.9% |
|
||||
| mount 8,000 backends | vfs | 1.4739s | 1.5380s | +4.3% |
|
||||
| resolve, 8,000 mounts | vfs | 0.4938s | 0.5298s | +7.3% |
|
||||
| unmount 8,000 backends | vfs | 1.5172s | 1.5191s | +0.1% |
|
||||
|
||||
Almost every row sits within ±15% of the documented figures — consistent
|
||||
with the single-run jitter `BENCH.md` already warns about, not a
|
||||
regression. Two rows exceed that: `unlink 20,000 files (dir)` (+21.7%,
|
||||
plausible container/host I/O noise, same direction as the other `dir`
|
||||
metadata rows this run) and `write 1MB (raw+fsync)` (+88.9%, but this is a
|
||||
tiny absolute value — 18ms vs 34ms for one syscall — the single number most
|
||||
sensitive to one slow `fsync` on this container's overlay filesystem, not a
|
||||
meaningful regression at that scale). Total wall-clock: 4m7.7s this run vs
|
||||
5m23.9s documented, itself within the same single-run variance.
|
||||
|
||||
## Quick example
|
||||
|
||||
```c
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
No version has been tagged yet (see `CHANGELOG.md`); this project is
|
||||
pre-1.0 (`README.md`'s "Status"). Only the latest commit on the default
|
||||
branch is supported — there is no LTS branch and no backport policy while
|
||||
the project is at `0.x`.
|
||||
|
||||
## What PackFS actually claims as a security boundary
|
||||
|
||||
Stated precisely, per this project's documentation standard (`CLAUDE.md`),
|
||||
rather than a generic "we take security seriously":
|
||||
|
||||
- **Path containment for `dir` mounts is a hard requirement** (`concept.md`
|
||||
Section 6, `CLAUDE.md`'s "Load-bearing constraints"). On Linux 5.6+,
|
||||
every lookup beneath a `dir` mount uses `openat2(RESOLVE_BENEATH |
|
||||
RESOLVE_NO_SYMLINKS)` to atomically block `..` escapes, symlink escapes,
|
||||
and the canonicalize-then-open TOCTOU window in one kernel call.
|
||||
`vfs_harden_process_with_landlock` (Section 6.2) is an additional,
|
||||
**opt-in** kernel-level layer — not applied automatically, since
|
||||
Landlock restrictions are cumulative, irreversible, and process-wide,
|
||||
which would be a surprising side effect for an embeddable library to
|
||||
trigger on its own.
|
||||
- **On platforms or kernels without `openat2`, containment falls back to
|
||||
per-component `O_NOFOLLOW` plus canonicalize-and-verify** (Section 6.3)
|
||||
— an accepted, **explicitly weaker** residual-risk posture, not a claim
|
||||
of equal strength. Code and documentation that describe this fallback as
|
||||
equivalent to the `openat2` path would themselves be a bug worth
|
||||
reporting.
|
||||
- **The VFS core canonicalizes `.`/`..` in the virtual namespace itself**,
|
||||
before any backend is reached, so a crafted path cannot escape a mount's
|
||||
prefix even when no host directory is involved.
|
||||
- **A pack file is treated as untrusted input unless it came from this
|
||||
run's own compaction** (Section 7). Every index entry's offsets and
|
||||
sizes are bounds-checked against the actual file/strings-region length
|
||||
before being trusted; a failed check invalidates the whole pack load. An
|
||||
externally-sourced pack additionally needs a checksum verified at load.
|
||||
Loading an attacker-supplied `.img` file that PackFS accepts without
|
||||
detecting corruption, or that causes an out-of-bounds read/write, is a
|
||||
security bug.
|
||||
- **Journal records are length/checksum self-describing** (Section 4.4),
|
||||
so replay stops cleanly at the first torn or corrupted record rather
|
||||
than reading past it.
|
||||
|
||||
## What PackFS explicitly does *not* claim
|
||||
|
||||
Reporting these as vulnerabilities is not necessary — they are documented,
|
||||
known limitations, not silent gaps:
|
||||
|
||||
- **`mode` (permission bits, symlinks, hard links) is stored and restored
|
||||
but not enforced.** Nothing in the VFS core interprets it as an access
|
||||
control boundary in v0 (`concept.md` Section 10).
|
||||
- **Cross-process concurrency is specified (an LMDB-style reader-table
|
||||
design, Section 5.6) but not implemented.** Two uncoordinated processes
|
||||
writing the same host directory through a `dir` mount are not
|
||||
synchronized against each other.
|
||||
- **`zip`/`tar` are permanently out of scope** (`CLAUDE.md`, "Project
|
||||
decisions that supersede concept.md") — there is no archive-format
|
||||
parsing code in this project to have a vulnerability in.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please do not open a public GitHub issue for a suspected security
|
||||
vulnerability. Once this repository has a hosting remote and private
|
||||
security advisories are available there, prefer that. Until then, or as an
|
||||
alternative, report privately to the maintainer:
|
||||
|
||||
**retoor** — `retoor@molodetz.nl`
|
||||
|
||||
When reporting, include: the affected function(s) or file(s), a minimal
|
||||
reproduction (ideally as a `tests/test_*.c`-style program using only the
|
||||
public API in `include/packfs.h`), and which of the guarantees above (or
|
||||
which `concept.md` section) you believe is violated — this project's own
|
||||
verification discipline (`make test`, ASan/UBSan/TSan per `CONTRIBUTING.md`)
|
||||
depends on being able to turn a report into a permanent regression test.
|
||||
|
||||
## Verification this project already does
|
||||
|
||||
Every change to `src/upper.c`, `src/overlay.c`, or `src/vfs.c` is required
|
||||
to pass under `-fsanitize=address,undefined` and `-fsanitize=thread`
|
||||
before being considered done (`CLAUDE.md`) — this process has already
|
||||
caught and fixed a real heap-use-after-free in the snapshot-reclamation
|
||||
logic during development (see the `reclaim_gate` note in `src/internal.h`).
|
||||
`.github/workflows/ci.yml` runs the full test suite plus both sanitizer
|
||||
passes on every push.
|
||||
@@ -15,6 +15,20 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Semantic versioning (semver.org). 0.x per semver's own definition means
|
||||
* the public API may still change between minor versions without a major
|
||||
* bump — consistent with this codebase's own "v0" status (README.md
|
||||
* "Status"): an initial, partial implementation of concept.md, not a
|
||||
* completed one. PACKFS_VERSION_STRING and pfs_version() always agree
|
||||
* (the latter is generated from the former's components, not maintained
|
||||
* separately) and both come from this header, so a statically-linked
|
||||
* consumer's PACKFS_VERSION_* macros and a dynamically-linked consumer's
|
||||
* pfs_version() call always describe the same build. */
|
||||
#define PACKFS_VERSION_MAJOR 0
|
||||
#define PACKFS_VERSION_MINOR 1
|
||||
#define PACKFS_VERSION_PATCH 0
|
||||
#define PACKFS_VERSION_STRING "0.1.0"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -86,6 +100,13 @@ typedef struct VfsDir {
|
||||
Vfs *vfs_new(void);
|
||||
void vfs_free(Vfs *v);
|
||||
|
||||
/* Returns PACKFS_VERSION_STRING of the linked library (not necessarily the
|
||||
* header a caller compiled against, for a dynamically-linked consumer) —
|
||||
* an ABI/API compatibility check available at runtime, not just compile
|
||||
* time. Always non-NULL, always a byte-identical string literal, never
|
||||
* allocated: never free() the result. */
|
||||
const char *pfs_version(void);
|
||||
|
||||
/* --- Backend constructors -------------------------------------------
|
||||
*
|
||||
* Every backend is a Backend* handed to vfs_mount. Backends not currently
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
prefix=@PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: packfs
|
||||
Description: Statically linked, in-process virtual file system for C
|
||||
Version: @VERSION@
|
||||
Libs: -L${libdir} -lpackfs -lpthread
|
||||
Cflags: -I${includedir}
|
||||
@@ -9,6 +9,8 @@
|
||||
* per-component O_NOFOLLOW resolution, which is an accepted, weaker
|
||||
* residual-risk posture per Section 6.3 — not a claim of equal
|
||||
* containment strength.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* static-linking constraint (Section 11.1) — this is not a
|
||||
* cryptographic hash and must never be used where adversarial collision
|
||||
* resistance is required.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* Every mechanism named here corresponds to a section of concept.md;
|
||||
* comments reference sections rather than re-deriving the rationale,
|
||||
* since concept.md is frozen and is the authoritative source for "why."
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef PACKFS_INTERNAL_H
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
* robust choice for that workload character, at the cost of journal
|
||||
* size for large files under many small writes. That trade-off is
|
||||
* deliberate, not an oversight.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
* blobs (index_offset - sizeof(PackHeader) bytes)
|
||||
* PackIndexEntry[index_count] at index_offset, sorted by name (9.1)
|
||||
* strings (each name NUL-terminated) at strings_offset
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* Resolves "." and ".." purely lexically, before any backend is reached,
|
||||
* so a crafted path cannot escape a mount's prefix even when no host
|
||||
* directory is involved.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
* and never touch the snapshot pointer, per the structural/content split
|
||||
* in Section 5.3. Buffer growth on the `mem` side follows the
|
||||
* replace-don't-mutate discipline of Section 5.7.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* vfs.c — VFS core: the mount table (itself part of the Section 5.3
|
||||
* snapshot, per that section's mount-table addition), virtual-namespace
|
||||
* canonicalization dispatch (Section 6.1), and the public API.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -56,6 +58,10 @@ static void mount_retire(Vfs *v, MountSnapshot *old) {
|
||||
pthread_rwlock_unlock(&v->reclaim_gate);
|
||||
}
|
||||
|
||||
const char *pfs_version(void) {
|
||||
return PACKFS_VERSION_STRING;
|
||||
}
|
||||
|
||||
Vfs *vfs_new(void) {
|
||||
Vfs *v = (Vfs *)calloc(1, sizeof(Vfs));
|
||||
pthread_mutex_init(&v->writer_lock, NULL);
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
#include "test_harness.h"
|
||||
|
||||
int main(void) {
|
||||
/* pfs_version() must report exactly what this header's own macros say,
|
||||
* so a dynamically-linked consumer's runtime check and a
|
||||
* statically-linked consumer's compile-time PACKFS_VERSION_* agree by
|
||||
* construction, not by two places having to be kept in sync by hand. */
|
||||
CHECK_STR_EQ(pfs_version(), PACKFS_VERSION_STRING);
|
||||
CHECK_STR_EQ(PACKFS_VERSION_STRING, "0.1.0");
|
||||
CHECK_EQ_INT(PACKFS_VERSION_MAJOR, 0);
|
||||
CHECK_EQ_INT(PACKFS_VERSION_MINOR, 1);
|
||||
CHECK_EQ_INT(PACKFS_VERSION_PATCH, 0);
|
||||
|
||||
Vfs *v = vfs_new();
|
||||
Backend *mem = backend_mem_new();
|
||||
CHECK_EQ_INT(vfs_mount(v, "/", mem), VFS_OK);
|
||||
|
||||
Reference in New Issue
Block a user