Give the kept-folder tests' folders an unprivileged owner when they run as root (hq ADR 0325)
mesh/merge-gate head d12bba01090d merged into trunk dd9ab7927d96 — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head d12bba01090d merged into trunk dd9ab7927d96 — pass: its merge-check.sh passed
mesh/delivery delivered

The build seat runs merge-check as root, so every folder a test made was root's
data, which is never kept (§12), and 28 tests failed for that alone. A test's
folders now stand for an operator's: run as root, they are given to nobody before
they are judged. The root rule is unchanged.
This commit is contained in:
2026-10-12 19:34:59 +02:00
parent c85c2804a0
commit d12bba0109
3 changed files with 88 additions and 3 deletions
+8
View File
@@ -33,6 +33,14 @@ func TestAnAccessTheMeshMayNotKeepYetIsSaidAsAWaitOfItsOwnKind(t *testing.T) {
if err := os.MkdirAll(filepath.Join(lib, "a film"), 0o755); err != nil {
t.Fatal(err)
}
// Run as root (the build seat's merge-check), the test's folders stand for an operator's, never root's.
if os.Geteuid() == 0 {
for _, p := range []string{dir, lib, filepath.Join(lib, "a film")} {
if err := os.Lchown(p, 65534, 65534); err != nil {
t.Fatal(err)
}
}
}
v := judgeAccess(judgedAccess{module: "radarr", id: "radarr.movies", name: "movies", path: lib,
users: []store.KeptUser{{Identity: "mesh-radarr", UID: 61001, Use: "read-write", From: "container"}}})
if v.state != link.StateWaiting || len(v.waits) != 1 {
+77 -2
View File
@@ -32,7 +32,7 @@ const (
// aclFolder is a folder of the test's own on a filesystem that carries ACLs, or the test is skipped saying why.
func aclFolder(t *testing.T) string {
t.Helper()
dir := t.TempDir()
dir := userDir(t)
fd, err := unix.Open(dir, unix.O_RDONLY|unix.O_DIRECTORY, 0)
if err != nil {
t.Fatal(err)
@@ -110,7 +110,9 @@ func effectiveOn(t *testing.T, path string, uid uint32) uint16 {
func applyAccesses(t *testing.T, known store.State, resources ...string) (Report, store.State, error) {
t.Helper()
toUser(t)
d := declare(t, strings.Join(resources, ","))
toUser(t)
return Apply(context.Background(), archHost(t), d, known, store.OriginDeclared, noServices, nil, nil)
}
@@ -135,6 +137,7 @@ func TestAKeptFolderCarriesAnEntryPerUserAndNothingElseOfItChanges(t *testing.T)
t.Fatal(err)
}
unix.Close(fd)
toUser(t)
_, _, before := readACLs(t, lib)
report, _, err := applyAccesses(t, store.State{},
@@ -253,6 +256,7 @@ func TestWhatIsMadeInAKeptFolderIsGivenItsUsersRights(t *testing.T) {
if err := os.Chmod(usual, 0o644); err != nil {
t.Fatal(err)
}
toUser(t)
c, err := JudgeKept(lib, rec.Want, ActLimits)
if err != nil {
t.Fatal(err)
@@ -269,6 +273,7 @@ func TestWhatIsMadeInAKeptFolderIsGivenItsUsersRights(t *testing.T) {
if r, _ := keptRecord(lib); !r.OutOfWatches {
t.Error("watches that ran out are not recorded")
}
toUser(t)
look := LookKept(lib, rec.Want, SweepLimits)
if look.Repaired == 0 {
t.Errorf("the sweep put nothing back: %+v", look)
@@ -276,6 +281,7 @@ func TestWhatIsMadeInAKeptFolderIsGivenItsUsersRights(t *testing.T) {
if got := effectiveOn(t, usual, radarr); got&6 != 6 {
t.Fatalf("after the sweep a read-write identity may %s", permWords(got))
}
toUser(t)
if c, _ := JudgeKept(lib, rec.Want, ActLimits); c.Narrowed != 0 {
t.Fatalf("after the sweep %d entries still give a user less than its right", c.Narrowed)
}
@@ -329,6 +335,7 @@ func TestAFolderThatHoldsDataIsTakenOnlyOnAWarrantAndLetGoEntryByEntry(t *testin
}
d := declare(t, a)
toUser(t)
done, said, err := TakeFolder(context.Background(), d, "lidarr.music", lib, "warrant-7", "the operator", noServices)
if err != nil {
t.Fatalf("the take act was refused: %v", err)
@@ -357,6 +364,7 @@ func TestAFolderThatHoldsDataIsTakenOnlyOnAWarrantAndLetGoEntryByEntry(t *testin
t.Fatal(err)
}
beforeMask, _ := func() (uint16, bool) { a, _, _ := readACLs(t, song); return a.perm(aclMask, 0) }()
toUser(t)
ReconcileKept(context.Background())
for _, p := range []string{lib, song} {
a, def, _ := readACLs(t, p)
@@ -397,6 +405,7 @@ func TestAWalkEntersNoOtherFilesystem(t *testing.T) {
},
fd: unix.Fstat,
}
toUser(t)
w, err := walkKept(dir, ActLimits, stats, nil, keepVisit(map[uint32]bool{radarr: true}))
if err != nil {
t.Fatal(err)
@@ -456,10 +465,12 @@ func TestAMissingFolderIsMadeWhereItsDataLivesAndWaitsWhereItsDiskIsNot(t *testi
if _, err := os.Stat(books); !errors.Is(err, os.ErrNotExist) {
t.Fatal("a folder was made where its disk is not")
}
toUser(t)
if v := AccessVerdict(context.Background(), books, false, true); v.Kind != VerdictWaitDisk {
t.Errorf("the look judges it %s, not waiting for its disk", v.Kind)
}
// And the folder made before, its dataset gone: its disk is not there, whatever is at its path now.
toUser(t)
if v := AccessVerdict(context.Background(), movies, false, true); v.Kind != VerdictWaitDisk {
t.Errorf("a kept folder whose dataset is not mounted is judged %s", v.Kind)
}
@@ -492,12 +503,14 @@ func TestTheSweepPutsBackWhatAnAppNarrowedAndCoversTheFolderOverLooks(t *testing
if err := os.Chmod(lib, 0o755); err != nil {
t.Fatal(err)
}
toUser(t)
if short, _ := judgeTop(lib, rec.Want); len(short) == 0 {
t.Fatal("a folder whose mask an app narrowed is not said short")
}
if _, _, err := applyAccesses(t, store.State{}, a); err != nil {
t.Fatal(err)
}
toUser(t)
if short, _ := judgeTop(lib, rec.Want); len(short) != 0 {
t.Fatalf("the apply did not put the folder back: %v", short)
}
@@ -506,6 +519,7 @@ func TestTheSweepPutsBackWhatAnAppNarrowedAndCoversTheFolderOverLooks(t *testing
looks, repaired := 0, 0
for {
looks++
toUser(t)
look := LookKept(lib, rec.Want, limits)
repaired += look.Repaired
if look.Failed != "" {
@@ -527,6 +541,7 @@ func TestTheSweepPutsBackWhatAnAppNarrowedAndCoversTheFolderOverLooks(t *testing
if repaired != 25 {
t.Errorf("the sweep put back %d of 25 narrowed entries", repaired)
}
toUser(t)
if c, _ := JudgeKept(lib, rec.Want, ActLimits); c.Narrowed != 0 {
t.Errorf("after the looks %d entries still give the user less than its right", c.Narrowed)
}
@@ -562,6 +577,7 @@ func TestRootsDataOnItsPlacedFilesystemIsNeverKept(t *testing.T) {
if o := outcomeOf(report, "m.data"); o.Action == "waiting" {
t.Errorf("root's data waits, and is never asked about: %+v", o)
}
toUser(t)
if v := AccessVerdict(context.Background(), path, false, true); v.Kind != VerdictRoot {
t.Errorf("the look judges root's data at %s %s", path, v.Kind)
}
@@ -625,6 +641,7 @@ func TestNoWalkRunsWhereTheDiskIsNotThere(t *testing.T) {
t.Fatal(err)
}
MountPoints = func() []string { return []string{"/"} }
toUser(t)
ReconcileKept(context.Background())
rec, had := keptRecord(lib)
if !had || len(rec.Users) != 2 {
@@ -638,6 +655,7 @@ func TestNoWalkRunsWhereTheDiskIsNotThere(t *testing.T) {
}
// The disk back: the drop runs.
MountPoints = func() []string { return []string{"/", dataset} }
toUser(t)
ReconcileKept(context.Background())
if _, still := func() (uint16, bool) { a, _, _ := readACLs(t, lib); return a.perm(aclUser, plex) }(); still {
t.Fatal("the drop did not run once the disk was there")
@@ -672,6 +690,7 @@ func TestADiskMountedOverAFolderKeptEmptyIsNotKeptWithoutAWarrant(t *testing.T)
t.Fatal(err)
}
MountPoints = func() []string { return []string{"/", pool} }
toUser(t)
if v := AccessVerdict(context.Background(), lib, false, true); v.Kind != VerdictWaitKeep {
t.Fatalf("the look judges it %s, and the data on the new disk was never taken", v.Kind)
}
@@ -720,6 +739,7 @@ func TestWideningTheMaskGivesTheOwningGroupNothingMore(t *testing.T) {
}
before := groupRight()
rec, _ := keptRecord(lib)
toUser(t)
LookKept(lib, rec.Want, SweepLimits)
if got := effectiveOn(t, ep, radarr); got&6 != 6 {
t.Fatalf("the sweep did not widen the mask: %s", permWords(got))
@@ -825,13 +845,14 @@ func TestTheWatchEndsWithItsContext(t *testing.T) {
// Review L6: a broken folder's words carry no path.
func TestABrokenFoldersWordsCarryNoPath(t *testing.T) {
dir := t.TempDir()
dir := userDir(t)
keeping(t, nil, nil)
file := filepath.Join(dir, "notafolder")
if err := os.WriteFile(file, nil, 0o644); err != nil {
t.Fatal(err)
}
for _, path := range []string{file, filepath.Join(file, "below")} {
toUser(t)
v := AccessVerdict(context.Background(), path, false, true)
if v.Kind != VerdictBroken || strings.Contains(v.Why, "/") {
t.Errorf("%s: %+v", path, v)
@@ -896,6 +917,7 @@ func TestAContainerWaitsWhileItsAccessesDiskIsNotThere(t *testing.T) {
}
d := declare(t, accessJSON("radarr.movies", lib, "read-write", false, "mesh-radarr:61001:read-write:container")+
`,{"id":"radarr.app","type":"container","name":"radarr","image":"`+pinned+`","volumes":["`+lib+`:/movies"]}`)
toUser(t)
report, _, _ := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, run, nil, nil)
if o := outcomeOf(report, "radarr.app"); o.Action != "waiting" {
t.Fatalf("a container mounting a folder whose disk is not there was not held: %+v", o)
@@ -919,6 +941,7 @@ func TestEntriesThatCouldNotBeDroppedAreSaidUntilTheyAre(t *testing.T) {
t.Fatal(err)
}
MountPoints = func() []string { return []string{"/"} }
toUser(t)
ReconcileKept(context.Background())
left := Leftovers()
if len(left) != 1 || left[0].Access != "radarr.movies" || !strings.Contains(left[0].Why, "could not be removed") ||
@@ -926,6 +949,7 @@ func TestEntriesThatCouldNotBeDroppedAreSaidUntilTheyAre(t *testing.T) {
t.Fatalf("entries that could not be dropped are not said, path-free: %+v", left)
}
MountPoints = func() []string { return []string{"/", dataset} }
toUser(t)
ReconcileKept(context.Background())
if left := Leftovers(); len(left) != 0 {
t.Fatalf("said after the drop ran: %+v", left)
@@ -952,6 +976,7 @@ func TestEntriesLeftOnAFilesystemNoLongerAtTheFolderAreDroppedWhenItIsBack(t *te
if _, _, err := applyAccesses(t, store.State{}, a); err != nil {
t.Fatal(err)
}
toUser(t)
done, _, err := TakeFolder(context.Background(), declare(t, a), "radarr.media", lib, "w-1", "op", noServices)
if err != nil {
t.Fatal(err)
@@ -971,6 +996,7 @@ func TestEntriesLeftOnAFilesystemNoLongerAtTheFolderAreDroppedWhenItIsBack(t *te
}
// The pool goes: the filesystem below holds the folder again, and the entries left there are taken out.
MountPoints = func() []string { return []string{"/"} }
toUser(t)
ReconcileKept(context.Background())
if _, still := func() (uint16, bool) {
a, _, _ := readACLs(t, filepath.Join(lib, "old"))
@@ -1058,6 +1084,7 @@ func placedPool(t *testing.T, source, fsid *string) (pool, lib, taken string) {
if _, _, err := applyAccesses(t, store.State{}, notTaken); err != nil {
t.Fatal(err)
}
toUser(t)
done, _, err := TakeFolder(context.Background(), declare(t, notTaken), "radarr.media", lib, "w-1", "op", noServices)
if err != nil {
t.Fatal(err)
@@ -1082,6 +1109,7 @@ func TestAnotherFilesystemAtTheSameMountPointIsNotTaken(t *testing.T) {
if o := outcomeOf(report, "radarr.media"); o.Action != "waiting" {
t.Fatalf("another filesystem at the same mount point was kept on the old warrant: %+v", o)
}
toUser(t)
if v := AccessVerdict(context.Background(), lib, true, true); v.Kind != VerdictWaitKeep {
t.Fatalf("the look judges it %s", v.Kind)
}
@@ -1100,6 +1128,7 @@ func TestADeviceRenamedIsTheSameFilesystem(t *testing.T) {
if o := outcomeOf(report, "radarr.media"); o.Action == "waiting" {
t.Fatalf("a device renamed reads as another filesystem: %+v", o)
}
toUser(t)
ReconcileKept(context.Background())
if got := effectiveOn(t, filepath.Join(lib, "old"), radarr); got != 7 {
t.Fatalf("the library's entries were walked away: the identity may %s", permWords(got))
@@ -1123,11 +1152,13 @@ func TestAMovedFolderIsNotTakenOnTheOldWarrant(t *testing.T) {
if _, _, err := applyAccesses(t, store.State{}, notTaken); err != nil {
t.Fatal(err)
}
toUser(t)
done, _, err := TakeFolder(context.Background(), declare(t, notTaken), "radarr.media", lib, "w-1", "op", noServices)
if err != nil {
t.Fatal(err)
}
<-done
toUser(t)
before := AccessVerdict(context.Background(), lib, true, true)
film := filepath.Join(lib, "film")
if err := os.WriteFile(film, []byte("data"), 0o644); err != nil {
@@ -1148,10 +1179,12 @@ func TestAMovedFolderIsNotTakenOnTheOldWarrant(t *testing.T) {
if o := outcomeOf(report, "radarr.media"); o.Action != "waiting" {
t.Fatalf("the second apply kept it on the old warrant: %+v", o)
}
toUser(t)
v := AccessVerdict(context.Background(), lib, true, true)
if v.Kind != VerdictWaitKeep || v.Placed == "" || v.Placed == before.Placed || strings.Contains(v.Placed, "/") {
t.Fatalf("the look does not ask again naming the new filesystem: %+v (before %+v)", v, before)
}
toUser(t)
ReconcileKept(context.Background())
if got := effectiveOn(t, film, radarr); got != 0 {
t.Fatal("the data on the new filesystem was walked")
@@ -1199,6 +1232,7 @@ func TestEntriesLeftOnTheFilesystemTheFolderIsOnAreNeverWalkedAway(t *testing.T)
f.Left = []store.KeptLeft{{Mount: pool, Placed: f.Placed, Users: users}}
return true
})
toUser(t)
ReconcileKept(context.Background())
if got := effectiveOn(t, filepath.Join(lib, "old"), radarr); got != 7 {
t.Fatalf("kept=%v: entries on the folder's own filesystem were walked away", kept)
@@ -1226,6 +1260,7 @@ func TestATakenOnOfTheOldFormIsNotTaken(t *testing.T) {
f.Mount, f.TakenOn = "/", "/"
return true
})
toUser(t)
if v := AccessVerdict(context.Background(), lib, true, true); v.Kind != VerdictWaitKeep {
t.Fatalf("an old-form record is read as taken: %s", v.Kind)
}
@@ -1263,6 +1298,7 @@ func takeIt(t *testing.T, lib, access string) {
if _, _, err := applyAccesses(t, store.State{}, access); err != nil {
t.Fatal(err)
}
toUser(t)
done, _, err := TakeFolder(context.Background(), declare(t, access), "radarr.media", lib, "w-1", "op", noServices)
if err != nil {
t.Fatal(err)
@@ -1307,6 +1343,7 @@ func TestAFilesystemThatCannotBeNamedIsNeverKept(t *testing.T) {
if got := effectiveOn(t, lib, radarr); got != 0 {
t.Fatal("an entry was added on a filesystem that cannot be named")
}
toUser(t)
if _, _, err := TakeFolder(context.Background(), declare(t, access), "radarr.media", lib, "w", "op", noServices); err == nil {
t.Fatal("the take act ran on a filesystem that cannot be named")
}
@@ -1327,6 +1364,7 @@ func TestAFilesystemsUUIDNamesItBeforeItsFsid(t *testing.T) {
if o := outcomeOf(report, "radarr.media"); o.Action == "waiting" {
t.Fatalf("a renumbered device with one UUID reads as another filesystem: %+v", o)
}
toUser(t)
ReconcileKept(context.Background())
if got := effectiveOn(t, filepath.Join(lib, "old"), radarr); got != 7 {
t.Fatalf("the library's entries were walked away: %s", permWords(got))
@@ -1348,6 +1386,7 @@ func TestEntriesLeftOnAFilesystemThatNeverReturnsAreForgotten(t *testing.T) {
if len(Leftovers()) != 1 {
t.Fatal("the leftover is not said before it expires")
}
toUser(t)
said := ReconcileKept(context.Background())
if left := Leftovers(); len(left) != 0 {
t.Fatalf("a leftover on a filesystem gone for longer than %s is still said: %+v", LeftFor, left)
@@ -1446,3 +1485,39 @@ func flightsDone(t *testing.T) {
return len(inFlight.reads) == 0
})
}
// userDirs are the folders the running test made, which toUser gives an unprivileged owner when it runs as root.
var userDirs []string
// userDir is a t.TempDir that toUser gives an unprivileged owner when the test runs as root (the build seat runs
// merge-check as root): root's data is never kept (ADR 0325 §12), and a test's own folders stand for an operator's.
func userDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
userDirs = append(userDirs, dir)
t.Cleanup(func() { userDirs = nil })
return dir
}
// unprivileged is the owner toUser gives a test's folders: nobody.
const unprivileged = 65534
// toUser gives everything under the running test's own folders to an unprivileged owner when the test runs as
// root, so they are judged as an operator's data is; as anyone else it changes nothing.
func toUser(t *testing.T) {
t.Helper()
if os.Geteuid() != 0 {
return
}
for _, dir := range userDirs {
err := filepath.WalkDir(dir, func(p string, _ os.DirEntry, err error) error {
if err != nil {
return err
}
return os.Lchown(p, unprivileged, unprivileged)
})
if err != nil {
t.Fatalf("giving the test's folders to an unprivileged owner: %v", err)
}
}
}
+3 -1
View File
@@ -22,7 +22,7 @@ import (
// made, no container held, nothing judged. These are origin/main's outcomes, word for word.
func TestAnOlderControllersAccessesAreAppliedExactlyAsBefore(t *testing.T) {
keeping(t, nil, nil)
dir := t.TempDir()
dir := userDir(t)
unwritable := filepath.Join(dir, "library") // its group may not write it
empty := filepath.Join(dir, "spool")
absent := filepath.Join(dir, "media", "movies")
@@ -66,6 +66,7 @@ func TestAnOlderControllersAccessesAreAppliedExactlyAsBefore(t *testing.T) {
}
return "", nil
}
toUser(t)
report, _, err := Apply(context.Background(), archHost(t), declare(t, strings.Join(resources, ",")), store.State{},
store.OriginDeclared, run, nil, nil)
@@ -126,6 +127,7 @@ func TestALeftoverSaysWhyItsWalkCouldNotRun(t *testing.T) {
t.Fatal(err)
}
uuid, fsid, source = "", "0000000000000000", "" // nothing names it now
toUser(t)
ReconcileKept(context.Background())
left := Leftovers()
if len(left) != 1 || strings.Contains(left[0].Why, "disk") || !strings.Contains(left[0].Why, "could not be named") {