fs.go 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package symlink
  2. import (
  3. "fmt"
  4. "os"
  5. "path"
  6. "path/filepath"
  7. "strings"
  8. )
  9. const maxLoopCounter = 100
  10. // FollowSymlink will follow an existing link and scope it to the root
  11. // path provided.
  12. func FollowSymlinkInScope(link, root string) (string, error) {
  13. prev := "/"
  14. root, err := filepath.Abs(root)
  15. if err != nil {
  16. return "", err
  17. }
  18. link, err = filepath.Abs(link)
  19. if err != nil {
  20. return "", err
  21. }
  22. if !strings.HasPrefix(filepath.Dir(link), root) {
  23. return "", fmt.Errorf("%s is not within %s", link, root)
  24. }
  25. for _, p := range strings.Split(link, "/") {
  26. prev = filepath.Join(prev, p)
  27. prev = filepath.Clean(prev)
  28. loopCounter := 0
  29. for {
  30. loopCounter++
  31. if loopCounter >= maxLoopCounter {
  32. return "", fmt.Errorf("loopCounter reached MAX: %v", loopCounter)
  33. }
  34. if !strings.HasPrefix(prev, root) {
  35. // Don't resolve symlinks outside of root. For example,
  36. // we don't have to check /home in the below.
  37. //
  38. // /home -> usr/home
  39. // FollowSymlinkInScope("/home/bob/foo/bar", "/home/bob/foo")
  40. break
  41. }
  42. stat, err := os.Lstat(prev)
  43. if err != nil {
  44. if os.IsNotExist(err) {
  45. break
  46. }
  47. return "", err
  48. }
  49. if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
  50. dest, err := os.Readlink(prev)
  51. if err != nil {
  52. return "", err
  53. }
  54. if path.IsAbs(dest) {
  55. prev = filepath.Join(root, dest)
  56. } else {
  57. prev, _ = filepath.Abs(prev)
  58. if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) {
  59. prev = filepath.Join(root, filepath.Base(dest))
  60. }
  61. }
  62. } else {
  63. break
  64. }
  65. }
  66. }
  67. return prev, nil
  68. }