github.com/lovishpuri/go-40569/src@v0.0.0-20230519171745-f8623e7c56cf/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 unix 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 := ignoringEINTR(func() error { 62 return unix.Unlinkat(parentFd, base, 0) 63 }) 64 if err == nil || IsNotExist(err) { 65 return nil 66 } 67 68 // EISDIR means that we have a directory, and we need to 69 // remove its contents. 70 // EPERM or EACCES means that we don't have write permission on 71 // the parent directory, but this entry might still be a directory 72 // whose contents need to be removed. 73 // Otherwise just return the error. 74 if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES { 75 return &PathError{Op: "unlinkat", Path: base, Err: err} 76 } 77 78 // Is this a directory we need to recurse into? 79 var statInfo syscall.Stat_t 80 statErr := ignoringEINTR(func() error { 81 return unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW) 82 }) 83 if statErr != nil { 84 if IsNotExist(statErr) { 85 return nil 86 } 87 return &PathError{Op: "fstatat", Path: base, Err: statErr} 88 } 89 if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { 90 // Not a directory; return the error from the unix.Unlinkat. 91 return &PathError{Op: "unlinkat", Path: base, Err: err} 92 } 93 94 // Remove the directory's entries. 95 var recurseErr error 96 for { 97 const reqSize = 1024 98 var respSize int 99 100 // Open the directory to recurse into 101 file, err := openFdAt(parentFd, base) 102 if err != nil { 103 if IsNotExist(err) { 104 return nil 105 } 106 recurseErr = &PathError{Op: "openfdat", Path: base, Err: err} 107 break 108 } 109 110 for { 111 numErr := 0 112 113 names, readErr := file.Readdirnames(reqSize) 114 // Errors other than EOF should stop us from continuing. 115 if readErr != nil && readErr != io.EOF { 116 file.Close() 117 if IsNotExist(readErr) { 118 return nil 119 } 120 return &PathError{Op: "readdirnames", Path: base, Err: readErr} 121 } 122 123 respSize = len(names) 124 for _, name := range names { 125 err := removeAllFrom(file, name) 126 if err != nil { 127 if pathErr, ok := err.(*PathError); ok { 128 pathErr.Path = base + string(PathSeparator) + pathErr.Path 129 } 130 numErr++ 131 if recurseErr == nil { 132 recurseErr = err 133 } 134 } 135 } 136 137 // If we can delete any entry, break to start new iteration. 138 // Otherwise, we discard current names, get next entries and try deleting them. 139 if numErr != reqSize { 140 break 141 } 142 } 143 144 // Removing files from the directory may have caused 145 // the OS to reshuffle it. Simply calling Readdirnames 146 // again may skip some entries. The only reliable way 147 // to avoid this is to close and re-open the 148 // directory. See issue 20841. 149 file.Close() 150 151 // Finish when the end of the directory is reached 152 if respSize < reqSize { 153 break 154 } 155 } 156 157 // Remove the directory itself. 158 unlinkError := ignoringEINTR(func() error { 159 return unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR) 160 }) 161 if unlinkError == nil || IsNotExist(unlinkError) { 162 return nil 163 } 164 165 if recurseErr != nil { 166 return recurseErr 167 } 168 return &PathError{Op: "unlinkat", Path: base, Err: unlinkError} 169 } 170 171 // openFdAt opens path relative to the directory in fd. 172 // Other than that this should act like openFileNolog. 173 // This acts like openFileNolog rather than OpenFile because 174 // we are going to (try to) remove the file. 175 // The contents of this file are not relevant for test caching. 176 func openFdAt(dirfd int, name string) (*File, error) { 177 var r int 178 for { 179 var e error 180 r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0) 181 if e == nil { 182 break 183 } 184 185 // See comment in openFileNolog. 186 if e == syscall.EINTR { 187 continue 188 } 189 190 return nil, e 191 } 192 193 if !supportsCloseOnExec { 194 syscall.CloseOnExec(r) 195 } 196 197 // We use kindNoPoll because we know that this is a directory. 198 return newFile(uintptr(r), name, kindNoPoll), nil 199 }