1package contenthash 2 3import ( 4 "os" 5 "path/filepath" 6 7 "github.com/pkg/errors" 8) 9 10var ( 11 errTooManyLinks = errors.New("too many links") 12) 13 14type onSymlinkFunc func(string, string) error 15 16// rootPath joins a path with a root, evaluating and bounding any 17// symlink to the root directory. 18// This is containerd/continuity/fs RootPath implementation with a callback on 19// resolving the symlink. 20func rootPath(root, path string, cb onSymlinkFunc) (string, error) { 21 if path == "" { 22 return root, nil 23 } 24 var linksWalked int // to protect against cycles 25 for { 26 i := linksWalked 27 newpath, err := walkLinks(root, path, &linksWalked, cb) 28 if err != nil { 29 return "", err 30 } 31 path = newpath 32 if i == linksWalked { 33 newpath = filepath.Join("/", newpath) 34 if path == newpath { 35 return filepath.Join(root, newpath), nil 36 } 37 path = newpath 38 } 39 } 40} 41 42func walkLink(root, path string, linksWalked *int, cb onSymlinkFunc) (newpath string, islink bool, err error) { 43 if *linksWalked > 255 { 44 return "", false, errTooManyLinks 45 } 46 47 path = filepath.Join("/", path) 48 if path == "/" { 49 return path, false, nil 50 } 51 realPath := filepath.Join(root, path) 52 53 fi, err := os.Lstat(realPath) 54 if err != nil { 55 // If path does not yet exist, treat as non-symlink 56 if errors.Is(err, os.ErrNotExist) { 57 return path, false, nil 58 } 59 return "", false, err 60 } 61 if fi.Mode()&os.ModeSymlink == 0 { 62 return path, false, nil 63 } 64 newpath, err = os.Readlink(realPath) 65 if err != nil { 66 return "", false, err 67 } 68 if cb != nil { 69 if err := cb(path, newpath); err != nil { 70 return "", false, err 71 } 72 } 73 *linksWalked++ 74 return newpath, true, nil 75} 76 77func walkLinks(root, path string, linksWalked *int, cb onSymlinkFunc) (string, error) { 78 switch dir, file := filepath.Split(path); { 79 case dir == "": 80 newpath, _, err := walkLink(root, file, linksWalked, cb) 81 return newpath, err 82 case file == "": 83 if os.IsPathSeparator(dir[len(dir)-1]) { 84 if dir == "/" { 85 return dir, nil 86 } 87 return walkLinks(root, dir[:len(dir)-1], linksWalked, cb) 88 } 89 newpath, _, err := walkLink(root, dir, linksWalked, cb) 90 return newpath, err 91 default: 92 newdir, err := walkLinks(root, dir, linksWalked, cb) 93 if err != nil { 94 return "", err 95 } 96 newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked, cb) 97 if err != nil { 98 return "", err 99 } 100 if !islink { 101 return newpath, nil 102 } 103 if filepath.IsAbs(newpath) { 104 return newpath, nil 105 } 106 return filepath.Join(newdir, newpath), nil 107 } 108} 109