Files
mesh-host/internal/apply/judge_directories_test.go
T
jschoubben b92fb68116
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/358-judge-directories-read-only delivered: every member is delivered
Judge a node's directories read-only before the walk refuses any (issue 358)
Once a mesh directory's mode and owner go only through a walk that follows no
link another account could make, a directory behind such a link is refused
and its module gated: on the control-node that could stop the controller.
The operator is to be told which directories that walk would refuse on every
node before it lands. So the engine judges the declaration it keeps with the
same walk, making and changing nothing: on a signed ask from the controller
(mesh.node.<node>.ask.judge-directories, held to the hand-over's checks) and
as `nox-mesh-host judge-directories` at the machine.
2026-10-12 13:57:52 +02:00

73 lines
2.9 KiB
Go

//go:build linux
package apply
import (
"os"
"path/filepath"
"strings"
"testing"
)
// The read-only judgement of a node's directories (novox/hq issue 358): one line each — placed, would be refused
// naming the link and who could replace it, or not there yet (placed: an apply makes it) — and nothing made or
// changed.
func TestJudgeDirectoriesSaysWhatTheWalkWouldRefuseAndChangesNothing(t *testing.T) {
base := t.TempDir()
plain := filepath.Join(base, "plain")
if err := os.Mkdir(plain, 0o755); err != nil {
t.Fatal(err)
}
elsewhere := filepath.Join(base, "elsewhere", "state")
if err := os.MkdirAll(elsewhere, 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(filepath.Join(base, "elsewhere"), filepath.Join(base, "linked")); err != nil {
t.Fatal(err)
}
missing := filepath.Join(base, "missing", "deeper")
d := declare(t, `{"id":"a.plain","type":"directory","path":"`+plain+`","mode":"0700"},`+
`{"id":"a.linked","type":"directory","path":"`+filepath.Join(base, "linked", "state")+`","mode":"0700"},`+
`{"id":"a.missing","type":"directory","path":"`+missing+`","mode":"0700"}`)
said := JudgeDirectories(d)
lines := strings.Split(said, "\n")
if len(lines) != 4 || !strings.HasPrefix(lines[0], "3 directories declared: 2 placed, 1 would be refused") {
t.Fatalf("the judgement: %q", said)
}
if lines[1] != plain+": placed" || lines[3] != missing+": placed" {
t.Errorf("a directory the walk reaches is not said placed: %q", said)
}
if !strings.Contains(lines[2], "would be refused") || !strings.Contains(lines[2], filepath.Join(base, "linked")) ||
!strings.Contains(lines[2], "every account") {
t.Errorf("the linked directory is not said refused naming the link and who: %q", lines[2])
}
if _, err := os.Stat(filepath.Join(base, "missing")); !os.IsNotExist(err) {
t.Error("the judgement made a directory")
}
for _, p := range []string{plain, elsewhere} {
if info, _ := os.Stat(p); info.Mode().Perm() != 0o755 {
t.Errorf("the judgement changed %s to %o", p, info.Mode().Perm())
}
}
}
// A link only root could have made is followed by the judgement as by the apply.
func TestJudgeDirectoriesFollowsARootOnlyLink(t *testing.T) {
wasDirs, wasLinks := couldReplace, linkReplacer
couldReplace = func(string, uint32, uint32, uint32) string { return "" }
linkReplacer = func(string, uint32) string { return "" }
t.Cleanup(func() { couldReplace, linkReplacer = wasDirs, wasLinks })
base := t.TempDir()
if err := os.MkdirAll(filepath.Join(base, "real", "state"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink("real", filepath.Join(base, "run")); err != nil {
t.Fatal(err)
}
path := filepath.Join(base, "run", "state")
if said := JudgeDirectories(declare(t, `{"id":"a.s","type":"directory","path":"`+path+`"}`)); !strings.Contains(said, path+": placed") {
t.Fatalf("a directory behind a root-only link is not said placed: %q", said)
}
}