os: when looping in RemoveAll, close and re-open directory
On some systems removing files can cause a directory to be re-shuffled,
so simply continuing to read files can cause us to miss some.
Close and re-open the directory when looping, to avoid that.
Read more files each time through the loop, to reduce the chance of
having to re-open.
Fixes #20841
Change-Id: I98a14774ca63786ad05ba5000cbdb01ad2884332
Reviewed-on: https://go-review.googlesource.com/121255
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/src/os/path.go b/src/os/path.go
index 5c53506..cdfbc18 100644
--- a/src/os/path.go
+++ b/src/os/path.go
@@ -6,7 +6,6 @@
import (
"io"
- "runtime"
"syscall"
)
@@ -84,32 +83,35 @@
return err
}
- // Directory.
- fd, err := Open(path)
- if err != nil {
- if IsNotExist(err) {
- // Race. It was deleted between the Lstat and Open.
- // Return nil per RemoveAll's docs.
- return nil
- }
- return err
- }
-
// Remove contents & return first error.
err = nil
for {
- if err == nil && (runtime.GOOS == "plan9" || runtime.GOOS == "nacl") {
- // Reset read offset after removing directory entries.
- // See golang.org/issue/22572.
- fd.Seek(0, 0)
+ fd, err := Open(path)
+ if err != nil {
+ if IsNotExist(err) {
+ // Already deleted by someone else.
+ return nil
+ }
+ return err
}
- names, err1 := fd.Readdirnames(100)
+
+ const request = 1024
+ names, err1 := fd.Readdirnames(request)
+
+ // Removing files from the directory may have caused
+ // the OS to reshuffle it. Simply calling Readdirnames
+ // again may skip some entries. The only reliable way
+ // to avoid this is to close and re-open the
+ // directory. See issue 20841.
+ fd.Close()
+
for _, name := range names {
err1 := RemoveAll(path + string(PathSeparator) + name)
if err == nil {
err = err1
}
}
+
if err1 == io.EOF {
break
}
@@ -120,10 +122,29 @@
if len(names) == 0 {
break
}
- }
- // Close directory, because windows won't remove opened directory.
- fd.Close()
+ // We don't want to re-open unnecessarily, so if we
+ // got fewer than request names from Readdirnames, try
+ // simply removing the directory now. If that
+ // succeeds, we are done.
+ if len(names) < request {
+ err1 := Remove(path)
+ if err1 == nil || IsNotExist(err1) {
+ return nil
+ }
+
+ if err != nil {
+ // We got some error removing the
+ // directory contents, and since we
+ // read fewer names than we requested
+ // there probably aren't more files to
+ // remove. Don't loop around to read
+ // the directory again. We'll probably
+ // just get the same error.
+ return err
+ }
+ }
+ }
// Remove directory.
err1 := Remove(path)