Skip to content

Commit d7280ce

Browse files
committed
Replace fs.WalkDir with recursive method
1 parent edad0b1 commit d7280ce

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

internal/fixperms/fdfs/fdfs.go

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,30 @@ import (
1414
const resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_MAGICLINKS | unix.RESOLVE_NO_XDEV
1515

1616
// FS uses a file descriptor for a directory as the base of a fs.FS.
17-
type FS uintptr
17+
type FS struct {
18+
file *os.File
19+
}
1820

1921
// DirFS opens the directory dir, and returns an FS rooted at that directory.
20-
// It uses open(2) with O_PATH+O_DIRECTORY+O_CLOEXEC.
21-
func DirFS(dir string) (FS, error) {
22-
bd, err := os.OpenFile(dir, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
22+
// It uses open(2) with O_RDONLY+O_DIRECTORY+O_CLOEXEC. Note that this will
23+
// resolve symlinks in the path, so only use this to open a trusted base path.
24+
func DirFS(dir string) (*FS, error) {
25+
f, err := os.OpenFile(dir, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
2326
if err != nil {
24-
return 0, err
27+
return nil, err
2528
}
26-
return FS(bd.Fd()), nil
29+
return &FS{file: f}, nil
2730
}
2831

2932
// Close closes the file descriptor.
30-
func (s FS) Close() error {
31-
return unix.Close(int(s))
33+
func (s *FS) Close() error {
34+
return s.file.Close()
3235
}
3336

34-
// Open wraps openat2(2) with O_RDONLY+O_NOFOLLOW+O_CLOEXEC.
35-
func (s FS) Open(path string) (fs.File, error) {
36-
fd, err := unix.Openat2(int(s), path, &unix.OpenHow{
37+
// Open wraps openat2(2) with O_RDONLY+O_NOFOLLOW+O_CLOEXEC, and prohibits
38+
// symlinks etc within the path.
39+
func (s *FS) Open(path string) (fs.File, error) {
40+
fd, err := unix.Openat2(int(s.file.Fd()), path, &unix.OpenHow{
3741
Flags: unix.O_RDONLY | unix.O_NOFOLLOW | unix.O_CLOEXEC,
3842
Mode: 0,
3943
Resolve: resolveFlags,
@@ -46,19 +50,69 @@ func (s FS) Open(path string) (fs.File, error) {
4650
}
4751

4852
// Lchown wraps fchownat(2) (with AT_SYMLINK_NOFOLLOW).
49-
func (s FS) Lchown(path string, uid, gid int) error {
50-
return unix.Fchownat(int(s), path, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
53+
func (s *FS) Lchown(path string, uid, gid int) error {
54+
return unix.Fchownat(int(s.file.Fd()), path, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
5155
}
5256

53-
// Sub wraps openat2(2) (with O_PATH+O_DIRECTORY+O_NOFOLLOW+O_CLOEXEC), and returns an FS.
54-
func (s FS) Sub(dir string) (FS, error) {
55-
subFD, err := unix.Openat2(int(s), dir, &unix.OpenHow{
56-
Flags: unix.O_PATH | unix.O_DIRECTORY | unix.O_NOFOLLOW | unix.O_CLOEXEC,
57+
// Sub wraps openat2(2) (with O_RDONLY+O_DIRECTORY+O_NOFOLLOW+O_CLOEXEC), and
58+
// returns an FS.
59+
func (s *FS) Sub(dir string) (*FS, error) {
60+
fd, err := unix.Openat2(int(s.file.Fd()), dir, &unix.OpenHow{
61+
Flags: unix.O_RDONLY | unix.O_DIRECTORY | unix.O_NOFOLLOW | unix.O_CLOEXEC,
5762
Mode: 0,
5863
Resolve: resolveFlags,
5964
})
6065
if err != nil {
61-
return 0, err
66+
return nil, err
67+
}
68+
return &FS{os.NewFile(uintptr(fd), dir)}, nil
69+
}
70+
71+
// RecursiveChown lchowns everything within the receiver.
72+
func (s *FS) RecursiveChown(uid, gid int) error {
73+
// Q: Why not fs.WalkDir(... s.Lchown(path, uid, gid) ... ) ?
74+
// A: fs.WalkDir gives the callback a subpath to each item. So although
75+
// fs.WalkDir doesn't traverse symlinks, there's a race between walking
76+
// each path (no intermediate symlinks), and passing that path to lchown
77+
// (has possibly changed).
78+
// Solution: More openat.
79+
80+
if err := s.Lchown(".", uid, gid); err != nil {
81+
return err
82+
}
83+
84+
// This closure exists so sd.Close happens before the next loop iteration,
85+
// rather than at the end of RecursiveChown.
86+
chownSubdir := func(name string) error {
87+
sd, err := s.Sub(name)
88+
if err != nil {
89+
return err
90+
}
91+
defer sd.Close()
92+
return sd.RecursiveChown(uid, gid)
93+
}
94+
95+
// The "file" within an *FS should always be a directory.
96+
ds, err := s.file.ReadDir(-1)
97+
if err != nil {
98+
return err
99+
}
100+
for _, d := range ds {
101+
if !d.IsDir() {
102+
if err := s.Lchown(d.Name(), uid, gid); err != nil {
103+
return err
104+
}
105+
continue
106+
}
107+
108+
// Make sure we're not about to recurse on a symlink.
109+
if d.Type()&fs.ModeSymlink != 0 {
110+
continue
111+
}
112+
113+
if err := chownSubdir(d.Name()); err != nil {
114+
return err
115+
}
62116
}
63-
return FS(subFD), nil
117+
return nil
64118
}

internal/fixperms/fixer/fixer.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,10 @@ func Main(argv []string, baseDir, uname string) (string, int) {
6666
return exitf(4, "buildkite-agent gid %q not an integer: %v", agentUser.Gid, err)
6767
}
6868

69-
// fs.WalkDir to find everything within the directory.
70-
// fchownat(2) to change the owner of the item.
71-
// We allow symlinks here, but operate on the symlinks themselves.
72-
if err := fs.WalkDir(pd, ".", func(path string, d fs.DirEntry, err error) error {
73-
return pd.Lchown(path, uid, gid)
74-
}); err != nil {
69+
// Do the recursive chown.
70+
if err := pd.RecursiveChown(uid, gid); err != nil {
7571
return exitf(5, "Couldn't recursively chown %s: %v", subpath, err)
7672
}
77-
7873
return exit0()
7974
}
8075

0 commit comments

Comments
 (0)