removeall_at.go 4.8 KB

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