mirror of https://github.com/golang/go.git
[release-branch.go1.21] 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. Fixes #67695. Change-Id: Ic1111d688eebc8804a87d39d3261c2a6eb33f176 Reviewed-on: https://go-review.googlesource.com/c/go/+/589057 Reviewed-by: Matthew Dempsky <mdempsky@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
parent
c8e40338cf
commit
9488a444d9
|
|
@ -157,7 +157,7 @@ const (
|
||||||
kindNonBlock
|
kindNonBlock
|
||||||
// kindNoPoll means that we should not put the descriptor into
|
// kindNoPoll means that we should not put the descriptor into
|
||||||
// non-blocking mode, because we know it is not a pipe or FIFO.
|
// non-blocking mode, because we know it is not a pipe or FIFO.
|
||||||
// Used by openFdAt for directories.
|
// Used by openDirAt for directories.
|
||||||
kindNoPoll
|
kindNoPoll
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -256,7 +256,7 @@ func epipecheck(file *File, e error) {
|
||||||
const DevNull = "/dev/null"
|
const DevNull = "/dev/null"
|
||||||
|
|
||||||
// openFileNolog is the Unix implementation of OpenFile.
|
// openFileNolog is the Unix implementation of OpenFile.
|
||||||
// Changes here should be reflected in openFdAt, if relevant.
|
// Changes here should be reflected in openDirAt, if relevant.
|
||||||
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
|
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
|
||||||
setSticky := false
|
setSticky := false
|
||||||
if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
|
if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
|
||||||
|
|
|
||||||
|
|
@ -74,22 +74,7 @@ func removeAllFrom(parent *File, base string) error {
|
||||||
if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
|
if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
|
||||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||||
}
|
}
|
||||||
|
uErr := err
|
||||||
// Is this a directory we need to recurse into?
|
|
||||||
var statInfo syscall.Stat_t
|
|
||||||
statErr := ignoringEINTR(func() error {
|
|
||||||
return unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
|
|
||||||
})
|
|
||||||
if statErr != nil {
|
|
||||||
if IsNotExist(statErr) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &PathError{Op: "fstatat", Path: base, Err: statErr}
|
|
||||||
}
|
|
||||||
if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
|
|
||||||
// Not a directory; return the error from the unix.Unlinkat.
|
|
||||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the directory's entries.
|
// Remove the directory's entries.
|
||||||
var recurseErr error
|
var recurseErr error
|
||||||
|
|
@ -98,11 +83,15 @@ func removeAllFrom(parent *File, base string) error {
|
||||||
var respSize int
|
var respSize int
|
||||||
|
|
||||||
// Open the directory to recurse into
|
// Open the directory to recurse into
|
||||||
file, err := openFdAt(parentFd, base)
|
file, err := openDirAt(parentFd, base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if IsNotExist(err) {
|
if IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err == syscall.ENOTDIR {
|
||||||
|
// Not a directory; return the error from the unix.Unlinkat.
|
||||||
|
return &PathError{Op: "unlinkat", Path: base, Err: uErr}
|
||||||
|
}
|
||||||
recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
|
recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -168,16 +157,19 @@ func removeAllFrom(parent *File, base string) error {
|
||||||
return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
|
return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
|
||||||
}
|
}
|
||||||
|
|
||||||
// openFdAt opens path relative to the directory in fd.
|
// openDirAt opens a directory name relative to the directory referred to by
|
||||||
// Other than that this should act like openFileNolog.
|
// the file descriptor dirfd. If name is anything but a directory (this
|
||||||
|
// includes a symlink to one), it should return an error. Other than that this
|
||||||
|
// should act like openFileNolog.
|
||||||
|
//
|
||||||
// This acts like openFileNolog rather than OpenFile because
|
// This acts like openFileNolog rather than OpenFile because
|
||||||
// we are going to (try to) remove the file.
|
// we are going to (try to) remove the file.
|
||||||
// The contents of this file are not relevant for test caching.
|
// The contents of this file are not relevant for test caching.
|
||||||
func openFdAt(dirfd int, name string) (*File, error) {
|
func openDirAt(dirfd int, name string) (*File, error) {
|
||||||
var r int
|
var r int
|
||||||
for {
|
for {
|
||||||
var e error
|
var e error
|
||||||
r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
|
r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -634,6 +634,7 @@ const (
|
||||||
O_APPEND = 0x8
|
O_APPEND = 0x8
|
||||||
O_CLOEXEC = 0x800000
|
O_CLOEXEC = 0x800000
|
||||||
O_CREAT = 0x100
|
O_CREAT = 0x100
|
||||||
|
O_DIRECTORY = 0x1000000
|
||||||
O_DSYNC = 0x40
|
O_DSYNC = 0x40
|
||||||
O_EXCL = 0x400
|
O_EXCL = 0x400
|
||||||
O_EXEC = 0x400000
|
O_EXEC = 0x400000
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue