package engine import ( "io/fs" "os" "path" "path/filepath" "strings" ) type resolvedFilesystemContext struct { native *nativeNamespace hostToken *namespaceToken rootPath string basePath string mainPath string } func resolveFilesystemContext(source Source, context *FilesystemContext, main parsedSource, hostToken *namespaceToken) (resolvedFilesystemContext, *Diagnostic) { if nativeContext(source, context) { return resolveNativeFilesystemContext(source, context) } return resolveHostFilesystemContext(source, context, main, hostToken) } func resolveNativeFilesystemContext(source Source, context *FilesystemContext) (resolvedFilesystemContext, *Diagnostic) { var namespace *nativeNamespace resolved := resolvedFilesystemContext{rootPath: ".", basePath: "."} if source.kind == sourceFilesystem { namespace = source.namespace resolved.mainPath = source.path resolved.basePath = path.Dir(source.path) if context != nil && context.Root != "" { resolved.rootPath = context.Root } } else if context != nil && context.FS != nil { namespace = context.FS.namespace resolved.rootPath, resolved.basePath = context.Root, context.BaseDir } if namespace == nil || nilFilesystem(namespace.filesystem) || !fs.ValidPath(resolved.rootPath) || !fs.ValidPath(resolved.basePath) { value := optionDiagnostic(CodeOptionsInvalid, "filesystem context is invalid") return resolvedFilesystemContext{}, &value } resolved.native = namespace if !containsNativePath(resolved.rootPath, resolved.basePath) || resolved.mainPath != "" && !containsNativePath(resolved.rootPath, resolved.mainPath) { value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil) return resolvedFilesystemContext{}, &value } return resolved, nil } func resolveHostFilesystemContext(source Source, context *FilesystemContext, main parsedSource, hostToken *namespaceToken) (resolvedFilesystemContext, *Diagnostic) { if main.source == nil || main.document == nil { value := importDiagnostic(CodeImportInvalid, "imports require a selected configuration source", nil) return resolvedFilesystemContext{}, &value } resolved := resolvedFilesystemContext{hostToken: hostToken} if source.kind == sourceFile { if context != nil && context.BaseDir != "" { value := optionDiagnostic(CodeOptionsInvalid, "file sources determine their own import base directory") return resolvedFilesystemContext{}, &value } canonical := main.source.Path if canonical == "" { var err error canonical, err = canonicalExistingPath(source.path) if err != nil { value := importDiagnostic(CodeImportInvalid, "selected source path could not be canonicalized", nil) return resolvedFilesystemContext{}, &value } } resolved.mainPath = canonical resolved.basePath = filepath.Dir(canonical) resolved.rootPath = resolved.basePath if context != nil && context.Root != "" { rootPath, err := canonicalExistingPath(context.Root) if err != nil { value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil) return resolvedFilesystemContext{}, &value } resolved.rootPath = rootPath } } else { if context == nil || context.BaseDir == "" || context.Root == "" { value := optionDiagnostic(CodeOptionsInvalid, "byte and reader imports require absolute base and root directories") return resolvedFilesystemContext{}, &value } if !filepath.IsAbs(context.BaseDir) || !filepath.IsAbs(context.Root) { value := optionDiagnostic(CodeOptionsInvalid, "byte and reader import base and root directories must be absolute") return resolvedFilesystemContext{}, &value } var err error resolved.basePath, err = canonicalExistingPath(context.BaseDir) if err != nil { value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil) return resolvedFilesystemContext{}, &value } resolved.rootPath, err = canonicalExistingPath(context.Root) if err != nil { value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil) return resolvedFilesystemContext{}, &value } } if !containedPath(resolved.rootPath, resolved.basePath) || resolved.mainPath != "" && !containedPath(resolved.rootPath, resolved.mainPath) { value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil) return resolvedFilesystemContext{}, &value } return resolved, nil } func containsNativePath(root, name string) bool { if !fs.ValidPath(name) || !fs.ValidPath(root) { return false } return root == "." || name == root || strings.HasPrefix(name, root+"/") } func validateResolvedFilesystemDirectories(resolved resolvedFilesystemContext) *Diagnostic { for _, directory := range []string{resolved.rootPath, resolved.basePath} { var info fs.FileInfo var err error if resolved.native != nil { info, err = fs.Stat(resolved.native.filesystem, directory) } else { info, err = os.Stat(directory) } if err != nil || !info.IsDir() { value := importDiagnostic(CodeImportInvalid, "configuration root and import base must be directories", nil) return &value } } return nil } func openResolvedFilesystemAccess(resolved resolvedFilesystemContext) (*filesystemAccess, error) { if resolved.native != nil { access := newNativeAccess(resolved.native.filesystem, resolved.rootPath, resolved.native.token) access.label = resolved.native.label return access, nil } access, err := openHostAccess(resolved.rootPath) if err != nil { return nil, err } if resolved.hostToken != nil { access.token = resolved.hostToken } return access, nil }