//go:build linux package apply import ( "errors" "os" "path/filepath" "strings" "testing" "github.com/novox/mesh-host/internal/declaration" "github.com/novox/mesh-host/internal/store" ) // The replay of novox/hq issue 358, written only with what the node-engine had before its fix, so it can be laid // over the commit before (mesh-lab's replays register): a mesh directory outside every home was given its mode // and owner by path, so a parent swapped for a link between two applies carried root's chmod to the link's target. // aMeshDirectory applies a directory declared at /parent/state once, as the mesh's own, and answers it // with the record a second apply would read. func aMeshDirectory(t *testing.T) (base string, r *declaration.Directory, previous store.Applied) { t.Helper() base = t.TempDir() r = &declaration.Directory{ID: "mod.state", Path: filepath.Join(base, "parent", "state"), Mode: "0700"} out, err := applyDirectory(r, store.Applied{}, false) if err != nil { t.Fatalf("the first apply: %v", err) } if out.Action != "created" { t.Fatalf("the first apply said %q, want created", out.Action) } return base, r, store.Applied{ID: r.ID, Type: "directory", Target: r.Path} } // swapParentForLink moves /parent away and puts a link to elsewhere (as written) in its place, where // elsewhere holds a directory state of mode 0755: what an account that can write does between two applies. func swapParentForLink(t *testing.T, base, to string) (target string) { t.Helper() elsewhere := filepath.Join(base, "elsewhere") target = filepath.Join(elsewhere, "state") if err := os.MkdirAll(target, 0o755); err != nil { t.Fatal(err) } if err := os.Chmod(target, 0o755); err != nil { t.Fatal(err) } if err := os.Rename(filepath.Join(base, "parent"), filepath.Join(base, "moved")); err != nil { t.Fatal(err) } if err := os.Symlink(to, filepath.Join(base, "parent")); err != nil { t.Fatal(err) } return target } func permOf(t *testing.T, path string) os.FileMode { t.Helper() info, err := os.Lstat(path) if err != nil { t.Fatal(err) } return info.Mode().Perm() } // The acceptance of issue 358: a parent swapped for a link after an apply leaves the link's target untouched, and // the refusal names the link and who could have made it. func TestAParentSwappedForALinkLeavesItsTargetUntouched(t *testing.T) { base, r, previous := aMeshDirectory(t) target := swapParentForLink(t, base, filepath.Join(base, "elsewhere")) _, err := applyDirectory(r, previous, true) var refusal *PlacementRefusedError if !errors.As(err, &refusal) { t.Fatalf("the directory reached through a link was not refused: %v", err) } if !strings.Contains(err.Error(), filepath.Join(base, "parent")) || !strings.Contains(err.Error(), "could replace") { t.Errorf("the refusal does not name the link and who could replace it: %v", err) } if got := permOf(t, target); got != 0o755 { t.Errorf("the link's target was given mode %o (was 755): root's chmod followed the link", got) } }