test(brainfs): pin the handle check with a FIFO held open by a writer
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user