os: in RemoveAll, try Remove first

Otherwise we can fail to remove a unreadable empty directory.

Fixes #29178

Change-Id: I43d5c89fce57a86626abe2a1c2bbf145716e087b
Reviewed-on: https://go-review.googlesource.com/c/153720
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/removeall_at.go b/src/os/removeall_at.go
index c42319a..f0fed6d 100644
--- a/src/os/removeall_at.go
+++ b/src/os/removeall_at.go
@@ -25,6 +25,12 @@
 		return &PathError{"RemoveAll", path, syscall.EINVAL}
 	}
 
+	// Simple case: if Remove works, we're done.
+	err := Remove(path)
+	if err == nil || IsNotExist(err) {
+		return nil
+	}
+
 	// RemoveAll recurses by deleting the path base from
 	// its parent directory
 	parentDir, base := splitPath(path)
diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go
index 1c9f163..0f7dce0 100644
--- a/src/os/removeall_test.go
+++ b/src/os/removeall_test.go
@@ -264,3 +264,31 @@
 		}
 	}
 }
+
+// Issue #29178.
+func TestRemoveReadOnlyDir(t *testing.T) {
+	t.Parallel()
+
+	tempDir, err := ioutil.TempDir("", "TestRemoveReadOnlyDir-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer RemoveAll(tempDir)
+
+	subdir := filepath.Join(tempDir, "x")
+	if err := Mkdir(subdir, 0); err != nil {
+		t.Fatal(err)
+	}
+
+	// If an error occurs make it more likely that removing the
+	// temporary directory will succeed.
+	defer Chmod(subdir, 0777)
+
+	if err := RemoveAll(subdir); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := Stat(subdir); err == nil {
+		t.Error("subdirectory was not removed")
+	}
+}