Share link dependency evaluation with host access

This commit is contained in:
2026-10-25 21:45:33 +00:00
parent 7bf2f0c687
commit 0f9ecf12ed
3 changed files with 156 additions and 51 deletions

View File

@@ -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 {