You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
5.0 KiB

  1. package util
  2. import (
  3. "io"
  4. "os"
  5. "path/filepath"
  6. "strconv"
  7. "sync"
  8. "time"
  9. "github.com/go-git/go-billy/v5"
  10. )
  11. // RemoveAll removes path and any children it contains. It removes everything it
  12. // can but returns the first error it encounters. If the path does not exist,
  13. // RemoveAll returns nil (no error).
  14. func RemoveAll(fs billy.Basic, path string) error {
  15. fs, path = getUnderlyingAndPath(fs, path)
  16. if r, ok := fs.(removerAll); ok {
  17. return r.RemoveAll(path)
  18. }
  19. return removeAll(fs, path)
  20. }
  21. type removerAll interface {
  22. RemoveAll(string) error
  23. }
  24. func removeAll(fs billy.Basic, path string) error {
  25. // This implementation is adapted from os.RemoveAll.
  26. // Simple case: if Remove works, we're done.
  27. err := fs.Remove(path)
  28. if err == nil || os.IsNotExist(err) {
  29. return nil
  30. }
  31. // Otherwise, is this a directory we need to recurse into?
  32. dir, serr := fs.Stat(path)
  33. if serr != nil {
  34. if os.IsNotExist(serr) {
  35. return nil
  36. }
  37. return serr
  38. }
  39. if !dir.IsDir() {
  40. // Not a directory; return the error from Remove.
  41. return err
  42. }
  43. dirfs, ok := fs.(billy.Dir)
  44. if !ok {
  45. return billy.ErrNotSupported
  46. }
  47. // Directory.
  48. fis, err := dirfs.ReadDir(path)
  49. if err != nil {
  50. if os.IsNotExist(err) {
  51. // Race. It was deleted between the Lstat and Open.
  52. // Return nil per RemoveAll's docs.
  53. return nil
  54. }
  55. return err
  56. }
  57. // Remove contents & return first error.
  58. err = nil
  59. for _, fi := range fis {
  60. cpath := fs.Join(path, fi.Name())
  61. err1 := removeAll(fs, cpath)
  62. if err == nil {
  63. err = err1
  64. }
  65. }
  66. // Remove directory.
  67. err1 := fs.Remove(path)
  68. if err1 == nil || os.IsNotExist(err1) {
  69. return nil
  70. }
  71. if err == nil {
  72. err = err1
  73. }
  74. return err
  75. }
  76. // WriteFile writes data to a file named by filename in the given filesystem.
  77. // If the file does not exist, WriteFile creates it with permissions perm;
  78. // otherwise WriteFile truncates it before writing.
  79. func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) error {
  80. f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
  81. if err != nil {
  82. return err
  83. }
  84. n, err := f.Write(data)
  85. if err == nil && n < len(data) {
  86. err = io.ErrShortWrite
  87. }
  88. if err1 := f.Close(); err == nil {
  89. err = err1
  90. }
  91. return err
  92. }
  93. // Random number state.
  94. // We generate random temporary file names so that there's a good
  95. // chance the file doesn't exist yet - keeps the number of tries in
  96. // TempFile to a minimum.
  97. var rand uint32
  98. var randmu sync.Mutex
  99. func reseed() uint32 {
  100. return uint32(time.Now().UnixNano() + int64(os.Getpid()))
  101. }
  102. func nextSuffix() string {
  103. randmu.Lock()
  104. r := rand
  105. if r == 0 {
  106. r = reseed()
  107. }
  108. r = r*1664525 + 1013904223 // constants from Numerical Recipes
  109. rand = r
  110. randmu.Unlock()
  111. return strconv.Itoa(int(1e9 + r%1e9))[1:]
  112. }
  113. // TempFile creates a new temporary file in the directory dir with a name
  114. // beginning with prefix, opens the file for reading and writing, and returns
  115. // the resulting *os.File. If dir is the empty string, TempFile uses the default
  116. // directory for temporary files (see os.TempDir). Multiple programs calling
  117. // TempFile simultaneously will not choose the same file. The caller can use
  118. // f.Name() to find the pathname of the file. It is the caller's responsibility
  119. // to remove the file when no longer needed.
  120. func TempFile(fs billy.Basic, dir, prefix string) (f billy.File, err error) {
  121. // This implementation is based on stdlib ioutil.TempFile.
  122. if dir == "" {
  123. dir = os.TempDir()
  124. }
  125. nconflict := 0
  126. for i := 0; i < 10000; i++ {
  127. name := filepath.Join(dir, prefix+nextSuffix())
  128. f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
  129. if os.IsExist(err) {
  130. if nconflict++; nconflict > 10 {
  131. randmu.Lock()
  132. rand = reseed()
  133. randmu.Unlock()
  134. }
  135. continue
  136. }
  137. break
  138. }
  139. return
  140. }
  141. // TempDir creates a new temporary directory in the directory dir
  142. // with a name beginning with prefix and returns the path of the
  143. // new directory. If dir is the empty string, TempDir uses the
  144. // default directory for temporary files (see os.TempDir).
  145. // Multiple programs calling TempDir simultaneously
  146. // will not choose the same directory. It is the caller's responsibility
  147. // to remove the directory when no longer needed.
  148. func TempDir(fs billy.Dir, dir, prefix string) (name string, err error) {
  149. // This implementation is based on stdlib ioutil.TempDir
  150. if dir == "" {
  151. dir = os.TempDir()
  152. }
  153. nconflict := 0
  154. for i := 0; i < 10000; i++ {
  155. try := filepath.Join(dir, prefix+nextSuffix())
  156. err = fs.MkdirAll(try, 0700)
  157. if os.IsExist(err) {
  158. if nconflict++; nconflict > 10 {
  159. randmu.Lock()
  160. rand = reseed()
  161. randmu.Unlock()
  162. }
  163. continue
  164. }
  165. if os.IsNotExist(err) {
  166. if _, err := os.Stat(dir); os.IsNotExist(err) {
  167. return "", err
  168. }
  169. }
  170. if err == nil {
  171. name = try
  172. }
  173. break
  174. }
  175. return
  176. }
  177. type underlying interface {
  178. Underlying() billy.Basic
  179. }
  180. func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) {
  181. u, ok := fs.(underlying)
  182. if !ok {
  183. return fs, path
  184. }
  185. if ch, ok := fs.(billy.Chroot); ok {
  186. path = fs.Join(ch.Root(), path)
  187. }
  188. return u.Underlying(), path
  189. }