Commit d039e12b authored by Baokun Lee's avatar Baokun Lee Committed by Ian Lance Taylor

os: consistently return PathError from RemoveAll

Fixes #30491

Change-Id: If4070e5d39d8649643d7e90f6f3eb499642e25ab
Reviewed-on: https://go-review.googlesource.com/c/go/+/164720
Run-TryBot: Baokun Lee <nototon@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
Reviewed-by: default avatarEmmanuel Odeke <emm.odeke@gmail.com>
parent 0a7bc8f4
...@@ -62,6 +62,7 @@ func MkdirAll(path string, perm FileMode) error { ...@@ -62,6 +62,7 @@ func MkdirAll(path string, perm FileMode) error {
// It removes everything it can but returns the first error // It removes everything it can but returns the first error
// it encounters. If the path does not exist, RemoveAll // it encounters. If the path does not exist, RemoveAll
// returns nil (no error). // returns nil (no error).
// If there is an error, it will be of type *PathError.
func RemoveAll(path string) error { func RemoveAll(path string) error {
return removeAll(path) return removeAll(path)
} }
......
...@@ -51,7 +51,7 @@ func splitPath(path string) (string, string) { ...@@ -51,7 +51,7 @@ func splitPath(path string) (string, string) {
// Remove leading directory path // Remove leading directory path
for i--; i >= 0; i-- { for i--; i >= 0; i-- {
if path[i] == '/' { if path[i] == '/' {
dirname = path[:i+1] dirname = path[:i]
basename = path[i+1:] basename = path[i+1:]
break break
} }
......
...@@ -46,13 +46,20 @@ func removeAll(path string) error { ...@@ -46,13 +46,20 @@ func removeAll(path string) error {
} }
defer parent.Close() defer parent.Close()
return removeAllFrom(parent, base) if err := removeAllFrom(parent, base); err != nil {
if pathErr, ok := err.(*PathError); ok {
pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
err = pathErr
}
return err
}
return nil
} }
func removeAllFrom(parent *File, path string) error { func removeAllFrom(parent *File, base string) error {
parentFd := int(parent.Fd()) parentFd := int(parent.Fd())
// Simple case: if Unlink (aka remove) works, we're done. // Simple case: if Unlink (aka remove) works, we're done.
err := unix.Unlinkat(parentFd, path, 0) err := unix.Unlinkat(parentFd, base, 0)
if err == nil || IsNotExist(err) { if err == nil || IsNotExist(err) {
return nil return nil
} }
...@@ -64,21 +71,21 @@ func removeAllFrom(parent *File, path string) error { ...@@ -64,21 +71,21 @@ func removeAllFrom(parent *File, path string) error {
// whose contents need to be removed. // whose contents need to be removed.
// Otherwise just return the error. // Otherwise just return the error.
if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES { if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
return err return &PathError{"unlinkat", base, err}
} }
// Is this a directory we need to recurse into? // Is this a directory we need to recurse into?
var statInfo syscall.Stat_t var statInfo syscall.Stat_t
statErr := unix.Fstatat(parentFd, path, &statInfo, unix.AT_SYMLINK_NOFOLLOW) statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
if statErr != nil { if statErr != nil {
if IsNotExist(statErr) { if IsNotExist(statErr) {
return nil return nil
} }
return statErr return &PathError{"fstatat", base, statErr}
} }
if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
// Not a directory; return the error from the Remove. // Not a directory; return the error from the unix.Unlinkat.
return err return &PathError{"unlinkat", base, err}
} }
// Remove the directory's entries. // Remove the directory's entries.
...@@ -87,12 +94,12 @@ func removeAllFrom(parent *File, path string) error { ...@@ -87,12 +94,12 @@ func removeAllFrom(parent *File, path string) error {
const request = 1024 const request = 1024
// Open the directory to recurse into // Open the directory to recurse into
file, err := openFdAt(parentFd, path) file, err := openFdAt(parentFd, base)
if err != nil { if err != nil {
if IsNotExist(err) { if IsNotExist(err) {
return nil return nil
} }
recurseErr = err recurseErr = &PathError{"openfdat", base, err}
break break
} }
...@@ -103,12 +110,15 @@ func removeAllFrom(parent *File, path string) error { ...@@ -103,12 +110,15 @@ func removeAllFrom(parent *File, path string) error {
if IsNotExist(readErr) { if IsNotExist(readErr) {
return nil return nil
} }
return readErr return &PathError{"readdirnames", base, readErr}
} }
for _, name := range names { for _, name := range names {
err := removeAllFrom(file, name) err := removeAllFrom(file, name)
if err != nil { if err != nil {
if pathErr, ok := err.(*PathError); ok {
pathErr.Path = base + string(PathSeparator) + pathErr.Path
}
recurseErr = err recurseErr = err
} }
} }
...@@ -127,7 +137,7 @@ func removeAllFrom(parent *File, path string) error { ...@@ -127,7 +137,7 @@ func removeAllFrom(parent *File, path string) error {
} }
// Remove the directory itself. // Remove the directory itself.
unlinkError := unix.Unlinkat(parentFd, path, unix.AT_REMOVEDIR) unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR)
if unlinkError == nil || IsNotExist(unlinkError) { if unlinkError == nil || IsNotExist(unlinkError) {
return nil return nil
} }
...@@ -135,7 +145,7 @@ func removeAllFrom(parent *File, path string) error { ...@@ -135,7 +145,7 @@ func removeAllFrom(parent *File, path string) error {
if recurseErr != nil { if recurseErr != nil {
return recurseErr return recurseErr
} }
return unlinkError return &PathError{"unlinkat", base, unlinkError}
} }
// openFdAt opens path relative to the directory in fd. // openFdAt opens path relative to the directory in fd.
...@@ -157,7 +167,7 @@ func openFdAt(dirfd int, name string) (*File, error) { ...@@ -157,7 +167,7 @@ func openFdAt(dirfd int, name string) (*File, error) {
continue continue
} }
return nil, &PathError{"openat", name, e} return nil, e
} }
if !supportsCloseOnExec { if !supportsCloseOnExec {
......
...@@ -286,7 +286,7 @@ func TestRemoveReadOnlyDir(t *testing.T) { ...@@ -286,7 +286,7 @@ func TestRemoveReadOnlyDir(t *testing.T) {
} }
// Issue #29983. // Issue #29983.
func TestRemoveAllButReadOnly(t *testing.T) { func TestRemoveAllButReadOnlyAndPathError(t *testing.T) {
switch runtime.GOOS { switch runtime.GOOS {
case "nacl", "js", "windows": case "nacl", "js", "windows":
t.Skipf("skipping test on %s", runtime.GOOS) t.Skipf("skipping test on %s", runtime.GOOS)
...@@ -347,10 +347,21 @@ func TestRemoveAllButReadOnly(t *testing.T) { ...@@ -347,10 +347,21 @@ func TestRemoveAllButReadOnly(t *testing.T) {
defer Chmod(d, 0777) defer Chmod(d, 0777)
} }
if err := RemoveAll(tempDir); err == nil { err = RemoveAll(tempDir)
if err == nil {
t.Fatal("RemoveAll succeeded unexpectedly") t.Fatal("RemoveAll succeeded unexpectedly")
} }
// The error should be of type *PathError.
// see issue 30491 for details.
if pathErr, ok := err.(*PathError); ok {
if g, w := pathErr.Path, filepath.Join(tempDir, "b", "y"); g != w {
t.Errorf("got %q, expected pathErr.path %q", g, w)
}
} else {
t.Errorf("got %T, expected *os.PathError", err)
}
for _, dir := range dirs { for _, dir := range dirs {
_, err := Stat(filepath.Join(tempDir, dir)) _, err := Stat(filepath.Join(tempDir, dir))
if inReadonly(dir) { if inReadonly(dir) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment