blob: 42b7fd8349c7248e87824cc15a45c4533abceef8 [file] [log] [blame]
package contenthash
import (
"os"
"path/filepath"
"github.com/pkg/errors"
)
var (
errTooManyLinks = errors.New("too many links")
)
type onSymlinkFunc func(string, string) error
// rootPath joins a path with a root, evaluating and bounding any
// symlink to the root directory.
// This is containerd/continuity/fs RootPath implementation with a callback on
// resolving the symlink.
func rootPath(root, path string, cb onSymlinkFunc) (string, error) {
if path == "" {
return root, nil
}
var linksWalked int // to protect against cycles
for {
i := linksWalked
newpath, err := walkLinks(root, path, &linksWalked, cb)
if err != nil {
return "", err
}
path = newpath
if i == linksWalked {
newpath = filepath.Join("/", newpath)
if path == newpath {
return filepath.Join(root, newpath), nil
}
path = newpath
}
}
}
func walkLink(root, path string, linksWalked *int, cb onSymlinkFunc) (newpath string, islink bool, err error) {
if *linksWalked > 255 {
return "", false, errTooManyLinks
}
path = filepath.Join("/", path)
if path == "/" {
return path, false, nil
}
realPath := filepath.Join(root, path)
fi, err := os.Lstat(realPath)
if err != nil {
// If path does not yet exist, treat as non-symlink
if errors.Is(err, os.ErrNotExist) {
return path, false, nil
}
return "", false, err
}
if fi.Mode()&os.ModeSymlink == 0 {
return path, false, nil
}
newpath, err = os.Readlink(realPath)
if err != nil {
return "", false, err
}
if cb != nil {
if err := cb(path, newpath); err != nil {
return "", false, err
}
}
*linksWalked++
return newpath, true, nil
}
func walkLinks(root, path string, linksWalked *int, cb onSymlinkFunc) (string, error) {
switch dir, file := filepath.Split(path); {
case dir == "":
newpath, _, err := walkLink(root, file, linksWalked, cb)
return newpath, err
case file == "":
if os.IsPathSeparator(dir[len(dir)-1]) {
if dir == "/" {
return dir, nil
}
return walkLinks(root, dir[:len(dir)-1], linksWalked, cb)
}
newpath, _, err := walkLink(root, dir, linksWalked, cb)
return newpath, err
default:
newdir, err := walkLinks(root, dir, linksWalked, cb)
if err != nil {
return "", err
}
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked, cb)
if err != nil {
return "", err
}
if !islink {
return newpath, nil
}
if filepath.IsAbs(newpath) {
return newpath, nil
}
return filepath.Join(newdir, newpath), nil
}
}