package main import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strings" "testing" "time" ) // The review of 2026-10-08 (mesh-catalog #136): each finding, reproduced. // roster writes the machines the mesh renders, name and address. func (f *fake) roster(lines ...string) { f.file(RosterFile, "# test\n"+strings.Join(lines, "\n")+"\n") } // wants writes what the nodes' mounts modules want of this machine, as the tools take it from the bus. func (f *fake) wants(w map[string][]Want) { raw, _ := json.Marshal(WantsFromBus{Wants: w}) f.file(WantsFile, string(raw)) } // mountinfo writes the kernel's mount table with these mount points. func (f *fake) mountinfo(points ...string) { var b strings.Builder for i, p := range points { fmt.Fprintf(&b, "%d 1 0:%d / %s rw,relatime shared:1 - zfs pool%d rw\n", 20+i, 40+i, p, i) } f.file(MountInfo, b.String()) } func (f *fake) symlink(target, at string) { f.dir(filepath.Dir(at)) if err := os.Symlink(target, filepath.Join(f.root, at)); err != nil { f.t.Fatal(err) } } // grantsConfig is a config with grants. func grantsConfig(shares, grants string) string { return "# test\nshares=" + shares + "\ngrants=" + grants + "\nrange=" + meshRange + "\n" } func refusedWhy(s Status, name string) string { for _, r := range s.Refused { if r.Name == name { return r.Why } } return "" } // Finding 1: a share owned by root would squash every client to root. func TestAShareWhoseOwnerIsRootIsRefused(t *testing.T) { f := newFake(t) f.dir("/srv/rootowned") f.dir("/srv/rootgroup") f.mountinfo("/") f.owners = map[string]Owner{"/srv/rootowned": {UID: 0, GID: 2000, Dir: true}, "/srv/rootgroup": {UID: 1001, GID: 0, Dir: true}} f.file(ConfigFile, grantsConfig("a=/srv/rootowned b=/srv/rootgroup", "a=ws:rw b=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "a", Access: "rw"}, {Share: "b", Access: "rw"}}}) s := f.machine().Pass(context.Background(), nil) for _, name := range []string{"a", "b"} { if !strings.Contains(refusedWhy(s, name), "root") { t.Errorf("%s is not refused for being root's: %+v", name, s) } } if strings.Contains(f.read(ExportFile), "anonuid=0") || strings.Contains(f.read(ExportFile), "anongid=0") { t.Errorf("an export squashes to root:\n%s", f.read(ExportFile)) } } // Finding 1, on a real inode: a folder root owns, read as the kernel says, is refused. Run as root (the build // seat, or `unshare -r`), the test's own temporary folder is root's; run as anyone else, a system folder is. func TestARealRootOwnedFolderIsRefused(t *testing.T) { f := newFake(t) f.mountinfo("/") share := "/srv/mine" f.dir(share) root := f.root if os.Getuid() != 0 { info, err := os.Stat("/usr") if err != nil { t.Skip("no /usr to stand for a root-owned folder:", err) } if uid, _ := inodeOwner(info); uid != 0 { t.Skipf("/usr is not root's here (uid %d); run as root or under unshare -r", uid) } root, share = "/", "/usr" } f.file(ConfigFile, grantsConfig("system="+share, "system=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "system", Access: "rw"}}}) m := f.machine() m.ownerOf = nil // the inode as the kernel says it if root == "/" { // Judge only reads: the folder's real path, its inode and the mount table. Nothing is written. sys := &Machine{Root: "/", Run: func(context.Context, string, ...string) Ran { return Ran{} }} _, refused, _ := sys.Judge(Config{Shares: []Share{{Name: "system", Path: share, Access: "rw"}}, Grants: map[string]map[string]string{"system": {"ws": "rw"}}, Range: "10.0.0.0/24"}, map[string]string{"ws": "10.0.0.3"}, map[string][]Want{"ws": {{Share: "system", Access: "rw"}}}) if len(refused) != 1 || !strings.Contains(refused[0].Why, "owned by root") { t.Fatalf("the system folder %s, root's on its inode, is not refused for being root's: %+v", share, refused) } return } s := m.Pass(context.Background(), nil) if !strings.Contains(refusedWhy(s, "system"), "root") { t.Errorf("a folder root owns on its inode is not refused: %+v", s) } } // Finding 2: a symlink is judged where it points, and the export names the real folder. func TestASymlinkIsJudgedAndExportedAtItsRealPath(t *testing.T) { f := newFake(t) f.mountinfo("/") f.restic("/srv/backups") f.dir("/srv/real-library") f.symlink("backups", "/srv/innocent") f.symlink("real-library", "/srv/library") f.file(ConfigFile, grantsConfig("innocent=/srv/innocent library=/srv/library", "innocent=ws:rw library=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "innocent", Access: "rw"}, {Share: "library", Access: "rw"}}}) s := f.machine().Pass(context.Background(), nil) if !strings.Contains(refusedWhy(s, "innocent"), "backup") { t.Errorf("a symlink to the backups is not refused: %+v", s) } text := f.read(ExportFile) if strings.Contains(text, "/srv/innocent") || strings.Contains(text, "/srv/backups") { t.Errorf("the backups are exported through a symlink:\n%s", text) } if !strings.Contains(text, "\n/srv/real-library ") || strings.Contains(text, "\n/srv/library ") { t.Errorf("the export does not name the real folder:\n%s", text) } } // Finding 3: a folder inside a backup repository, or one holding a repository deeper than one level, // is refused; a walk cut short refuses rather than exports. func TestABackupRepositoryAboveOrDeepBelowIsRefusedAndAnUnfinishedLookRefuses(t *testing.T) { f := newFake(t) f.mountinfo("/") f.restic("/srv/repo") f.dir("/srv/repo/data/ab") f.restic("/srv/media/films/old/backup") f.dir("/srv/wide/a") f.dir("/srv/wide/b") f.dir("/srv/wide/c") f.dir("/srv/clean/films/one") f.file(ConfigFile, grantsConfig("inside=/srv/repo/data/ab deep=/srv/media wide=/srv/wide clean=/srv/clean", "inside=ws:rw deep=ws:rw wide=ws:rw clean=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "inside", Access: "rw"}, {Share: "deep", Access: "rw"}, {Share: "wide", Access: "rw"}, {Share: "clean", Access: "rw"}}}) m := f.machine() m.WalkLimit = 4 s := m.Pass(context.Background(), nil) if !strings.Contains(refusedWhy(s, "inside"), "inside the backup repository /srv/repo") { t.Errorf("a folder inside a backup repository: %q", refusedWhy(s, "inside")) } if !strings.Contains(refusedWhy(s, "deep"), "/srv/media/films/old/backup") { t.Errorf("a repository three levels down: %q", refusedWhy(s, "deep")) } m2 := f.machine() m2.WalkLimit = 2 s = m2.Pass(context.Background(), nil) if !strings.Contains(refusedWhy(s, "wide"), "could not make sure") { t.Errorf("a look cut short by its bound did not refuse: %q", refusedWhy(s, "wide")) } m3 := f.machine() s = m3.Pass(context.Background(), nil) if refusedWhy(s, "clean") != "" || refusedWhy(s, "wide") != "" { t.Errorf("a clean folder is refused: %+v", s.Refused) } } // Finding 5: a share is exported to the nodes the server grants it to and whose mounts want it, at the // lower of the two accesses, at the roster's address only; the control node and strangers get nothing. func TestAShareGoesOnlyToTheNodesGrantedAndWantingItAtTheLowerAccess(t *testing.T) { f := newFake(t) f.mountinfo("/", "/srv/library", "/srv/pool") f.dir("/srv/library") f.dir("/srv/pool/downloads") f.dir("/srv/pool/unwanted") f.file(ConfigFile, grantsConfig("library=/srv/library downloads=/srv/pool/downloads unwanted=/srv/pool/unwanted", "library=ws:rw,laptop:ro downloads=ws:rw unwanted=ws:rw")) f.roster("ws 10.0.0.3", "laptop 10.0.0.4", "control 10.0.0.1", "outside 192.0.2.9") f.wants(map[string][]Want{ "ws": {{Share: "library", Access: "ro"}, {Share: "downloads", Access: "rw"}}, "laptop": {{Share: "library", Access: "rw"}}, "control": {{Share: "library", Access: "rw"}}, "stranger": {{Share: "library", Access: "rw"}}, "outside": {{Share: "downloads", Access: "rw"}}, }) f.kernelReads() s := f.machine().Pass(context.Background(), nil) text := f.read(ExportFile) var lib, dl string for _, line := range strings.Split(text, "\n") { if strings.HasPrefix(line, "/srv/library ") { lib = line } if strings.HasPrefix(line, "/srv/pool/downloads ") { dl = line } } if !strings.Contains(lib, " 10.0.0.3(ro,") || !strings.Contains(lib, " 10.0.0.4(ro,") { t.Errorf("the library goes ro to the workstation (it wants ro) and ro to the laptop (granted ro): %q", lib) } if strings.Contains(text, "10.0.0.1") || strings.Contains(text, meshRange) || strings.Contains(text, "192.0.2.9") { t.Errorf("the control node, the whole range or an address outside the range is exported to:\n%s", text) } if !strings.Contains(dl, " 10.0.0.3(rw,") || strings.Count(dl, "(") != 1 { t.Errorf("downloads goes rw to the workstation only: %q", dl) } if strings.Contains(text, "/srv/pool/unwanted") { t.Errorf("a share no node wants is exported:\n%s", text) } var said = strings.Join(s.Said, "\n") for _, want := range []string{"control wants library", "stranger", "unwanted is granted to ws, which does not want it"} { if !strings.Contains(said, want) { t.Errorf("the status does not say %q:\n%s", want, said) } } for _, e := range s.Shares { if e.Name == "library" && (len(e.To) != 2 || !e.Live) { t.Errorf("the library's clients: %+v", e) } } if !strings.Contains(f.read(StatusFile), `"to"`) { t.Errorf("the status lacks each share's clients") } } // With no wants file (the bus never answered), nothing is exported: closed, not open. func TestWithNoWantsNothingIsExported(t *testing.T) { f := newFake(t) f.mountinfo("/") f.dir("/srv/library") f.file(ConfigFile, grantsConfig("library=/srv/library", "library=ws:rw")) f.roster("ws 10.0.0.3") f.machine().Pass(context.Background(), nil) if strings.Contains(f.read(ExportFile), "/srv/library") { t.Errorf("exported with no node asking:\n%s", f.read(ExportFile)) } } // Finding 6: `mp` keeps a share unexported while its filesystem is not mounted. func TestTheExportCarriesMpForItsFilesystem(t *testing.T) { f := newFake(t) f.mountinfo("/", "/srv/library", "/srv/pool") f.dir("/srv/library") f.dir("/srv/pool/downloads") f.dir("/srv/plain") f.file(ConfigFile, grantsConfig("library=/srv/library downloads=/srv/pool/downloads plain=/srv/plain", "library=ws:rw downloads=ws:rw plain=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "library", Access: "rw"}, {Share: "downloads", Access: "rw"}, {Share: "plain", Access: "rw"}}}) f.machine().Pass(context.Background(), nil) text := f.read(ExportFile) if !strings.Contains(text, "/srv/library 10.0.0.3(rw,mp,") { t.Errorf("a dataset share lacks mp:\n%s", text) } if !strings.Contains(text, "/srv/pool/downloads 10.0.0.3(rw,mp=/srv/pool,") { t.Errorf("a folder in a dataset lacks mp=:\n%s", text) } if strings.Contains(text, "/srv/plain 10.0.0.3(rw,mp") { t.Errorf("a folder of the root filesystem names mp:\n%s", text) } } // Finding 7: the module's export file goes when the module is unassigned, and stays on a restart. func TestTheExportFileGoesOnUnassignmentAndStaysOnARestart(t *testing.T) { f := newFake(t) f.mountinfo("/") f.dir("/srv/library") f.file(ConfigFile, grantsConfig("library=/srv/library", "library=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "library", Access: "rw"}}}) f.kernelReads() m := f.machine() m.Pass(context.Background(), nil) f.ran = nil m.Stopped() if !strings.Contains(f.read(ExportFile), "/srv/library") { t.Errorf("a restart (config still there) removed the exports") } for _, r := range f.ran { if strings.Contains(r, "exportfs") { t.Errorf("a restart reloaded the exports: %v", f.ran) } } if err := os.Remove(filepath.Join(f.root, ConfigFile)); err != nil { t.Fatal(err) } m.Stopped() if _, err := os.Stat(filepath.Join(f.root, ExportFile)); err == nil { t.Errorf("the export file stays after the module's config was removed") } if strings.Contains(f.read(Etab), "/srv/library") { t.Errorf("the kernel still exports the folder after unassignment: %q", f.read(Etab)) } } // Finding 9: the seat's schema passes `confirm` as a string. func TestConfirmIsTrueAsABooleanOrTheStringTrue(t *testing.T) { for _, c := range []struct { v any want bool }{{true, true}, {"true", true}, {"TRUE", true}, {"false", false}, {"yes", false}, {nil, false}, {false, false}} { if got := confirmed(map[string]any{"confirm": c.v}); got != c.want { t.Errorf("confirm %#v read as %v", c.v, got) } } } // Grants are read plainly, and "none" grants nothing. func TestParseGrants(t *testing.T) { g, problems := ParseGrants("library=ws:rw,laptop:ro downloads=ws:rw") if len(problems) != 0 || g["library"]["laptop"] != "ro" || g["downloads"]["ws"] != "rw" { t.Errorf("%v %v", g, problems) } g, problems = ParseGrants("none") if len(g) != 0 || len(problems) != 0 { t.Errorf("none: %v %v", g, problems) } _, problems = ParseGrants("library=ws library=x:rw bad=ws:rx") if len(problems) != 3 { t.Errorf("bad grants are said: %v", problems) } } // The wants are taken from each node's `mounts` state: only what is asked of this machine, keyed by the // node that asked. func TestWantsFromTheMountsStateKeepOnlyWhatIsAskedOfThisMachine(t *testing.T) { states := map[string][]byte{ "ws": []byte(`{"wants":[{"server":"home","share":"library","access":"rw"},{"server":"other","share":"x","access":"rw"}],"shares":[]}`), "laptop": []byte(`{"wants":[{"server":"home","share":"library","access":"ro"}]}`), "broken": []byte(`not json`), "none": []byte(`{"shares":[]}`), } w, notes := WantsFromStates("home", states) if len(w) != 2 || len(w["ws"]) != 1 || w["ws"][0].Share != "library" || w["laptop"][0].Access != "ro" { t.Errorf("%+v", w) } if len(notes) != 1 || !strings.Contains(notes[0], "broken") { t.Errorf("a state that cannot be read is said: %v", notes) } m := newFake(t).machine() if err := m.Write(WantsFile, "{}"); err != nil { t.Errorf("the tools may write the wants file: %v", err) } } // The look below a share for a backup repository walks up to WalkLimit folders; on a library of tens of // thousands of folders that must not run on every 30 s pass. A clean result is kept for LookKept, and a // repository put there later is found once that has passed. func TestTheLookBelowIsKeptForAWhileAndThenTakenAgain(t *testing.T) { f := newFake(t) f.mountinfo("/") f.dir("/srv/clean/films/one") f.file(ConfigFile, grantsConfig("clean=/srv/clean", "clean=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "clean", Access: "rw"}}}) m := f.machine() now := time.Date(2026, 10, 8, 19, 0, 0, 0, time.UTC) m.Now = func() time.Time { return now } if s := m.Pass(context.Background(), nil); refusedWhy(s, "clean") != "" { t.Fatalf("a clean folder is refused: %+v", s.Refused) } f.restic("/srv/clean/films/one/backup") now = now.Add(time.Minute) if s := m.Pass(context.Background(), nil); refusedWhy(s, "clean") != "" { t.Errorf("the look was taken again within a minute: %+v", s.Refused) } now = now.Add(LookKept) if s := m.Pass(context.Background(), nil); !strings.Contains(refusedWhy(s, "clean"), "/srv/clean/films/one/backup") { t.Errorf("a repository put there later was not found once the kept look expired: %+v", s.Refused) } } // mesh-host #62 found that a stop the service manager ends with SIGKILL after 90 s leaves the export file // behind. A pass that hangs in a command (sudo, exportfs: 20 s each) must not hold the cleanup: SIGTERM // cancels the running pass, and Stopped has its own short bound, so the file is gone well inside the stop // timeout even when every command hangs. func TestAHangingCommandStillLetsStoppedRemoveTheFileInTime(t *testing.T) { f := newFake(t) f.mountinfo("/") f.dir("/srv/library") f.file(ConfigFile, grantsConfig("library=/srv/library", "library=ws:rw")) f.roster("ws 10.0.0.3") f.wants(map[string][]Want{"ws": {{Share: "library", Access: "rw"}}}) m := f.machine() inner := m.Run hung := make(chan struct{}, 8) m.Run = func(ctx context.Context, name string, args ...string) Ran { line := strings.Join(append([]string{name}, args...), " ") if strings.Contains(line, "exportfs") { hung <- struct{}{} <-ctx.Done() // hangs until its context ends, as a stuck sudo or exportfs would return Ran{Code: -1, Err: ctx.Err().Error()} } return inner(ctx, name, args...) } saved := StopTimeout StopTimeout = 300 * time.Millisecond defer func() { StopTimeout = saved }() ctx, cancel := context.WithCancel(context.Background()) done := make(chan error, 1) go func() { done <- m.RunExports(ctx) }() select { case <-hung: // the first pass is stuck in exportfs case <-time.After(5 * time.Second): t.Fatal("the first pass never reached exportfs") } if err := os.Remove(filepath.Join(f.root, ConfigFile)); err != nil { // unassigned: config removed first t.Fatal(err) } start := time.Now() cancel() // SIGTERM select { case <-done: case <-time.After(3 * time.Second): t.Fatal("the process did not stop within 3 s of SIGTERM while a command hung") } if took := time.Since(start); took > 2*time.Second { t.Errorf("stopping took %s with a hanging command; the bound is StopTimeout (%s)", took, StopTimeout) } if _, err := os.Stat(filepath.Join(f.root, ExportFile)); err == nil { t.Errorf("the export file stays after an unassignment whose commands hung") } }