filepath.go 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package util
  2. import (
  3. "errors"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path/filepath"
  8. )
  9. //WalkSkipDir is the Error returned when we want to skip descending into a directory
  10. var WalkSkipDir = errors.New("skip this directory")
  11. //WalkFunc is a callback function called for each path as a directory is walked
  12. //If resolvedPath != "", then we are following symbolic links.
  13. type WalkFunc func(resolvedPath string, info os.FileInfo, err error) error
  14. //Walk walks a path, optionally following symbolic links, and for each path,
  15. //it calls the walkFn passed.
  16. //
  17. //It is similar to filepath.Walk, except that it supports symbolic links and
  18. //can detect infinite loops while following sym links.
  19. //It solves the issue where your WalkFunc needs a path relative to the symbolic link
  20. //(resolving links within walkfunc loses the path to the symbolic link for each traversal).
  21. func Walk(path string, followSymlinks bool, detectSymlinkInfiniteLoop bool, walkFn WalkFunc) error {
  22. info, err := os.Lstat(path)
  23. if err != nil {
  24. return err
  25. }
  26. var symlinkPathsFollowed map[string]bool
  27. var resolvedPath string
  28. if followSymlinks {
  29. resolvedPath = path
  30. if detectSymlinkInfiniteLoop {
  31. symlinkPathsFollowed = make(map[string]bool, 8)
  32. }
  33. }
  34. return walk(path, info, resolvedPath, symlinkPathsFollowed, walkFn)
  35. }
  36. //walk walks the path. It is a helper/sibling function to Walk.
  37. //It takes a resolvedPath into consideration. This way, paths being walked are
  38. //always relative to the path argument, even if symbolic links were resolved).
  39. //
  40. //If resolvedPath is "", then we are not following symbolic links.
  41. //If symlinkPathsFollowed is not nil, then we need to detect infinite loop.
  42. func walk(path string, info os.FileInfo, resolvedPath string,
  43. symlinkPathsFollowed map[string]bool, walkFn WalkFunc) error {
  44. if info == nil {
  45. return errors.New("Walk: Nil FileInfo passed")
  46. }
  47. err := walkFn(resolvedPath, info, nil)
  48. if err != nil {
  49. if info.IsDir() && err == WalkSkipDir {
  50. err = nil
  51. }
  52. return err
  53. }
  54. if resolvedPath != "" && info.Mode()&os.ModeSymlink == os.ModeSymlink {
  55. path2, err := os.Readlink(resolvedPath)
  56. if err != nil {
  57. return err
  58. }
  59. //vout("SymLink Path: %v, links to: %v", resolvedPath, path2)
  60. if symlinkPathsFollowed != nil {
  61. if _, ok := symlinkPathsFollowed[path2]; ok {
  62. errMsg := "Potential SymLink Infinite Loop. Path: %v, Link To: %v"
  63. return fmt.Errorf(errMsg, resolvedPath, path2)
  64. } else {
  65. symlinkPathsFollowed[path2] = true
  66. }
  67. }
  68. info2, err := os.Lstat(path2)
  69. if err != nil {
  70. return err
  71. }
  72. return walk(path, info2, path2, symlinkPathsFollowed, walkFn)
  73. }
  74. if info.IsDir() {
  75. list, err := ioutil.ReadDir(path)
  76. if err != nil {
  77. return walkFn(resolvedPath, info, err)
  78. }
  79. for _, fileInfo := range list {
  80. path2 := filepath.Join(path, fileInfo.Name())
  81. var resolvedPath2 string
  82. if resolvedPath != "" {
  83. resolvedPath2 = filepath.Join(resolvedPath, fileInfo.Name())
  84. }
  85. err = walk(path2, fileInfo, resolvedPath2, symlinkPathsFollowed, walkFn)
  86. if err != nil {
  87. return err
  88. }
  89. }
  90. return nil
  91. }
  92. return nil
  93. }