]> git.ipfire.org Git - thirdparty/gcc.git/blame - libgo/go/os/removeall_at.go
libgo: update to Go1.14beta1
[thirdparty/gcc.git] / libgo / go / os / removeall_at.go
CommitLineData
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
7package os
8
9import (
10 "internal/syscall/unix"
11 "io"
99e20ba5 12 "runtime"
4f4a855d
ILT
13 "syscall"
14)
15
99e20ba5 16func 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 59func 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.
171func 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}