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>
Option 1 of #157, first slice. Search walked and read the brain by
path, so a symlink planted in the Syncthing-shared brain dir put an
outside file's contents in brain_query results, and a FIFO swapped into
the wiki hung the query forever (reproduced: the FIFO race test blocked
for the full 6s timeout before this change).
New package internal/brainfs: Open (os.Root on the brain dir),
ReadRegular (Lstat + non-blocking open + type check on the handle) and
WalkRegular (walks the root's FS, yields regular files only, never
follows a link). search.Query and hydrate use it.
Tests (red first): a wiki symlink is not followed, a symlinked dir is not
descended, hydrate refuses a symlink, and race tests swap a symlink or a
FIFO into a wiki note while Query loops; any leaked byte or a hang fails.
Refs #157
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>