github.com/code-reading/golang@v0.0.0-20220303082512-ba5bc0e589a3/go/src/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 // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris 7 8 package os 9 10 import ( 11 "internal/syscall/unix" 12 "io" 13 "syscall" 14 ) 15 16 func removeAll(path string) error { 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{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 // 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 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 57 } 58 59 func removeAllFrom(parent *File, base string) error { 60 parentFd := int(parent.Fd()) 61 // Simple case: if Unlink (aka remove) works, we're done. 62 err := unix.Unlinkat(parentFd, base, 0) 63 if err == nil || IsNotExist(err) { 64 return nil 65 } 66 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 { 74 return &PathError{Op: "unlinkat", Path: base, Err: err} 75 } 76 77 // Is this a directory we need to recurse into? 78 var statInfo syscall.Stat_t 79 statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW) 80 if statErr != nil { 81 if IsNotExist(statErr) { 82 return nil 83 } 84 return &PathError{Op: "fstatat", Path: base, Err: statErr} 85 } 86 if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { 87 // Not a directory; return the error from the unix.Unlinkat. 88 return &PathError{Op: "unlinkat", Path: base, Err: err} 89 } 90 91 // Remove the directory's entries. 92 var recurseErr error 93 for { 94 const reqSize = 1024 95 var respSize int 96 97 // Open the directory to recurse into 98 file, err := openFdAt(parentFd, base) 99 if err != nil { 100 if IsNotExist(err) { 101 return nil 102 } 103 recurseErr = &PathError{Op: "openfdat", Path: base, Err: err} 104 break 105 } 106 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{Op: "readdirnames", Path: base, Err: readErr} 118 } 119 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 } 131 } 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 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 149 if respSize < reqSize { 150 break 151 } 152 } 153 154 // Remove the directory itself. 155 unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR) 156 if unlinkError == nil || IsNotExist(unlinkError) { 157 return nil 158 } 159 160 if recurseErr != nil { 161 return recurseErr 162 } 163 return &PathError{Op: "unlinkat", Path: base, Err: unlinkError} 164 } 165 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 175 r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0) 176 if e == nil { 177 break 178 } 179 180 // See comment in openFileNolog. 181 if e == syscall.EINTR { 182 continue 183 } 184 185 return nil, e 186 } 187 188 if !supportsCloseOnExec { 189 syscall.CloseOnExec(r) 190 } 191 192 return newFile(uintptr(r), name, kindOpenFile), nil 193 }