package engine import ( "io/fs" "os" "path/filepath" "strings" ) func validateFilesystemContext(source Source, context *FilesystemContext, main parsedSource, hostToken *namespaceToken) (resolvedFilesystemContext, *Diagnostic) { resolved, diagnostic := resolveFilesystemContext(source, context, main, hostToken) if diagnostic != nil { // Keep absent-input validation's existing diagnostic while sharing // root/base derivation with overlay and resource-context handling. if source.kind == sourceNone && context.FS == nil && diagnostic.Code == CodeImportOutsideRoot { diagnostic.Message = "import base must stay inside the configuration root" } return resolvedFilesystemContext{}, diagnostic } if diagnostic := validateResolvedFilesystemDirectories(resolved); diagnostic != nil { return resolvedFilesystemContext{}, diagnostic } if resolved.native != nil { return resolved, nil } root, err := os.OpenRoot(resolved.rootPath) if err != nil { value := globFilesystemDiagnostic(err, "configuration root could not be opened", Location{}) value.Primary = nil return resolvedFilesystemContext{}, &value } if err := root.Close(); err != nil { value := importDiagnostic(CodeImportRead, "configuration root could not be closed", nil) return resolvedFilesystemContext{}, &value } return resolved, nil } func filesystemOptionDiagnostics(source Source, context *FilesystemContext) []Diagnostic { switch source.kind { case sourceNone: return absentFilesystemOptionDiagnostics(context) case sourceSearch: if context.FS != nil { return absentFilesystemOptionDiagnostics(context) } // A selected host file can derive its root; absence requires an // explicit root. Check that compatibility only after selection. if context.BaseDir != "" && !filepath.IsAbs(context.BaseDir) { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "filesystem base directory must be an absolute host path")} } case sourceFile: if context.FS != nil { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "host file sources cannot use a native filesystem context")} } if context.BaseDir != "" { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "file sources determine their own import base directory")} } case sourceFilesystem: if context.FS == nil || context.FS.namespace == nil || source.namespace == nil || context.FS.namespace.token != source.namespace.token { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "filesystem file context must use the source namespace")} } if context.BaseDir != "" { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "file sources determine their own import base directory")} } if context.Root != "" && !fs.ValidPath(context.Root) { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "filesystem root must be a slash-relative valid path")} } case sourceBytes, sourceReader, sourceMapping: if context.BaseDir == "" || context.Root == "" { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "byte and reader filesystem context requires base and root directories")} } if context.FS != nil { if context.FS.namespace == nil || !fs.ValidPath(context.BaseDir) || !fs.ValidPath(context.Root) { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "filesystem base and root must be slash-relative valid paths")} } } else if !filepath.IsAbs(context.BaseDir) || !filepath.IsAbs(context.Root) { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "byte and reader filesystem base and root directories must be absolute")} } } return nil } func absentFilesystemOptionDiagnostics(context *FilesystemContext) []Diagnostic { if context.FS != nil { if context.FS.namespace == nil || nilFilesystem(context.FS.namespace.filesystem) || context.BaseDir == "" || context.Root == "" || !fs.ValidPath(context.BaseDir) || !fs.ValidPath(context.Root) { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "native filesystem context requires a namespace, base, and root directory")} } } else if context.Root == "" || !filepath.IsAbs(context.Root) || context.BaseDir != "" && !filepath.IsAbs(context.BaseDir) { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "absent-input filesystem root and base directories must be absolute host paths")} } return nil } func nativeContext(source Source, context *FilesystemContext) bool { return source.kind == sourceFilesystem || context != nil && context.FS != nil } func canonicalExistingPath(name string) (string, error) { absolute, err := filepath.Abs(name) if err != nil { return "", err } return filepath.EvalSymlinks(filepath.Clean(absolute)) } func containedPath(root, target string) bool { relative, err := filepath.Rel(root, target) if err != nil || filepath.IsAbs(relative) { return false } return relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) }