109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
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
|
|
}
|
|
}
|