| 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 |
| } |
| } |