aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/os
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/os')
-rw-r--r--libgo/go/os/file_unix.go1
-rw-r--r--libgo/go/os/path.go8
-rw-r--r--libgo/go/os/removeall_at.go50
-rw-r--r--libgo/go/os/removeall_noat.go6
-rw-r--r--libgo/go/os/removeall_test.go80
5 files changed, 128 insertions, 17 deletions
diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go
index 5f7ab30..912ba5a 100644
--- a/libgo/go/os/file_unix.go
+++ b/libgo/go/os/file_unix.go
@@ -192,6 +192,7 @@ func epipecheck(file *File, e error) {
const DevNull = "/dev/null"
// openFileNolog is the Unix implementation of OpenFile.
+// Changes here should be reflected in openFdAt, if relevant.
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
setSticky := false
if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
diff --git a/libgo/go/os/path.go b/libgo/go/os/path.go
index 30cc6c8..104b7ce 100644
--- a/libgo/go/os/path.go
+++ b/libgo/go/os/path.go
@@ -58,6 +58,14 @@ func MkdirAll(path string, perm FileMode) error {
return nil
}
+// RemoveAll removes path and any children it contains.
+// It removes everything it can but returns the first error
+// it encounters. If the path does not exist, RemoveAll
+// returns nil (no error).
+func RemoveAll(path string) error {
+ return removeAll(path)
+}
+
// endsWithDot reports whether the final component of path is ".".
func endsWithDot(path string) bool {
if path == "." {
diff --git a/libgo/go/os/removeall_at.go b/libgo/go/os/removeall_at.go
index 5128de7..d1210ee 100644
--- a/libgo/go/os/removeall_at.go
+++ b/libgo/go/os/removeall_at.go
@@ -9,10 +9,11 @@ package os
import (
"internal/syscall/unix"
"io"
+ "runtime"
"syscall"
)
-func RemoveAll(path string) error {
+func removeAll(path string) error {
if path == "" {
// fail silently to retain compatibility with previous behavior
// of RemoveAll. See issue 28830.
@@ -56,8 +57,13 @@ func removeAllFrom(parent *File, path string) error {
return nil
}
- // If not a "is directory" error, we have a problem
- if err != syscall.EISDIR && err != syscall.EPERM {
+ // EISDIR means that we have a directory, and we need to
+ // remove its contents.
+ // EPERM or EACCES means that we don't have write permission on
+ // the parent directory, but this entry might still be a directory
+ // whose contents need to be removed.
+ // Otherwise just return the error.
+ if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
return err
}
@@ -68,11 +74,11 @@ func removeAllFrom(parent *File, path string) error {
return statErr
}
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 Remove.
return err
}
- // Remove the directory's entries
+ // Remove the directory's entries.
var recurseErr error
for {
const request = 1024
@@ -87,7 +93,7 @@ func removeAllFrom(parent *File, path string) error {
}
names, readErr := file.Readdirnames(request)
- // Errors other than EOF should stop us from continuing
+ // Errors other than EOF should stop us from continuing.
if readErr != nil && readErr != io.EOF {
file.Close()
if IsNotExist(readErr) {
@@ -116,7 +122,7 @@ func removeAllFrom(parent *File, path string) error {
}
}
- // Remove the directory itself
+ // Remove the directory itself.
unlinkError := unix.Unlinkat(parentFd, path, unix.AT_REMOVEDIR)
if unlinkError == nil || IsNotExist(unlinkError) {
return nil
@@ -128,11 +134,31 @@ func removeAllFrom(parent *File, path string) error {
return unlinkError
}
-func openFdAt(fd int, path string) (*File, error) {
- fd, err := unix.Openat(fd, path, O_RDONLY, 0)
- if err != nil {
- return nil, err
+// openFdAt opens path relative to the directory in fd.
+// Other than that this should act like openFileNolog.
+// This acts like openFileNolog rather than OpenFile because
+// we are going to (try to) remove the file.
+// The contents of this file are not relevant for test caching.
+func openFdAt(dirfd int, name string) (*File, error) {
+ var r int
+ for {
+ var e error
+ r, e = unix.Openat(dirfd, name, O_RDONLY, 0)
+ if e == nil {
+ break
+ }
+
+ // See comment in openFileNolog.
+ if runtime.GOOS == "darwin" && e == syscall.EINTR {
+ continue
+ }
+
+ return nil, &PathError{"openat", name, e}
+ }
+
+ if !supportsCloseOnExec {
+ syscall.CloseOnExec(r)
}
- return NewFile(uintptr(fd), path), nil
+ return newFile(uintptr(r), name, kindOpenFile), nil
}
diff --git a/libgo/go/os/removeall_noat.go b/libgo/go/os/removeall_noat.go
index 47fff42..7d9f73e 100644
--- a/libgo/go/os/removeall_noat.go
+++ b/libgo/go/os/removeall_noat.go
@@ -11,11 +11,7 @@ import (
"syscall"
)
-// RemoveAll removes path and any children it contains.
-// It removes everything it can but returns the first error
-// it encounters. If the path does not exist, RemoveAll
-// returns nil (no error).
-func RemoveAll(path string) error {
+func removeAll(path string) error {
if path == "" {
// fail silently to retain compatibility with previous behavior
// of RemoveAll. See issue 28830.
diff --git a/libgo/go/os/removeall_test.go b/libgo/go/os/removeall_test.go
index 0f7dce0..9dab0d4 100644
--- a/libgo/go/os/removeall_test.go
+++ b/libgo/go/os/removeall_test.go
@@ -292,3 +292,83 @@ func TestRemoveReadOnlyDir(t *testing.T) {
t.Error("subdirectory was not removed")
}
}
+
+// Issue #29983.
+func TestRemoveAllButReadOnly(t *testing.T) {
+ switch runtime.GOOS {
+ case "nacl", "js", "windows":
+ t.Skipf("skipping test on %s", runtime.GOOS)
+ }
+
+ if Getuid() == 0 {
+ t.Skip("skipping test when running as root")
+ }
+
+ t.Parallel()
+
+ tempDir, err := ioutil.TempDir("", "TestRemoveAllButReadOnly-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(tempDir)
+
+ dirs := []string{
+ "a",
+ "a/x",
+ "a/x/1",
+ "b",
+ "b/y",
+ "b/y/2",
+ "c",
+ "c/z",
+ "c/z/3",
+ }
+ readonly := []string{
+ "b",
+ }
+ inReadonly := func(d string) bool {
+ for _, ro := range readonly {
+ if d == ro {
+ return true
+ }
+ dd, _ := filepath.Split(d)
+ if filepath.Clean(dd) == ro {
+ return true
+ }
+ }
+ return false
+ }
+
+ for _, dir := range dirs {
+ if err := Mkdir(filepath.Join(tempDir, dir), 0777); err != nil {
+ t.Fatal(err)
+ }
+ }
+ for _, dir := range readonly {
+ d := filepath.Join(tempDir, dir)
+ if err := Chmod(d, 0555); err != nil {
+ t.Fatal(err)
+ }
+
+ // Defer changing the mode back so that the deferred
+ // RemoveAll(tempDir) can succeed.
+ defer Chmod(d, 0777)
+ }
+
+ if err := RemoveAll(tempDir); err == nil {
+ t.Fatal("RemoveAll succeeded unexpectedly")
+ }
+
+ for _, dir := range dirs {
+ _, err := Stat(filepath.Join(tempDir, dir))
+ if inReadonly(dir) {
+ if err != nil {
+ t.Errorf("file %q was deleted but should still exist", dir)
+ }
+ } else {
+ if err == nil {
+ t.Errorf("file %q still exists but should have been deleted", dir)
+ }
+ }
+ }
+}