fix(tunnel): DetectTunnels walks and reads through brainfs (#174, #157 slice 5b) #186

Merged
mathias merged 2 commits from fix/tunnel-detect-174 into main 2026-10-05 20:12:45 +00:00
2 changed files with 68 additions and 23 deletions
Showing only changes of commit 1c9c218dbf - Show all commits
+68 -22
View File
@@ -19,25 +19,37 @@ import (
// every note with a blocking os.Open, so a FIFO named *.md anywhere under // every note with a blocking os.Open, so a FIFO named *.md anywhere under
// wiki/ hung every brain_write and brain_update (wikiUpkeep -> AutoTunnel), // wiki/ hung every brain_write and brain_update (wikiUpkeep -> AutoTunnel),
// and a symlinked note's title became a tunnel candidate. // and a symlinked note's title became a tunnel candidate.
func TestDetectTunnelsReturnsPromptlyWithAFIFOUnderWiki(t *testing.T) { func TestDetectTunnelsReturnsPromptlyWithAFIFOUnderWikiWithAndWithoutAWriter(t *testing.T) {
b := t.TempDir() for _, withWriter := range []bool{false, true} {
facts := filepath.Join(b, "wiki", "w", "facts") t.Run(fmt.Sprintf("writer=%v", withWriter), func(t *testing.T) {
require.NoError(t, os.MkdirAll(facts, 0o755)) b := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(facts, "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644)) facts := filepath.Join(b, "wiki", "w", "facts")
require.NoError(t, syscall.Mkfifo(filepath.Join(facts, "f.md"), 0o644)) require.NoError(t, os.MkdirAll(facts, 0o755))
done := make(chan []brain.TunnelCandidate, 1) require.NoError(t, os.WriteFile(filepath.Join(facts, "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644))
go func() { fifo := filepath.Join(facts, "f.md")
cs, _ := brain.DetectTunnels(b, "text mentioning zqinside phrase here") require.NoError(t, syscall.Mkfifo(fifo, 0o644))
done <- cs if withWriter {
}() // A writer held open: a reader that opens the FIFO gets no
select { // EOF and parks, unless the FIFO is never opened.
case cs := <-done: w, err := os.OpenFile(fifo, os.O_RDWR, 0)
assert.Contains(t, fmt.Sprintf("%+v", cs), "Zqinside Phrase", "positive control") require.NoError(t, err)
case <-time.After(3 * time.Second): t.Cleanup(func() { _ = w.Close() })
if r, err := os.OpenFile(filepath.Join(facts, "f.md"), os.O_WRONLY|syscall.O_NONBLOCK, 0); err == nil { }
_ = r.Close() done := make(chan []brain.TunnelCandidate, 1)
} go func() {
t.Fatal("DetectTunnels blocked on a FIFO") cs, _ := brain.DetectTunnels(b, "text mentioning zqinside phrase here")
done <- cs
}()
select {
case cs := <-done:
assert.Contains(t, fmt.Sprintf("%+v", cs), "Zqinside Phrase", "positive control")
case <-time.After(3 * time.Second):
if r, err := os.OpenFile(fifo, os.O_WRONLY|syscall.O_NONBLOCK, 0); err == nil {
_ = r.Close()
}
t.Fatal("DetectTunnels blocked on a FIFO")
}
})
} }
} }
@@ -45,16 +57,18 @@ func TestDetectTunnelsRefusesSymlinkedNotesAndSurvivesSwapRaces(t *testing.T) {
b := t.TempDir() b := t.TempDir()
facts := filepath.Join(b, "wiki", "w", "facts") facts := filepath.Join(b, "wiki", "w", "facts")
require.NoError(t, os.MkdirAll(facts, 0o755)) require.NoError(t, os.MkdirAll(facts, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(facts, "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644)) // The static note's title differs from the raced note's, so the
// harness's "inside was read" floor can only be met by reading r.md.
require.NoError(t, os.WriteFile(filepath.Join(facts, "ok.md"), []byte("---\ntitle: Zqstatic Words\n---\n"), 0o644))
require.NoError(t, os.Symlink(brainfstest.Outside(t), filepath.Join(facts, "out.md"))) require.NoError(t, os.Symlink(brainfstest.Outside(t), filepath.Join(facts, "out.md")))
inBrain := brainfstest.WriteSentinelAt(t, filepath.Join(b, "raw", "s.md")) inBrain := brainfstest.WriteSentinelAt(t, filepath.Join(b, "raw", "s.md"))
require.NoError(t, os.Symlink(filepath.Join("..", "..", "..", "raw", "s.md"), filepath.Join(facts, "in.md"))) require.NoError(t, os.Symlink(filepath.Join("..", "..", "..", "raw", "s.md"), filepath.Join(facts, "in.md")))
content := "zqinside phrase and " + brainfstest.Sentinel content := "zqstatic words, zqinside phrase and " + brainfstest.Sentinel
cs, err := brain.DetectTunnels(b, content) cs, err := brain.DetectTunnels(b, content)
require.NoError(t, err) require.NoError(t, err)
got := fmt.Sprintf("%+v", cs) got := fmt.Sprintf("%+v", cs)
assert.Contains(t, got, "Zqinside Phrase", "positive control") assert.Contains(t, got, "Zqstatic Words", "positive control")
assert.NotContains(t, got, brainfstest.Sentinel) assert.NotContains(t, got, brainfstest.Sentinel)
p := filepath.Join(facts, "r.md") p := filepath.Join(facts, "r.md")
@@ -77,3 +91,35 @@ func TestDetectTunnelsSkipsAWikiThatIsNotARealDirectory(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, cs) assert.Empty(t, cs)
} }
// An unreadable directory under wiki/ is a walk error and DetectTunnels
// reports it, as filepath.WalkDir did; it is not silently dropped.
func TestDetectTunnelsReportsAnUnreadableDirectoryUnderWiki(t *testing.T) {
if os.Geteuid() == 0 {
t.Skip("root ignores directory permissions")
}
b := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(b, "wiki", "w", "facts"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(b, "wiki", "w", "facts", "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644))
locked := filepath.Join(b, "wiki", "other", "locked")
require.NoError(t, os.MkdirAll(locked, 0o755))
require.NoError(t, os.Chmod(locked, 0o000))
t.Cleanup(func() { _ = os.Chmod(locked, 0o755) })
_, err := brain.DetectTunnels(b, "zqinside phrase")
assert.ErrorIs(t, err, os.ErrPermission)
}
// _index.md files are generated, not notes: their title never becomes a
// candidate.
func TestDetectTunnelsSkipsIndexFiles(t *testing.T) {
b := t.TempDir()
w := filepath.Join(b, "wiki", "w")
require.NoError(t, os.MkdirAll(filepath.Join(w, "facts"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(w, "_index.md"), []byte("---\ntitle: Zqindex Words\n---\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(w, "facts", "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644))
cs, err := brain.DetectTunnels(b, "zqindex words and zqinside phrase")
require.NoError(t, err)
got := fmt.Sprintf("%+v", cs)
assert.Contains(t, got, "Zqinside Phrase", "positive control")
assert.NotContains(t, got, "Zqindex")
}
-1
View File
@@ -116,7 +116,6 @@ func collectWingEntries(bfs *brainfs.Brain, wingRel string) ([]noteEntry, error)
return out, nil return out, nil
} }
// readTitleAndCreatedIn is readTitleAndCreated through the brain root, // readTitleAndCreatedIn is readTitleAndCreated through the brain root,
// regular files only (hyperguild#157). // regular files only (hyperguild#157).
func readTitleAndCreatedIn(bfs *brainfs.Brain, rel, slug string) (string, string) { func readTitleAndCreatedIn(bfs *brainfs.Brain, rel, slug string) (string, string) {