]>
Commit | Line | Data |
---|---|---|
4f4a855d ILT |
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 | ||
27d6b510 | 5 | // +build aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris |
4f4a855d ILT |
6 | |
7 | package os | |
8 | ||
9 | import ( | |
10 | "internal/syscall/unix" | |
11 | "io" | |
99e20ba5 | 12 | "runtime" |
4f4a855d ILT |
13 | "syscall" |
14 | ) | |
15 | ||
99e20ba5 | 16 | func removeAll(path string) error { |
4f4a855d ILT |
17 | if path == "" { |
18 | // fail silently to retain compatibility with previous behavior | |
19 | // of RemoveAll. See issue 28830. | |
20 | return nil | |
21 | } | |
22 | ||
23 | // The rmdir system call does not permit removing ".", | |
24 | // so we don't permit it either. | |
25 | if endsWithDot(path) { | |
26 | return &PathError{"RemoveAll", path, 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 | // RemoveAll recurses by deleting the path base from | |
36 | // its parent directory | |
37 | parentDir, base := splitPath(path) | |
38 | ||
39 | parent, err := Open(parentDir) | |
40 | if IsNotExist(err) { | |
41 | // If parent does not exist, base cannot exist. Fail silently | |
42 | return nil | |
43 | } | |
44 | if err != nil { | |
45 | return err | |
46 | } | |
47 | defer parent.Close() | |
48 | ||
04862afe ILT |
49 | if err := removeAllFrom(parent, base); err != nil { |
50 | if pathErr, ok := err.(*PathError); ok { | |
51 | pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path | |
52 | err = pathErr | |
53 | } | |
54 | return err | |
55 | } | |
56 | return nil | |
4f4a855d ILT |
57 | } |
58 | ||
04862afe | 59 | func removeAllFrom(parent *File, base string) error { |
4f4a855d ILT |
60 | parentFd := int(parent.Fd()) |
61 | // Simple case: if Unlink (aka remove) works, we're done. | |
04862afe | 62 | err := unix.Unlinkat(parentFd, base, 0) |
4f4a855d ILT |
63 | if err == nil || IsNotExist(err) { |
64 | return nil | |
65 | } | |
66 | ||
99e20ba5 ILT |
67 | // EISDIR means that we have a directory, and we need to |
68 | // remove its contents. | |
69 | // EPERM or EACCES means that we don't have write permission on | |
70 | // the parent directory, but this entry might still be a directory | |
71 | // whose contents need to be removed. | |
72 | // Otherwise just return the error. | |
73 | if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES { | |
04862afe | 74 | return &PathError{"unlinkat", base, err} |
4f4a855d ILT |
75 | } |
76 | ||
77 | // Is this a directory we need to recurse into? | |
78 | var statInfo syscall.Stat_t | |
04862afe | 79 | statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW) |
4f4a855d | 80 | if statErr != nil { |
4fd3c8aa ILT |
81 | if IsNotExist(statErr) { |
82 | return nil | |
83 | } | |
04862afe | 84 | return &PathError{"fstatat", base, statErr} |
4f4a855d ILT |
85 | } |
86 | if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { | |
04862afe ILT |
87 | // Not a directory; return the error from the unix.Unlinkat. |
88 | return &PathError{"unlinkat", base, err} | |
4f4a855d ILT |
89 | } |
90 | ||
99e20ba5 | 91 | // Remove the directory's entries. |
4f4a855d ILT |
92 | var recurseErr error |
93 | for { | |
aa8901e9 ILT |
94 | const reqSize = 1024 |
95 | var respSize int | |
4f4a855d ILT |
96 | |
97 | // Open the directory to recurse into | |
04862afe | 98 | file, err := openFdAt(parentFd, base) |
4f4a855d ILT |
99 | if err != nil { |
100 | if IsNotExist(err) { | |
101 | return nil | |
102 | } | |
04862afe | 103 | recurseErr = &PathError{"openfdat", base, err} |
a8b58d84 | 104 | break |
4f4a855d ILT |
105 | } |
106 | ||
aa8901e9 ILT |
107 | for { |
108 | numErr := 0 | |
109 | ||
110 | names, readErr := file.Readdirnames(reqSize) | |
111 | // Errors other than EOF should stop us from continuing. | |
112 | if readErr != nil && readErr != io.EOF { | |
113 | file.Close() | |
114 | if IsNotExist(readErr) { | |
115 | return nil | |
116 | } | |
117 | return &PathError{"readdirnames", base, readErr} | |
4f4a855d | 118 | } |
4f4a855d | 119 | |
aa8901e9 ILT |
120 | respSize = len(names) |
121 | for _, name := range names { | |
122 | err := removeAllFrom(file, name) | |
123 | if err != nil { | |
124 | if pathErr, ok := err.(*PathError); ok { | |
125 | pathErr.Path = base + string(PathSeparator) + pathErr.Path | |
126 | } | |
127 | numErr++ | |
128 | if recurseErr == nil { | |
129 | recurseErr = err | |
130 | } | |
04862afe | 131 | } |
aa8901e9 ILT |
132 | } |
133 | ||
134 | // If we can delete any entry, break to start new iteration. | |
135 | // Otherwise, we discard current names, get next entries and try deleting them. | |
136 | if numErr != reqSize { | |
137 | break | |
4f4a855d ILT |
138 | } |
139 | } | |
140 | ||
141 | // Removing files from the directory may have caused | |
142 | // the OS to reshuffle it. Simply calling Readdirnames | |
143 | // again may skip some entries. The only reliable way | |
144 | // to avoid this is to close and re-open the | |
145 | // directory. See issue 20841. | |
146 | file.Close() | |
147 | ||
148 | // Finish when the end of the directory is reached | |
aa8901e9 | 149 | if respSize < reqSize { |
4f4a855d ILT |
150 | break |
151 | } | |
152 | } | |
153 | ||
99e20ba5 | 154 | // Remove the directory itself. |
04862afe | 155 | unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR) |
4f4a855d ILT |
156 | if unlinkError == nil || IsNotExist(unlinkError) { |
157 | return nil | |
158 | } | |
159 | ||
160 | if recurseErr != nil { | |
161 | return recurseErr | |
162 | } | |
04862afe | 163 | return &PathError{"unlinkat", base, unlinkError} |
4f4a855d ILT |
164 | } |
165 | ||
99e20ba5 ILT |
166 | // openFdAt opens path relative to the directory in fd. |
167 | // Other than that this should act like openFileNolog. | |
168 | // This acts like openFileNolog rather than OpenFile because | |
169 | // we are going to (try to) remove the file. | |
170 | // The contents of this file are not relevant for test caching. | |
171 | func openFdAt(dirfd int, name string) (*File, error) { | |
172 | var r int | |
173 | for { | |
174 | var e error | |
656297e1 | 175 | r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0) |
99e20ba5 ILT |
176 | if e == nil { |
177 | break | |
178 | } | |
179 | ||
180 | // See comment in openFileNolog. | |
181 | if runtime.GOOS == "darwin" && e == syscall.EINTR { | |
182 | continue | |
183 | } | |
184 | ||
04862afe | 185 | return nil, e |
99e20ba5 ILT |
186 | } |
187 | ||
188 | if !supportsCloseOnExec { | |
189 | syscall.CloseOnExec(r) | |
4f4a855d ILT |
190 | } |
191 | ||
99e20ba5 | 192 | return newFile(uintptr(r), name, kindOpenFile), nil |
4f4a855d | 193 | } |