Skip to content

fix(preflight): match FUSE network mounts by normalised name, not by spelling (client#725) - #726

Merged
LukasWodka merged 2 commits into
developfrom
fix/725-fuse-fstype-normalisation
Aug 17, 2026
Merged

fix(preflight): match FUSE network mounts by normalised name, not by spelling (client#725)#726
LukasWodka merged 2 commits into
developfrom
fix/725-fuse-fstype-normalisation

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Fixes#725

Summary

_pf_is_network_fstype (shared by _pf_storage_type and the pre-log early_data_dir_guard, #432) matched the FUSE network filesystems only as fuse.sshfs / fuse.s3fs / fuse.rclone / fuse.glusterfs / fuse.ceph / fuse.davfs — and three of those six had no bare twin.

Which spelling arrives depends on which reader in _pf_fstype answered, and two of its three readers never emit a fuse. prefix. Measured by mounting each filesystem for real on Ubuntu 24.04 (libfuse 3.14.0, util-linux 2.39.3, coreutils 9.4) and Ubuntu 20.04 (libfuse 2.9.9) — identical on both:

reader (in _pf_fstype order)sshfsrclonebindfs
findmnt -nro FSTYPEfuse.sshfsfuse.rclonefuse
stat -f -c %T (Linux fallback, no findmnt)fuseblkfuseblkfuseblk
df + mount (the macOS path)macfusemacfusemacfuse

So on macOS none of the six entries can ever match — macOS has no findmnt and deliberately skips the stat reader (BSD stat -f is a format string), so the df+mount path always answers, with the macFUSE vfs name. And wherever findmnt is absent on Linux, stat -f -c %T collapses every FUSE mount to fuseblk (the fuse and fuseblk kernel filesystems share one magic number). In both cases the guard read "Local storage" and MySQL was installed onto exactly the filesystem it exists to reject.

The originally-suspected mechanism — a bare sshfs in the mount table — did not reproduce on either libfuse generation; sshfs and rclone always set a subtype. The prefixed spellings are right for findmnt; the defect is that they were the only spellings.

Change

  • Normalise, don't enumerate. Strip a leading fuse. before matching; the case list now carries bare names only, so it cannot regrow a prefixed entry with no bare twin. A guard test asserts the list contains no fuse.-prefixed entry, and fails closed if it can't read the function.
  • Opaque FUSE (fuse, fuseblk, macfuse, osxfuse) — FUSE with the subtype erased — gets its own predicate and warns instead of hard failing. sshfs and a local ntfs-3g/gocryptfs are indistinguishable at that point, so a refusal would block legitimate local installs, and TRACEBLOC_ALLOW_NETWORK_FS reads as nonsense to someone who is not on a network filesystem. The silent "Local storage" pass was the defect; the warn names what it might be and continues.

Deliberately not in this PR: recovering the real subtype when a reader degrades (readable from /proc/mounts on Linux and from the sshfs#… device field on macOS). That would let the named cases hard fail on every platform instead of only warning. Happy to file it as a follow-up.

Test plan

New tests write their input filesystem names down from the measured reader outputs, not from the case list — a list checked against itself is self-consistent and blind. The measurement table is recorded in the test file header.

  • bats scripts/tests/preflight.bats134/134 pass (5 new tests, 0 failures)
  • bats scripts/tests/bats-hygiene.bats — green; every new assertion is || return 1-hardened
  • bats scripts/tests/copy-catalog.bats — green (no user-facing copy in the goldens changed)
  • make check — green; make check-all — green (465 helm unit tests, 31 suites)
  • scripts/gen-manifest.sh re-run; scripts/manifest.sha256 committed (R8 gate)

Mutation-proof — each mutation was verified to have actually applied (anchor gone) before trusting the result:

mutationresult
drop the fuse. strip5 tests red
regrow the original bug (fuse.sshfs entry, no bare sshfs)7 tests red, incl. the no-prefix structural guard
opaque predicate always false3 tests red
restore the classifier exactly as it ships on develop5 of the new tests red

The last row is the one that matters: the tests fail against the real pre-fix code, not just against a synthetic mutation.

Found while fact-checking tracebloc/e2e-test-agent#125, which documents this guard and flags the gap without fixing it (the defect is here, not there).

🤖 Generated with Claude Code


Note

Medium Risk
Changes install-time refusal/warning for MySQL data paths—affects whether installs proceed on FUSE/macOS/Linux—but behavior is well-tested and opaque FUSE stays advisory rather than blocking.

Overview
Fixes HOST_DATA_DIR preflight so network FUSE mounts are caught regardless of how _pf_fstype spells them, and generic FUSE no longer passes as “local storage.”

_pf_is_network_fstype now lowercases and strips a leading fuse. before matching a bare-name allowlist (adds gcsfuse, blobfuse, blobfuse2, etc.). Unlisted fuse.* subtypes are no longer treated as local.

_pf_is_opaque_fuse_fstype and _pf_opaque_fuse_warn handle subtype-erased readings (fuseblk, macfuse, bare fuse) and unknown named FUSE drivers: warn and continue (not hard-fail), with wording that distinguishes “subtype unknown” vs “driver not recognised.” early_data_dir_guard and _pf_storage_type use the same classifiers.

docs/INSTALL.md checklist and scripts/manifest.sha256 are updated; scripts/tests/preflight.bats adds broad coverage (prefix invariance, mutual exclusivity, guard agreement).

Reviewed by Cursor Bugbot for commit f802292. Bugbot is set up for automated code reviews on this repo. Configure here.

…spelling (client#725)
_pf_is_network_fstype matched the FUSE network filesystems only in their
`fuse.`-prefixed spelling, and three of those six (sshfs, s3fs, rclone) had
no bare twin. Which spelling arrives depends on which reader in _pf_fstype
answered — and two of its three readers never produce a `fuse.` prefix, so
the entries were unreachable there and a network HOST_DATA_DIR was accepted
by the guard that exists to reject it.
Measured on Ubuntu 24.04 (libfuse 3.14.0 / util-linux 2.39.3 / coreutils 9.4)
and 20.04 (libfuse 2.9.9), mounting each filesystem for real:
reader sshfs rclone bindfs
findmnt -nro FSTYPE fuse.sshfs fuse.rclone fuse
stat -f -c %T fuseblk fuseblk fuseblk
df + mount (macOS path) macfuse macfuse macfuse
So macOS — which has no findmnt and deliberately skips the stat reader —
could never match ANY of the six, and the Linux stat fallback collapses every
FUSE mount to `fuseblk`.
Normalise instead of enumerate: strip a leading `fuse.` before matching and
keep bare names only, so the list cannot regrow a prefixed entry with no bare
twin. A subtype-erased reading (fuse / fuseblk / macfuse / osxfuse) is now
classified as opaque FUSE and WARNS rather than hard failing — sshfs and a
local ntfs-3g read identically there, so refusing would block legitimate local
installs, and TRACEBLOC_ALLOW_NETWORK_FS is nonsense advice off a network FS.
Silently reading as "Local storage" was the actual defect.
Tests state the input names independently of the matcher (from the measured
reader outputs, not from the case list) and cover both spellings, the local
fuse.* filesystems that must NOT be swept up by the prefix strip, the opaque
readings, and agreement between _pf_storage_type and early_data_dir_guard. All
five new tests fail against the classifier as it ships on develop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LukasWodkaLukasWodka self-assigned this Aug 14, 2026
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

bugbot run

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 021eb1f. Configure here.

saadqbal
saadqbal previously approved these changes Aug 17, 2026

@saadqbalsaadqbal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, careful PR 👍 Normalise-then-match is the right shape, and the opaque-FUSE warn tier is a good call — refusing every unnamed fuse/fuseblk/macfuse would block legit local FUSE. Tests are non-vacuous (names come from real reader output, and #85 greps the source to stop the fuse.-prefix regrowing). Ran the suite locally: 134/134 green, manifest hash matches. One optional nit inline.

Comment threadscripts/lib/preflight.sh Outdated
… review)
saadqbal, flagged optional: `fuse.gcsfuse` (GCS) and `fuse.blobfuse2` (Azure) --
the cloud twins of the `s3fs` already covered -- were on neither list, so they
stripped to a name nobody matched, missed the opaque tier (not bare `fuse`), and
were announced as "Local storage". A hospital mounting object storage at
HOST_DATA_DIR corrupts the database exactly as s3fs does.
Both added, with `blobfuse` for the v1 driver.
AND THE STRUCTURAL HALF, because a list only ever holds the drivers someone
thought of and the next cloud FUSE ships without asking us: an unrecognised
`fuse.<subtype>` is now OPAQUE rather than local. We have no evidence it is
network -- so it stays a warning, not a hard fail -- but we have no evidence it
is local either, and announcing "Local storage" was a confident answer about a
mount we could not identify.
TWO THINGS THE EXISTING SUITE CAUGHT, both worth more than the change itself:
* `_pf_is_opaque_fuse_fstype` and `_pf_is_network_fstype` are MUTUALLY EXCLUSIVE
by design, and bats asserts it. A bare `fuse.*) return 0` broke that:
`fuse.sshfs` satisfied both, and the only thing keeping it a hard fail rather
than a notice was the order the caller happens to ask in. A future caller
asking "opaque?" first would silently downgrade a database-corrupting mount.
The arm now negates the network classifier, so the answer is independent of
call order.
* The opaque warning says "the mount table doesn't say which one". True for
`fuseblk`; FALSE for `fuse.gocryptfs`, where the table says precisely which
one and the gap is that WE have no opinion. Shipping that sentence would have
been the false-statement shape this repo keeps finding. The wording now
branches on which unknown it is.
Local FUSE (ntfs-3g, gocryptfs) moves from a silent pass to a notice. That is
the honest reading -- we genuinely cannot tell them from a cloud mount by name
-- and it is a warning, never a failure. Say so if you would rather have only
the three names; the fuse.* arm is a one-line revert.
138 bats pass (was 134). Four mutations, each anchor asserted unique first:
drop the cloud twins from the network list -> reddened
unrecognised FUSE falls back to local again -> reddened
drop the exclusivity negation -> reddened
restore the false wording for a named subtype -> reddened
manifest.sha256 regenerated.
@LukasWodka
LukasWodka merged commit 89539ed into developAug 17, 2026
47 checks passed
@LukasWodka
LukasWodka deleted the fix/725-fuse-fstype-normalisation branch August 17, 2026 11:53
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

/fr-pass

Best-effort functional review passed (triage: merged clean, non-interactive; behavioral evidence limited while e2e journey is red — backend#2206). Advancing to Ready for prod.

Sign up for freeto 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.

preflight: the network-FS guard's FUSE entries are unreachable on macOS and on the stat fallback

2 participants

@LukasWodka@saadqbal