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