108 lines
2.4 KiB
Go
108 lines
2.4 KiB
Go
|
package contenthash
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
)
|
||
|
|
||
|
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 os.IsNotExist(err) {
|
||
|
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
|
||
|
}
|
||
|
}
|