Skip to content

Commit 4636fa6

Browse files
committed
Implement os.RemoveAll(), test on darwin only until Readdir implemented elsewhere
1 parent 65482b1 commit 4636fa6

File tree

4 files changed

+551
-5
lines changed

4 files changed

+551
-5
lines changed

src/os/file.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,6 @@ func Symlink(oldname, newname string) error {
6060
return ErrNotImplemented
6161
}
6262

63-
// RemoveAll is a stub, it is not implemented.
64-
func RemoveAll(path string) error {
65-
return ErrNotImplemented
66-
}
67-
6863
// Name returns the name of the file with which it was opened.
6964
func (f *File) Name() string {
7065
return f.name

src/os/path.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,23 @@ func MkdirAll(path string, perm FileMode) error {
5959
}
6060
return nil
6161
}
62+
63+
// RemoveAll removes path and any children it contains.
64+
// It removes everything it can but returns the first error
65+
// it encounters. If the path does not exist, RemoveAll
66+
// returns nil (no error).
67+
// If there is an error, it will be of type *PathError.
68+
func RemoveAll(path string) error {
69+
return removeAll(path)
70+
}
71+
72+
// endsWithDot reports whether the final component of path is ".".
73+
func endsWithDot(path string) bool {
74+
if path == "." {
75+
return true
76+
}
77+
if len(path) >= 2 && path[len(path)-1] == '.' && IsPathSeparator(path[len(path)-2]) {
78+
return true
79+
}
80+
return false
81+
}

src/os/removeall_noat.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build !baremetal,!js
6+
7+
package os
8+
9+
import (
10+
"io"
11+
"runtime"
12+
"syscall"
13+
)
14+
15+
func removeAll(path string) error {
16+
if path == "" {
17+
// fail silently to retain compatibility with previous behavior
18+
// of RemoveAll. See issue 28830.
19+
return nil
20+
}
21+
22+
// The rmdir system call permits removing "." on Plan 9,
23+
// so we don't permit it to remain consistent with the
24+
// "at" implementation of RemoveAll.
25+
if endsWithDot(path) {
26+
return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL}
27+
}
28+
29+
// Simple case: if Remove works, we're done.
30+
err := Remove(path)
31+
if err == nil || IsNotExist(err) {
32+
return nil
33+
}
34+
35+
// Otherwise, is this a directory we need to recurse into?
36+
dir, serr := Lstat(path)
37+
if serr != nil {
38+
if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
39+
return nil
40+
}
41+
return serr
42+
}
43+
if !dir.IsDir() {
44+
// Not a directory; return the error from Remove.
45+
return err
46+
}
47+
48+
// Remove contents & return first error.
49+
err = nil
50+
for {
51+
fd, err := Open(path)
52+
if err != nil {
53+
if IsNotExist(err) {
54+
// Already deleted by someone else.
55+
return nil
56+
}
57+
return err
58+
}
59+
60+
const reqSize = 1024
61+
var names []string
62+
var readErr error
63+
64+
for {
65+
numErr := 0
66+
names, readErr = fd.Readdirnames(reqSize)
67+
68+
for _, name := range names {
69+
err1 := RemoveAll(path + string(PathSeparator) + name)
70+
if err == nil {
71+
err = err1
72+
}
73+
if err1 != nil {
74+
numErr++
75+
}
76+
}
77+
78+
// If we can delete any entry, break to start new iteration.
79+
// Otherwise, we discard current names, get next entries and try deleting them.
80+
if numErr != reqSize {
81+
break
82+
}
83+
}
84+
85+
// Removing files from the directory may have caused
86+
// the OS to reshuffle it. Simply calling Readdirnames
87+
// again may skip some entries. The only reliable way
88+
// to avoid this is to close and re-open the
89+
// directory. See issue 20841.
90+
fd.Close()
91+
92+
if readErr == io.EOF {
93+
break
94+
}
95+
// If Readdirnames returned an error, use it.
96+
if err == nil {
97+
err = readErr
98+
}
99+
if len(names) == 0 {
100+
break
101+
}
102+
103+
// We don't want to re-open unnecessarily, so if we
104+
// got fewer than request names from Readdirnames, try
105+
// simply removing the directory now. If that
106+
// succeeds, we are done.
107+
if len(names) < reqSize {
108+
err1 := Remove(path)
109+
if err1 == nil || IsNotExist(err1) {
110+
return nil
111+
}
112+
113+
if err != nil {
114+
// We got some error removing the
115+
// directory contents, and since we
116+
// read fewer names than we requested
117+
// there probably aren't more files to
118+
// remove. Don't loop around to read
119+
// the directory again. We'll probably
120+
// just get the same error.
121+
return err
122+
}
123+
}
124+
}
125+
126+
// Remove directory.
127+
err1 := Remove(path)
128+
if err1 == nil || IsNotExist(err1) {
129+
return nil
130+
}
131+
if runtime.GOOS == "windows" && IsPermission(err1) {
132+
if fs, err := Stat(path); err == nil {
133+
if err = Chmod(path, FileMode(0200|int(fs.Mode()))); err == nil {
134+
err1 = Remove(path)
135+
}
136+
}
137+
}
138+
if err == nil {
139+
err = err1
140+
}
141+
return err
142+
}

0 commit comments

Comments
 (0)