test(brainfs): pin the handle check with a FIFO held open by a writer
CI / Lint / Test / Vet (pull_request) Successful in 47s
CI / Mirror to GitHub (pull_request) Skipped

PR #161 review: removing ReadRegular's type check on the opened handle
stayed green, because the race test's FIFOs had no writer and read EOF
at once; with a writer holding the FIFO open, search hung after 16
iterations. The open-and-check step is split into readOpened, and
TestReadOpenedRefusesAFIFOHeldOpenByAWriter reaches it past the Lstat
with a writer open: it must return ErrNotRegular promptly (the flip is
red).

Also unit tests for ReadRegular (symlink, escape), Rel (inside incl.
..foo, escapes) and WalkRegular (regular files only), and the hard-link
residual documented where it is accepted, as in api/rawroot.go.

Refs #157

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 18:26:27 +02:00
co-authored by Claude Opus 5.5
parent 9c9ba31770
commit ffe46c7077
2 changed files with 107 additions and 0 deletions
+15
View File
@@ -60,6 +60,21 @@ func (b *Brain) ReadRegular(rel string) ([]byte, error) {
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("%s: %w", rel, ErrNotRegular)
}
return b.readOpened(rel)
}
// readOpened opens rel non-blocking and reads it only if the OPENED handle is
// a regular file. This is the check that matters under a race: an entry
// swapped for a FIFO after the Lstat, with a writer holding it open, would
// otherwise park io.ReadAll forever (PR #161 review: removing it hung search
// after 16 iterations).
//
// Accepted residual, as in api/rawroot.go: a HARD link to an outside file is
// a regular file and is read. Syncthing syncs contents, not links, so a peer
// cannot create one; fs.protected_hardlinks=1 and the pod's secrets are on a
// different filesystem from the brain volume, so a local caller cannot link
// them in. Refusing Nlink > 1 is the next step if either changes.
func (b *Brain) readOpened(rel string) ([]byte, error) {
f, err := b.root.OpenFile(rel, os.O_RDONLY|syscall.O_NONBLOCK, 0)
if err != nil {
return nil, err