walkfs.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. // Copyright 2009 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. // This part copied directly from golang.org/src/path/filepath/path.go (Go
  5. // 1.6) and lightly modified to be methods on BasicFilesystem.
  6. // In our Walk() all paths given to a WalkFunc() are relative to the
  7. // filesystem root.
  8. package fs
  9. import (
  10. "errors"
  11. "path/filepath"
  12. )
  13. var ErrInfiniteRecursion = errors.New("infinite filesystem recursion detected")
  14. type ancestorDirList struct {
  15. list []FileInfo
  16. fs Filesystem
  17. }
  18. func (ancestors *ancestorDirList) Push(info FileInfo) {
  19. l.Debugf("ancestorDirList: Push '%s'", info.Name())
  20. ancestors.list = append(ancestors.list, info)
  21. }
  22. func (ancestors *ancestorDirList) Pop() FileInfo {
  23. aLen := len(ancestors.list)
  24. info := ancestors.list[aLen-1]
  25. l.Debugf("ancestorDirList: Pop '%s'", info.Name())
  26. ancestors.list = ancestors.list[:aLen-1]
  27. return info
  28. }
  29. func (ancestors *ancestorDirList) Contains(info FileInfo) bool {
  30. l.Debugf("ancestorDirList: Contains '%s'", info.Name())
  31. for _, ancestor := range ancestors.list {
  32. if ancestors.fs.SameFile(info, ancestor) {
  33. return true
  34. }
  35. }
  36. return false
  37. }
  38. // WalkFunc is the type of the function called for each file or directory
  39. // visited by Walk. The path argument contains the argument to Walk as a
  40. // prefix; that is, if Walk is called with "dir", which is a directory
  41. // containing the file "a", the walk function will be called with argument
  42. // "dir/a". The info argument is the FileInfo for the named path.
  43. //
  44. // If there was a problem walking to the file or directory named by path, the
  45. // incoming error will describe the problem and the function can decide how
  46. // to handle that error (and Walk will not descend into that directory). If
  47. // an error is returned, processing stops. The sole exception is when the function
  48. // returns the special value SkipDir. If the function returns SkipDir when invoked
  49. // on a directory, Walk skips the directory's contents entirely.
  50. // If the function returns SkipDir when invoked on a non-directory file,
  51. // Walk skips the remaining files in the containing directory.
  52. type WalkFunc func(path string, info FileInfo, err error) error
  53. type walkFilesystem struct {
  54. Filesystem
  55. checkInfiniteRecursion bool
  56. }
  57. func NewWalkFilesystem(next Filesystem) Filesystem {
  58. fs := &walkFilesystem{
  59. Filesystem: next,
  60. }
  61. for _, opt := range next.Options() {
  62. if _, ok := opt.(*OptionJunctionsAsDirs); ok {
  63. fs.checkInfiniteRecursion = true
  64. break
  65. }
  66. }
  67. return fs
  68. }
  69. // walk recursively descends path, calling walkFn.
  70. func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ancestors *ancestorDirList) error {
  71. l.Debugf("walk: path=%s", path)
  72. path, err := Canonicalize(path)
  73. if err != nil {
  74. return err
  75. }
  76. err = walkFn(path, info, nil)
  77. if err != nil {
  78. if info.IsDir() && err == SkipDir {
  79. return nil
  80. }
  81. return err
  82. }
  83. if !info.IsDir() && path != "." {
  84. return nil
  85. }
  86. if f.checkInfiniteRecursion {
  87. if !ancestors.Contains(info) {
  88. ancestors.Push(info)
  89. defer ancestors.Pop()
  90. } else {
  91. return walkFn(path, info, ErrInfiniteRecursion)
  92. }
  93. }
  94. names, err := f.DirNames(path)
  95. if err != nil {
  96. return walkFn(path, info, err)
  97. }
  98. for _, name := range names {
  99. filename := filepath.Join(path, name)
  100. fileInfo, err := f.Lstat(filename)
  101. if err != nil {
  102. if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
  103. return err
  104. }
  105. } else {
  106. err = f.walk(filename, fileInfo, walkFn, ancestors)
  107. if err != nil {
  108. if !fileInfo.IsDir() || err != SkipDir {
  109. return err
  110. }
  111. }
  112. }
  113. }
  114. return nil
  115. }
  116. // Walk walks the file tree rooted at root, calling walkFn for each file or
  117. // directory in the tree, including root. All errors that arise visiting files
  118. // and directories are filtered by walkFn. The files are walked in lexical
  119. // order, which makes the output deterministic but means that for very
  120. // large directories Walk can be inefficient.
  121. // Walk does not follow symbolic links.
  122. func (f *walkFilesystem) Walk(root string, walkFn WalkFunc) error {
  123. info, err := f.Lstat(root)
  124. if err != nil {
  125. return walkFn(root, nil, err)
  126. }
  127. var ancestors *ancestorDirList
  128. if f.checkInfiniteRecursion {
  129. ancestors = &ancestorDirList{fs: f.Filesystem}
  130. }
  131. return f.walk(root, info, walkFn, ancestors)
  132. }
  133. func (f *walkFilesystem) underlying() (Filesystem, bool) {
  134. return f.Filesystem, true
  135. }
  136. func (*walkFilesystem) wrapperType() filesystemWrapperType {
  137. return filesystemWrapperTypeWalk
  138. }