Files
retoorandClaude Sonnet 5 419182bb05 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
2026-09-14 10:58:35 +00:00

107 lines
4.2 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) {
/* 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);
/* 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();
}