Skip to content

xfs: identify filesystem without root, and fail loud when it can't - #85

Merged
martinus merged 3 commits into
masterfrom
xfs-robustness
Jul 20, 2026
Merged

xfs: identify filesystem without root, and fail loud when it can't#85
martinus merged 3 commits into
masterfrom
xfs-robustness

Conversation

@martinus

Copy link
Copy Markdown
Owner

Problem

Running oans on XFS unprivileged silently scanned 0 files and exited 0 (Nothing to deduplicate) — looking like success while doing nothing. Root cause: for any non-btrfs filesystem, oans reads the fs UUID via libblkid, which needs root or a populated blkid cache. On a fresh device run without root, the lookup fails → the scan root is rejected → the whole tree is discarded. (btrfs is unaffected: it reads the UUID via an unprivileged ioctl.)

Discovered while confirming the "works on btrfs and XFS" claim for launch: a real XFS reflink smoke test deduped correctly as root, but the unprivileged run exposed this.

Changes

  1. Unprivileged UUID via FS_IOC_GETFSUUID (Linux 6.4+): an fd ioctl that returns the fs UUID directly — no root, no blkid cache. Falls back to the existing mountinfo + libblkid path on older kernels (ENOTTY). This makes XFS work unprivileged on modern kernels.
  2. Fail loud: if no root can be locked onto a supported filesystem, oans now stops with a clear error and non-zero exit instead of a silent no-op.
  3. CI: an XFS leg (matrix over btrfs/xfs) runs the integration suite unprivileged, proving the FS_IOC_GETFSUUID path end to end. The harness auto-skips the few btrfs-only (fragmentation) tests on XFS.
  4. Docs: README + man page note the XFS/root requirement on pre-6.4 kernels.

Testing

  • scripts/verify.sh: 88 integration tests pass, valgrind clean.
  • Confirmed FS_IOC_GETFSUUID succeeds unprivileged locally (a tmpfs dir that the old blkid path could not identify is now scanned without a UUID warning).
  • End-to-end XFS dedupe confirmed on a real loopback XFS reflink filesystem (three identical files collapse to one shared physical extent, Reclaimed 38.1 MiB).

🤖 Generated with Claude Code

On XFS (any non-btrfs fs) oans looked up the filesystem UUID via libblkid,
which needs root or a populated blkid cache. Run unprivileged on a fresh
device, the lookup failed, the scan root was silently rejected, and oans
reported "Nothing to deduplicate" and exited 0 — looking like success while
doing nothing.

- Try FS_IOC_GETFSUUID first (Linux 6.4+): an unprivileged fd ioctl that
  returns the fs UUID directly, no root and no blkid cache needed. Falls back
  to the mountinfo + libblkid path on older kernels (ENOTTY). btrfs already
  had an unprivileged ioctl path and is unchanged.
- If no root can be locked onto a supported filesystem, stop with a clear
  error and non-zero exit instead of a silent no-op.
- CI: add an XFS leg (matrix over btrfs/xfs) that runs the integration suite
  unprivileged, proving the FS_IOC_GETFSUUID path end to end.
- Document the XFS/root requirement in the README and man page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
martinus and others added 2 commits July 20, 2026 14:18
Address the /simplify pass on the XFS robustness change:
- copy the FS_IOC_GETFSUUID bytes with uuid_copy() to match the file's idiom
  (used at the other UUID copy sites) instead of a hand-rolled memcpy.
- collapse the triplicated "if (!parent_checked) seed_fs_lock_failed = true;"
  rejection at the three fs-lock failure exits in check_file into a single
  seed_reject() helper, removing the duplication and the added nesting.

No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test_shared_tail_is_deduped builds a partially-shared file by writing a head,
fsync, then the shared tail to force an extent boundary so the tail is its own
extent for the extent pass to match. That boundary only forms under
copy-on-write: XFS overwrites in place and keeps the file as a single extent,
so the tail is never a matchable extent and the dedupe cannot happen there.

This is the same btrfs-COW dependency the sibling
test_least_fragmented_target.py already gates on with @requires_btrfs; this
test was mislabeled @requires_reflink and only surfaced now that CI runs an XFS
leg. Genuine whole-file and naturally-aligned dedupe still works on XFS (proven
by the other integration tests, which pass on the XFS leg).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@martinus
martinus merged commit a79d82a into master Jul 20, 2026
4 checks passed
@martinus
martinus deleted the xfs-robustness branch July 20, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant