schfs_fsck: list snapshots, and mkschfs can make one

--snapshots is a range scan over SCHFS_SNAP_OBJECTID, printing each
retained root's id, name, block and the generation it was taken at. The
name is printed with an explicit precision: it is bounded but a damaged
image need not have terminated it.

mkschfs grows --snapshot NAME, which takes one of the finished tree. It
runs after the commit, because a snapshot names a *committed* root --
taking it before would record a root that exists only in memory.

Verified end to end: an image built with --snapshot golden lists the
snapshot and still passes --check. An image without one prints "no
snapshots" rather than nothing at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-10 19:33:54 +02:00
commit 36614b69c5
2 changed files with 76 additions and 2 deletions

View file

@ -676,6 +676,60 @@ do_extract(const char *dir)
return 0;
}
/* --------------------------------------------------------------------- */
/* --snapshots */
/* */
/* A snapshot is a retained tree root, stored as an ordinary record under */
/* SCHFS_SNAP_OBJECTID. Listing them is a range scan over that objectid. */
/* --------------------------------------------------------------------- */
static int
do_snapshots(void)
{
struct schfs_cursor c;
schfs_cursor_init(&c, &VOL.tree, CURBLK);
struct schfs_key k;
schfs_mkkey(&k, SCHFS_SNAP_OBJECTID, SCHFS_KEY_SNAP, 0);
int r = schfs_cursor_seek(&c, &k);
if (r == -2) {
fprintf(stderr, "schfs_fsck: tree is unreadable\n");
return 1;
}
int n = 0;
while (r == 0) {
const struct schfs_key *got = schfs_cursor_key(&c);
if (!got || got->objectid != SCHFS_SNAP_OBJECTID ||
got->type != SCHFS_KEY_SNAP)
break;
unsigned len;
const struct schfs_snap *sn =
(const struct schfs_snap *)schfs_cursor_val(&c, &len);
if (sn && len == sizeof(*sn)) {
if (n == 0)
printf("%-6s %-24s %-12s %s\n",
"id", "name", "root", "created at");
// The name is bounded but not guaranteed terminated by a bad
// image, so it is printed with an explicit precision.
printf("%-6llu %-24.*s %-12llu generation %llu\n",
(unsigned long long)got->offset,
SCHFS_SNAP_NAME_MAX, sn->name,
(unsigned long long)sn->root,
(unsigned long long)sn->created);
n++;
}
r = schfs_cursor_next(&c);
}
if (n == 0)
printf("no snapshots\n");
return 0;
}
/* ------------------------------------------------------------------ */
int
@ -690,7 +744,8 @@ main(int argc, char **argv)
" --stat PATH one object's metadata\n"
" --cat PATH a file's contents\n"
" --tree every path in the volume\n"
" --extract DIR write the whole tree onto the host\n");
" --extract DIR write the whole tree onto the host\n"
" --snapshots list the retained tree roots\n");
return 2;
}
@ -751,6 +806,8 @@ main(int argc, char **argv)
return do_cat(arg);
if (strcmp(action, "--extract") == 0 && arg)
return do_extract(arg);
if (strcmp(action, "--snapshots") == 0)
return do_snapshots();
fprintf(stderr, "schfs_fsck: unknown or incomplete action '%s'\n", action);
return 2;