//go:build linux package stacks import ( "io" "log" "os" "path/filepath" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/appbackup" ) // TestWalkMerge_PreservesSetgid is the #8 fix proof: the non-app merge walk must preserve the SOURCE // dir's setgid bit + group, so the userdata ownership convention (2775 setgid, gid 1000) survives a // whole-drive migration. Companion: the pre-fix path (MkdirAll 0755 / copyFile .Perm()) drops setgid // → this test FAILS on the old code (mutation-proven separately). func TestWalkMerge_PreservesSetgid(t *testing.T) { lg := log.New(io.Discard, "", 0) src := t.TempDir() dst := t.TempDir() // Source userdata-style tree with setgid dirs (the convention). media := filepath.Join(src, "userdata", "media") if err := os.MkdirAll(media, 0o755); err != nil { t.Fatal(err) } for _, d := range []string{filepath.Join(src, "userdata"), media} { if err := os.Chmod(d, os.ModeSetgid|0o775); err != nil { t.Fatal(err) } } if err := os.WriteFile(filepath.Join(media, "movie.txt"), []byte("VID"), 0o664); err != nil { t.Fatal(err) } if err := walkMerge(lg, src, dst, nil, false, nil); err != nil { t.Fatalf("walkMerge: %v", err) } // Target media dir must still carry setgid + the source group. srcFI, _ := os.Stat(media) dstFI, err := os.Stat(filepath.Join(dst, "userdata", "media")) if err != nil { t.Fatalf("target media dir missing: %v", err) } if dstFI.Mode()&os.ModeSetgid == 0 { t.Errorf("migration DROPPED the setgid bit on the dir: mode=%v", dstFI.Mode()) } if dstFI.Mode().Perm() != 0o775 { t.Errorf("target dir perm = %o, want 0775", dstFI.Mode().Perm()) } sg, _ := appbackup.StatGID(srcFI) dg, _ := appbackup.StatGID(dstFI) if sg != dg { t.Errorf("target dir group = %d, want source group %d", dg, sg) } }