walkfs.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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. "path/filepath"
  11. )
  12. type ancestorDirList struct {
  13. list []FileInfo
  14. fs Filesystem
  15. }
  16. func (ancestors *ancestorDirList) Push(info FileInfo) {
  17. l.Debugf("ancestorDirList: Push '%s'", info.Name())
  18. ancestors.list = append(ancestors.list, info)
  19. }
  20. func (ancestors *ancestorDirList) Pop() FileInfo {
  21. aLen := len(ancestors.list)
  22. info := ancestors.list[aLen-1]
  23. l.Debugf("ancestorDirList: Pop '%s'", info.Name())
  24. ancestors.list = ancestors.list[:aLen-1]
  25. return info
  26. }
  27. func (ancestors *ancestorDirList) Contains(info FileInfo) bool {
  28. l.Debugf("ancestorDirList: Contains '%s'", info.Name())
  29. for _, ancestor := range ancestors.list {
  30. if ancestors.fs.SameFile(info, ancestor) {
  31. return true
  32. }
  33. }
  34. return false
  35. }
  36. // WalkFunc is the type of the function called for each file or directory
  37. // visited by Walk. The path argument contains the argument to Walk as a
  38. // prefix; that is, if Walk is called with "dir", which is a directory
  39. // containing the file "a", the walk function will be called with argument
  40. // "dir/a". The info argument is the FileInfo for the named path.
  41. //
  42. // If there was a problem walking to the file or directory named by path, the
  43. // incoming error will describe the problem and the function can decide how
  44. // to handle that error (and Walk will not descend into that directory). If
  45. // an error is returned, processing stops. The sole exception is when the function
  46. // returns the special value SkipDir. If the function returns SkipDir when invoked
  47. // on a directory, Walk skips the directory's contents entirely.
  48. // If the function returns SkipDir when invoked on a non-directory file,
  49. // Walk skips the remaining files in the containing directory.
  50. type WalkFunc func(path string, info FileInfo, err error) error
  51. type walkFilesystem struct {
  52. Filesystem
  53. }
  54. func NewWalkFilesystem(next Filesystem) Filesystem {
  55. return &walkFilesystem{next}
  56. }
  57. // walk recursively descends path, calling walkFn.
  58. func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ancestors *ancestorDirList) error {
  59. l.Debugf("walk: path=%s", path)
  60. path, err := Canonicalize(path)
  61. if err != nil {
  62. return err
  63. }
  64. err = walkFn(path, info, nil)
  65. if err != nil {
  66. if info.IsDir() && err == SkipDir {
  67. return nil
  68. }
  69. return err
  70. }
  71. if !info.IsDir() && path != "." {
  72. return nil
  73. }
  74. if !ancestors.Contains(info) {
  75. ancestors.Push(info)
  76. defer ancestors.Pop()
  77. } else {
  78. l.Warnf("Infinite filesystem recursion detected on path '%s', not walking further down", path)
  79. return nil
  80. }
  81. names, err := f.DirNames(path)
  82. if err != nil {
  83. return walkFn(path, info, err)
  84. }
  85. for _, name := range names {
  86. filename := filepath.Join(path, name)
  87. fileInfo, err := f.Lstat(filename)
  88. if err != nil {
  89. if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir {
  90. return err
  91. }
  92. } else {
  93. err = f.walk(filename, fileInfo, walkFn, ancestors)
  94. if err != nil {
  95. if !fileInfo.IsDir() || err != SkipDir {
  96. return err
  97. }
  98. }
  99. }
  100. }
  101. return nil
  102. }
  103. // Walk walks the file tree rooted at root, calling walkFn for each file or
  104. // directory in the tree, including root. All errors that arise visiting files
  105. // and directories are filtered by walkFn. The files are walked in lexical
  106. // order, which makes the output deterministic but means that for very
  107. // large directories Walk can be inefficient.
  108. // Walk does not follow symbolic links.
  109. func (f *walkFilesystem) Walk(root string, walkFn WalkFunc) error {
  110. info, err := f.Lstat(root)
  111. if err != nil {
  112. return walkFn(root, nil, err)
  113. }
  114. ancestors := &ancestorDirList{fs: f.Filesystem}
  115. return f.walk(root, info, walkFn, ancestors)
  116. }