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