Let the setuid search run to its 15-minute bound, ask pacman once, and call a walk with errors incomplete
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 fail: its merge-check.sh failed: --- FAIL: TestHomesAreAPersonsOrAnAgentsNotAServicesOrRoots (0.00s)
mesh/delivery superseded: a newer head of the same pull request

The search kept a two-minute limit of its own inside its 15-minute bound, so
a long walk always ended incomplete; pacman was asked once per path; and find
exiting with an error but some output was read as a complete walk. The bound
and the not-judged reasons move to the public rootsearch package so the
controller reads the same values (the review of 2026-10-09).
This commit is contained in:
jochen
2026-10-09 12:11:57 +02:00
parent b28d7bbcbf
commit 2673e7a2c9
4 changed files with 195 additions and 40 deletions
+70
View File
@@ -5,6 +5,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/novox/mesh-host/rootsearch"
"os/exec"
"strings"
"testing"
@@ -318,3 +319,72 @@ polkit.addRule(function(a, s) { /* isInGroup("wheel") */ return polkit.Result.YE
t.Error("each .pkla section on its own")
}
}
// The search's bound is the one the controller reads (rootsearch.Bound): a search may run its whole fifteen
// minutes, with no shorter limit inside it that would end every long walk incomplete (the review of 2026-10-09).
func TestTheSearchRunsToItsBoundWithNoShorterLimitInside(t *testing.T) {
if rootsearch.Bound != 15*time.Minute {
t.Fatalf("the shared bound is %s", rootsearch.Bound)
}
m := clean()
var left time.Duration
run := func(ctx context.Context, name string, args ...string) (string, error) {
if name == "find" {
if d, ok := ctx.Deadline(); ok {
left = time.Until(d)
}
}
return m.run(ctx, name, args...)
}
ctx, cancel := context.WithTimeout(t.Context(), SearchBound)
defer cancel()
if _, err := (Exec{Run: run}).setuidSearch(ctx, []string{"/"}); err != nil {
t.Fatal(err)
}
if left < SearchBound-time.Minute {
t.Fatalf("find was given %s, less than the search's bound of %s", left, SearchBound)
}
}
// The package manager is asked about every path found in one call, and answers each: owned, or owned by nothing.
func TestThePackageManagerIsAskedOnceForEveryPathFound(t *testing.T) {
m := clean()
m.setuid = "/usr/bin/sudo\n/usr/bin/passwd\n/usr/local/bin/x\n/opt/y\n"
m.packaged = map[string]bool{"/usr/bin/sudo": true, "/usr/bin/passwd": true}
found, err := (Exec{Run: m.run}).setuidSearch(t.Context(), []string{"/"})
if err != nil {
t.Fatal(err)
}
if m.pacmanCalls != 1 {
t.Errorf("pacman was asked %d times for four paths", m.pacmanCalls)
}
if strings.Join(found, ",") != "/opt/y,/usr/local/bin/x" {
t.Errorf("unowned: %v", found)
}
// An error pacman says that is not "owned by nothing" is no answer.
odd := func(ctx context.Context, name string, args ...string) (string, error) {
if name == "pacman" {
return "", errors.New("pacman exited 1: error: could not open database")
}
return m.run(ctx, name, args...)
}
if _, err := (Exec{Run: odd}).setuidSearch(t.Context(), []string{"/"}); err == nil {
t.Error("a package manager that could not answer was read as an answer")
}
}
// find that exits with an error has not walked everything: the search is incomplete, unless every error was a
// file that vanished while it walked.
func TestAWalkFindDidNotCompleteIsIncomplete(t *testing.T) {
m := clean()
m.setuid = "/usr/local/bin/x\n"
m.findErr = errors.New("find exited 1: find: '/mnt/data/private': Permission denied")
if _, err := (Exec{Run: m.run}).setuidSearch(t.Context(), []string{"/"}); err == nil {
t.Fatal("a walk that could not enter a directory was read as complete")
}
m.findErr = errors.New("find exited 1: find: '/tmp/a': No such file or directory\nfind: '/tmp/b': No such file or directory")
found, err := (Exec{Run: m.run}).setuidSearch(t.Context(), []string{"/"})
if err != nil || len(found) != 1 {
t.Fatalf("files that vanished while find walked: %v %v", found, err)
}
}