A symlinked metadata/ or blobs/ directory in a file:// destination lists as empty, and prune then deletes every blob #296

Open
opened 2026-10-13 00:47:07 +02:00 by clawbot · 1 comment
Collaborator

Found by the defect hunt in #288 (part 5).

What is wrong: FileStorer.List and ListStream (internal/storage/file.go) walk with afero.Walk, which does not follow a symlink, so a symlinked metadata/ or blobs/ is listed as one non-directory key. Stat, Get and Put do follow it, so backups and restores keep working while every listing is empty.

How it was shown: after a backup, metadata/ was moved elsewhere and replaced by a symlink to it. snapshot list then warned "1 local snapshot record(s) not found in backup destination store", and prune --force printed "Removing stale local record ..." and "Deleted 1 blob(s)", leaving 0 blobs while both metadata files were still in place behind the link. With blobs/ symlinked instead, remote info --json gave total_blob_count: 0 beside referenced_blob_count: 1, and remote nuke --force printed "Backup destination store is now empty." with the blob still there.

Definition of done

  • A listing of a file:// destination reads through a symlinked directory the way Stat and Get do, or fails; it never reports a store it could not read as empty.
  • Tests cover a symlinked metadata/ and a symlinked blobs/, and check that prune deletes nothing in either case.

Model: opus-5-5

Found by the defect hunt in https://git.eeqj.de/sneak/vaultik/issues/288 (part 5). **What is wrong:** `FileStorer.List` and `ListStream` (`internal/storage/file.go`) walk with `afero.Walk`, which does not follow a symlink, so a symlinked `metadata/` or `blobs/` is listed as one non-directory key. `Stat`, `Get` and `Put` do follow it, so backups and restores keep working while every listing is empty. **How it was shown:** after a backup, `metadata/` was moved elsewhere and replaced by a symlink to it. `snapshot list` then warned "1 local snapshot record(s) not found in backup destination store", and `prune --force` printed "Removing stale local record ..." and "Deleted 1 blob(s)", leaving 0 blobs while both metadata files were still in place behind the link. With `blobs/` symlinked instead, `remote info --json` gave `total_blob_count: 0` beside `referenced_blob_count: 1`, and `remote nuke --force` printed "Backup destination store is now empty." with the blob still there. **Definition of done** - A listing of a `file://` destination reads through a symlinked directory the way `Stat` and `Get` do, or fails; it never reports a store it could not read as empty. - Tests cover a symlinked `metadata/` and a symlinked `blobs/`, and check that `prune` deletes nothing in either case. Model: opus-5-5
clawbot added the critical label 2026-10-13 00:47:07 +02:00
clawbot self-assigned this 2026-10-13 00:47:07 +02:00
Author
Collaborator

Implemented in #331: the file:// listings now read through a symlinked directory as Get and Stat do, and a link pointing back up the tree makes them fail. A symlinked metadata/ or blobs/ whose target is missing still lists as empty, as a missing directory does.

Model: opus-5-5

Implemented in https://git.eeqj.de/sneak/vaultik/pulls/331: the `file://` listings now read through a symlinked directory as `Get` and `Stat` do, and a link pointing back up the tree makes them fail. A symlinked `metadata/` or `blobs/` whose target is missing still lists as empty, as a missing directory does. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/vaultik#296