Share link dependency evaluation with host access
This commit is contained in:
@@ -119,8 +119,8 @@ func (a *filesystemAccess) importEntryTarget(name string, entry fs.DirEntry, fol
|
||||
return importEntryTarget{symlink: symlink, dir: info.IsDir(), identity: name}, nil
|
||||
}
|
||||
|
||||
// A link stays active until its target has resolved, not until the entire
|
||||
// original pathname has resolved. Remaining segments may reuse a finished link.
|
||||
// A link remains active until its own target resolves. Completed targets can be
|
||||
// reused while the caller continues resolving its remaining components.
|
||||
type importLinkFrame struct {
|
||||
pending []string
|
||||
link string
|
||||
@@ -190,58 +190,34 @@ func resolveImportLinkPath(name string, operations importLinkOperations) (string
|
||||
}
|
||||
|
||||
func resolveHostImportPath(access *filesystemAccess, name string) (string, error) {
|
||||
frames := []importLinkFrame{{pending: strings.Split(filepath.ToSlash(name), "/")}}
|
||||
resolved := ""
|
||||
activeLinks := make(map[string]struct{})
|
||||
for len(frames) > 0 {
|
||||
frame := &frames[len(frames)-1]
|
||||
if len(frame.pending) == 0 {
|
||||
delete(activeLinks, frame.link)
|
||||
frames = frames[:len(frames)-1]
|
||||
continue
|
||||
}
|
||||
segment := frame.pending[0]
|
||||
frame.pending = frame.pending[1:]
|
||||
candidate := path.Join(resolved, segment)
|
||||
if candidate == "" {
|
||||
candidate = "."
|
||||
}
|
||||
info, err := access.root.Lstat(filepath.FromSlash(candidate))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.Mode()&fs.ModeSymlink == 0 {
|
||||
resolved = candidate
|
||||
continue
|
||||
}
|
||||
if _, exists := activeLinks[candidate]; exists {
|
||||
return "", errImportDirectoryLinkCycle
|
||||
}
|
||||
activeLinks[candidate] = struct{}{}
|
||||
target, err := access.root.Readlink(filepath.FromSlash(candidate))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var targetPath string
|
||||
if filepath.IsAbs(target) {
|
||||
relative, err := filepath.Rel(access.rootPath, filepath.Clean(target))
|
||||
if err != nil {
|
||||
return resolveImportLinkPath(filepath.ToSlash(name), hostImportLinkOperations(access))
|
||||
}
|
||||
|
||||
func hostImportLinkOperations(access *filesystemAccess) importLinkOperations {
|
||||
return importLinkOperations{
|
||||
lstat: func(candidate string) (fs.FileInfo, error) {
|
||||
return access.root.Lstat(filepath.FromSlash(candidate))
|
||||
},
|
||||
readlink: func(candidate string) (string, error) {
|
||||
return access.root.Readlink(filepath.FromSlash(candidate))
|
||||
},
|
||||
normalizeTarget: func(candidate, target string) (string, error) {
|
||||
var targetPath string
|
||||
if filepath.IsAbs(target) {
|
||||
relative, err := filepath.Rel(access.rootPath, filepath.Clean(target))
|
||||
if err != nil {
|
||||
return "", errImportPathOutsideRoot
|
||||
}
|
||||
targetPath = path.Clean(filepath.ToSlash(relative))
|
||||
} else {
|
||||
targetPath = path.Clean(path.Join(path.Dir(candidate), filepath.ToSlash(target)))
|
||||
}
|
||||
if !withinPortableRoot(targetPath) {
|
||||
return "", errImportPathOutsideRoot
|
||||
}
|
||||
targetPath = path.Clean(filepath.ToSlash(relative))
|
||||
} else {
|
||||
targetPath = path.Clean(path.Join(path.Dir(candidate), filepath.ToSlash(target)))
|
||||
}
|
||||
if !withinPortableRoot(targetPath) {
|
||||
return "", errImportPathOutsideRoot
|
||||
}
|
||||
frames = append(frames, importLinkFrame{pending: strings.Split(targetPath, "/"), link: candidate})
|
||||
resolved = ""
|
||||
return targetPath, nil
|
||||
},
|
||||
}
|
||||
if resolved == "" {
|
||||
resolved = "."
|
||||
}
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
func withinPortableRoot(name string) bool {
|
||||
|
||||
Reference in New Issue
Block a user