Skip to content

Commit a3a584e

Browse files
kolyshkingopherbot
authored andcommitted
os: RemoveAll: fix symlink race for unix
Since all the platforms now support O_DIRECTORY flag for open, it can be used to (together with O_NOFOLLOW) to ensure we open a directory, thus eliminating the need to call stat before open. This fixes the symlink race, when a directory is replaced by a symlink in between stat and open calls. While at it, rename openFdAt to openDirAt, because this function is (and was) meant for directories only. NOTE Solaris supports O_DIRECTORY since before Solaris 11 (which is the only version Go supports since supported version now), and Illumos always had it. The only missing piece was O_DIRECTORY flag value, which is taken from golang.org/x/sys/unix. Updates #52745. Change-Id: Ic1111d688eebc8804a87d39d3261c2a6eb33f176 Reviewed-on: https://go-review.googlesource.com/c/go/+/588495 Reviewed-by: Ian Lance Taylor <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Aleksa Sarai <[email protected]> Reviewed-by: Than McIntosh <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
1 parent 3dcb962 commit a3a584e

3 files changed

Lines changed: 16 additions & 23 deletions

File tree

src/os/file_unix.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ const (
152152
kindSock
153153
// kindNoPoll means that we should not put the descriptor into
154154
// non-blocking mode, because we know it is not a pipe or FIFO.
155-
// Used by openFdAt and openDirNolog for directories.
155+
// Used by openDirAt and openDirNolog for directories.
156156
kindNoPoll
157157
)
158158

@@ -260,7 +260,7 @@ func epipecheck(file *File, e error) {
260260
const DevNull = "/dev/null"
261261

262262
// openFileNolog is the Unix implementation of OpenFile.
263-
// Changes here should be reflected in openFdAt and openDirNolog, if relevant.
263+
// Changes here should be reflected in openDirAt and openDirNolog, if relevant.
264264
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
265265
setSticky := false
266266
if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {

src/os/removeall_at.go

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,7 @@ func removeAllFrom(parent *File, base string) error {
7474
if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
7575
return &PathError{Op: "unlinkat", Path: base, Err: err}
7676
}
77-
78-
// Is this a directory we need to recurse into?
79-
var statInfo syscall.Stat_t
80-
statErr := ignoringEINTR(func() error {
81-
return unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
82-
})
83-
if statErr != nil {
84-
if IsNotExist(statErr) {
85-
return nil
86-
}
87-
return &PathError{Op: "fstatat", Path: base, Err: statErr}
88-
}
89-
if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
90-
// Not a directory; return the error from the unix.Unlinkat.
91-
return &PathError{Op: "unlinkat", Path: base, Err: err}
92-
}
77+
uErr := err
9378

9479
// Remove the directory's entries.
9580
var recurseErr error
@@ -98,11 +83,15 @@ func removeAllFrom(parent *File, base string) error {
9883
var respSize int
9984

10085
// Open the directory to recurse into
101-
file, err := openFdAt(parentFd, base)
86+
file, err := openDirAt(parentFd, base)
10287
if err != nil {
10388
if IsNotExist(err) {
10489
return nil
10590
}
91+
if err == syscall.ENOTDIR {
92+
// Not a directory; return the error from the unix.Unlinkat.
93+
return &PathError{Op: "unlinkat", Path: base, Err: uErr}
94+
}
10695
recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
10796
break
10897
}
@@ -168,16 +157,19 @@ func removeAllFrom(parent *File, base string) error {
168157
return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
169158
}
170159

171-
// openFdAt opens path relative to the directory in fd.
172-
// Other than that this should act like openFileNolog.
160+
// openDirAt opens a directory name relative to the directory referred to by
161+
// the file descriptor dirfd. If name is anything but a directory (this
162+
// includes a symlink to one), it should return an error. Other than that this
163+
// should act like openFileNolog.
164+
//
173165
// This acts like openFileNolog rather than OpenFile because
174166
// we are going to (try to) remove the file.
175167
// The contents of this file are not relevant for test caching.
176-
func openFdAt(dirfd int, name string) (*File, error) {
168+
func openDirAt(dirfd int, name string) (*File, error) {
177169
var r int
178170
for {
179171
var e error
180-
r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
172+
r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
181173
if e == nil {
182174
break
183175
}

src/syscall/zerrors_solaris_amd64.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)