package engine import ( "errors" "io" "io/fs" "os" "path" "path/filepath" "strings" ) // namespaceToken identifies one caller-selected filesystem without comparing // arbitrary fs.FS values. A token can be shared by copies of a source wrapper. type namespaceToken struct{ marker byte } type nativeNamespace struct { filesystem fs.FS label string token *namespaceToken } func newNativeNamespace(label string, filesystem fs.FS) *nativeNamespace { return &nativeNamespace{filesystem: filesystem, label: label, token: &namespaceToken{}} } type resourceIdentity struct { namespace *namespaceToken path string occurrence *namespaceToken } // filesystemAccess is a load-scoped view of either a contained host root or a // borrowed native filesystem. Names are canonical host paths or fs.FS paths. type filesystemAccess struct { rootPath string root *os.Root native fs.FS token *namespaceToken label string } func openHostAccess(rootPath string) (*filesystemAccess, error) { root, err := os.OpenRoot(rootPath) if err != nil { return nil, err } return &filesystemAccess{rootPath: rootPath, root: root, token: &namespaceToken{}}, nil } func newNativeAccess(filesystem fs.FS, root string, token *namespaceToken) *filesystemAccess { return &filesystemAccess{rootPath: root, native: filesystem, token: token} } func (a *filesystemAccess) close() error { if a.root != nil { return a.root.Close() } return nil } func (a *filesystemAccess) identity(name string) resourceIdentity { return resourceIdentity{namespace: a.token, path: name} } func (a *filesystemAccess) syntheticIdentity() resourceIdentity { return resourceIdentity{namespace: a.token, occurrence: &namespaceToken{}} } func (a *filesystemAccess) join(base, name string) string { if a.native != nil { return path.Join(base, name) } return filepath.Join(base, filepath.FromSlash(path.Clean(name))) } func (a *filesystemAccess) dir(name string) string { if a.native != nil { return path.Dir(name) } return filepath.Dir(name) } func (a *filesystemAccess) contains(name string) bool { if a.native == nil { return containedPath(a.rootPath, filepath.Clean(name)) } if !fs.ValidPath(name) || !fs.ValidPath(a.rootPath) { return false } return a.rootPath == "." || name == a.rootPath || strings.HasPrefix(name, a.rootPath+"/") } func (a *filesystemAccess) canonical(name string) (string, error) { if a.native == nil { return canonicalExistingPath(name) } if !fs.ValidPath(name) { return "", fs.ErrInvalid } _, err := fs.Stat(a.native, name) if err != nil { return "", err } return name, nil } func (a *filesystemAccess) relative(name string) (string, error) { if a.native != nil { if !a.contains(name) { return "", fs.ErrInvalid } return name, nil } relative, err := filepath.Rel(a.rootPath, name) if err != nil || filepath.IsAbs(relative) || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) { return "", fs.ErrInvalid } return relative, nil } func (a *filesystemAccess) stat(name string) (fs.FileInfo, error) { if a.native != nil { return fs.Stat(a.native, name) } relative, err := a.relative(name) if err != nil { return nil, err } return a.root.Stat(relative) } func (a *filesystemAccess) open(name string) (fs.File, error) { if a.native != nil { return a.native.Open(name) } relative, err := a.relative(name) if err != nil { return nil, err } return a.root.Open(relative) } type directoryReadError struct { opened bool err error } func (e *directoryReadError) Error() string { return "directory could not be read" } func (e *directoryReadError) Unwrap() error { return e.err } func missingDirectoryOpen(err error) bool { var failure *directoryReadError return errors.As(err, &failure) && !failure.opened && errors.Is(err, fs.ErrNotExist) } func (a *filesystemAccess) readDirectory(name string, visit func(fs.DirEntry) bool) error { file, err := a.open(name) if err != nil { return &directoryReadError{err: err} } failure := func(err error) error { if err == nil { return nil } return &directoryReadError{opened: true, err: err} } if reader, ok := file.(fs.ReadDirFile); ok { for { entries, readErr := reader.ReadDir(globReadBatch) for _, entry := range entries { if !visit(entry) { return failure(file.Close()) } } if errors.Is(readErr, io.EOF) { return failure(file.Close()) } if readErr != nil { _ = file.Close() return failure(readErr) } if len(entries) == 0 { _ = file.Close() return failure(io.ErrNoProgress) } } } if err := file.Close(); err != nil { return failure(err) } // fs.ReadDir provides the standard fallback for implementations whose opened // files do not implement fs.ReadDirFile. It may allocate all entries itself. entries, err := fs.ReadDir(a.native, name) if err != nil { return failure(err) } for _, entry := range entries { if !visit(entry) { break } } return nil }