Monorepo for Tangled tangled.org
6

Configure Feed

Select the types of activity you want to include in your feed.

1package sandbox 2 3import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 "syscall" 11) 12 13// ChmodRepoTree sets directory modes to 0770 and file modes to 0660 under 14// root, preserving the executable bit on files (hook scripts need it). 15// Symlinks are skipped since their mode is not meaningful. 16// 17// The group bits exist so the knot service (running as the git user, which 18// is in the git group that owns the repos) can still read and write the 19// repo via group permissions even though the repo's UID owner is a virtual 20// UID. Sandbox subprocesses run with NoSetGroups: true so they don't gain 21// group access and cross-owner isolation still holds. 22func ChmodRepoTree(root string) error { 23 return filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { 24 if err != nil { 25 return err 26 } 27 if d.Type()&fs.ModeSymlink != 0 { 28 return nil 29 } 30 if d.IsDir() { 31 return os.Chmod(path, 0770) 32 } 33 info, err := d.Info() 34 if err != nil { 35 return err 36 } 37 mode := fs.FileMode(0660) 38 if info.Mode()&0100 != 0 { 39 mode = 0770 40 } 41 return os.Chmod(path, mode) 42 }) 43} 44 45// ChownRepoTree recursively chowns every entry under root to uid:gid. 46// Entries are processed deepest-first so a directory is only chowned after 47// its contents, preserving the calling process's access throughout the walk. 48// Call ChmodRepoTree first if you also want to tighten permissions; this 49// function only changes ownership. 50func ChownRepoTree(root string, uid int, gid int) error { 51 type entry struct { 52 path string 53 depth int 54 } 55 var entries []entry 56 if err := filepath.WalkDir(root, func(path string, _ fs.DirEntry, err error) error { 57 if err != nil { 58 return err 59 } 60 depth := strings.Count(path, string(filepath.Separator)) 61 entries = append(entries, entry{path, depth}) 62 return nil 63 }); err != nil { 64 return err 65 } 66 67 sort.Slice(entries, func(i, j int) bool { 68 return entries[i].depth > entries[j].depth 69 }) 70 71 for _, e := range entries { 72 if err := os.Lchown(e.path, uid, gid); err != nil { 73 return err 74 } 75 } 76 return nil 77} 78 79// LookupUIDForRepoPath returns the owner UID and GID of the repo directory at 80// repoPath. scanPath is validated as a prefix to guard against directory escape. 81func LookupUIDForRepoPath(scanPath, repoPath string) (uid uint32, gid uint32, err error) { 82 if !strings.HasPrefix(repoPath, scanPath) { 83 return 0, 0, fmt.Errorf("repo path %q is outside scan path %q", repoPath, scanPath) 84 } 85 var stat syscall.Stat_t 86 if err := syscall.Stat(repoPath, &stat); err != nil { 87 return 0, 0, err 88 } 89 return stat.Uid, stat.Gid, nil 90} 91 92// ServiceGid returns the GID of scanPath, which is treated as the "service 93// group" that owns all repositories. Callers chown repo trees to 94// (virtualUID, ServiceGid(scanPath)) so the knot service (a member of this 95// group) retains read+write access via the group bits set by ChmodRepoTree. 96func ServiceGid(scanPath string) (uint32, error) { 97 var stat syscall.Stat_t 98 if err := syscall.Stat(scanPath, &stat); err != nil { 99 return 0, fmt.Errorf("stat %s: %w", scanPath, err) 100 } 101 return stat.Gid, nil 102}