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 7package os 8 9import ( 10 "internal/syscall/unix" 11 "io" 12 "syscall" 13) 14 15func 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 58func 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 uErr := err 78 79 // Remove the directory's entries. 80 var recurseErr error 81 for { 82 const reqSize = 1024 83 var respSize int 84 85 // Open the directory to recurse into 86 file, err := openDirAt(parentFd, base) 87 if err != nil { 88 if IsNotExist(err) { 89 return nil 90 } 91 if err == syscall.ENOTDIR || err == unix.NoFollowErrno { 92 // Not a directory; return the error from the unix.Unlinkat. 93 return &PathError{Op: "unlinkat", Path: base, Err: uErr} 94 } 95 recurseErr = &PathError{Op: "openfdat", Path: base, Err: err} 96 break 97 } 98 99 for { 100 numErr := 0 101 102 names, readErr := file.Readdirnames(reqSize) 103 // Errors other than EOF should stop us from continuing. 104 if readErr != nil && readErr != io.EOF { 105 file.Close() 106 if IsNotExist(readErr) { 107 return nil 108 } 109 return &PathError{Op: "readdirnames", Path: base, Err: readErr} 110 } 111 112 respSize = len(names) 113 for _, name := range names { 114 err := removeAllFrom(file, name) 115 if err != nil { 116 if pathErr, ok := err.(*PathError); ok { 117 pathErr.Path = base + string(PathSeparator) + pathErr.Path 118 } 119 numErr++ 120 if recurseErr == nil { 121 recurseErr = err 122 } 123 } 124 } 125 126 // If we can delete any entry, break to start new iteration. 127 // Otherwise, we discard current names, get next entries and try deleting them. 128 if numErr != reqSize { 129 break 130 } 131 } 132 133 // Removing files from the directory may have caused 134 // the OS to reshuffle it. Simply calling Readdirnames 135 // again may skip some entries. The only reliable way 136 // to avoid this is to close and re-open the 137 // directory. See issue 20841. 138 file.Close() 139 140 // Finish when the end of the directory is reached 141 if respSize < reqSize { 142 break 143 } 144 } 145 146 // Remove the directory itself. 147 unlinkError := ignoringEINTR(func() error { 148 return unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR) 149 }) 150 if unlinkError == nil || IsNotExist(unlinkError) { 151 return nil 152 } 153 154 if recurseErr != nil { 155 return recurseErr 156 } 157 return &PathError{Op: "unlinkat", Path: base, Err: unlinkError} 158} 159 160// openDirAt opens a directory name relative to the directory referred to by 161// the file descriptor dirfd. If name is anything but a directory (this 162// includes a symlink to one), it should return an error. Other than that this 163// should act like openFileNolog. 164// 165// This acts like openFileNolog rather than OpenFile because 166// we are going to (try to) remove the file. 167// The contents of this file are not relevant for test caching. 168func openDirAt(dirfd int, name string) (*File, error) { 169 var r int 170 for { 171 var e error 172 r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) 173 if e == nil { 174 break 175 } 176 177 // See comment in openFileNolog. 178 if e == syscall.EINTR { 179 continue 180 } 181 182 return nil, e 183 } 184 185 if !supportsCloseOnExec { 186 syscall.CloseOnExec(r) 187 } 188 189 // We use kindNoPoll because we know that this is a directory. 190 return newFile(r, name, kindNoPoll, false), nil 191} 192