package engine import ( "io/fs" "os" "path" "path/filepath" "strings" ) func validateFilesystemContext(source Source, context *FilesystemContext, main parsedSource) *Diagnostic { if source.kind == sourceNone && context.FS == nil { return validateAbsentHostFilesystemContext(context) } if nativeContext(source, context) { access, basePath, mainPath, diagnostic := nativeFilesystemAccess(source, context) if diagnostic != nil { return diagnostic } return validateNativeDirectories(access, basePath, mainPath) } rootPath, _, _, diagnostic := importFilesystem(source, context, main) if diagnostic != nil { return diagnostic } root, err := os.OpenRoot(rootPath) if err != nil { value := globFilesystemDiagnostic(err, "configuration root could not be opened", Location{}) value.Primary = nil return &value } if err := root.Close(); err != nil { value := importDiagnostic(CodeImportRead, "configuration root could not be closed", nil) return &value } return nil } func validateAbsentHostFilesystemContext(context *FilesystemContext) *Diagnostic { rootPath, err := canonicalExistingPath(context.Root) if err != nil { value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil) return &value } basePath := rootPath if context.BaseDir != "" { basePath, err = canonicalExistingPath(context.BaseDir) if err != nil { value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil) return &value } } if !containedPath(rootPath, basePath) { value := importDiagnostic(CodeImportOutsideRoot, "import base must stay inside the configuration root", nil) return &value } for _, candidate := range []string{rootPath, basePath} { info, err := os.Stat(candidate) if err != nil || !info.IsDir() { value := importDiagnostic(CodeImportInvalid, "configuration root and import base must be directories", nil) return &value } } root, err := os.OpenRoot(rootPath) if err != nil { value := globFilesystemDiagnostic(err, "configuration root could not be opened", Location{}) value.Primary = nil return &value } if err := root.Close(); err != nil { value := importDiagnostic(CodeImportRead, "configuration root could not be closed", nil) return &value } return 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 } // nativeFilesystemAccess resolves namespace context without opening caller // resources. File inputs always derive their base from the selected file. func nativeFilesystemAccess(source Source, context *FilesystemContext) (*filesystemAccess, string, string, *Diagnostic) { var namespace *nativeNamespace rootPath := "." basePath := "." mainPath := "" if source.kind == sourceFilesystem { namespace = source.namespace mainPath = source.path basePath = path.Dir(source.path) if context != nil && context.Root != "" { rootPath = context.Root } } else if context != nil && context.FS != nil { namespace = context.FS.namespace rootPath, basePath = context.Root, context.BaseDir } if namespace == nil || nilFilesystem(namespace.filesystem) || !fs.ValidPath(rootPath) || !fs.ValidPath(basePath) { value := optionDiagnostic(CodeOptionsInvalid, "filesystem context is invalid") return nil, "", "", &value } access := newNativeAccess(namespace.filesystem, rootPath, namespace.token) access.label = namespace.label if !access.contains(basePath) || mainPath != "" && !access.contains(mainPath) { value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil) return nil, "", "", &value } return access, basePath, mainPath, nil } func validateNativeDirectories(access *filesystemAccess, basePath, mainPath string) *Diagnostic { if !access.contains(basePath) || mainPath != "" && !access.contains(mainPath) { value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil) return &value } for _, directory := range []string{access.rootPath, basePath} { info, err := access.stat(directory) if err != nil || !info.IsDir() { value := importDiagnostic(CodeImportInvalid, "configuration root and import base must be directories", nil) return &value } } return nil } func importFilesystem(source Source, context *FilesystemContext, main parsedSource) (rootPath, basePath, mainPath string, diagnostic *Diagnostic) { if main.source == nil || main.document == nil { value := importDiagnostic(CodeImportInvalid, "imports require a selected configuration source", nil) return "", "", "", &value } if source.kind == sourceFile { if context != nil && context.BaseDir != "" { value := optionDiagnostic(CodeOptionsInvalid, "file sources determine their own import base directory") return "", "", "", &value } canonical, err := canonicalExistingPath(source.path) if err != nil { value := importDiagnostic(CodeImportInvalid, "selected source path could not be canonicalized", nil) return "", "", "", &value } mainPath = canonical basePath = filepath.Dir(canonical) rootPath = 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 "", "", "", &value } } } else { if context == nil || context.BaseDir == "" || context.Root == "" { value := optionDiagnostic(CodeOptionsInvalid, "byte and reader imports require absolute base and root directories") return "", "", "", &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 "", "", "", &value } var err error basePath, err = canonicalExistingPath(context.BaseDir) if err != nil { value := importDiagnostic(CodeImportInvalid, "import base directory could not be canonicalized", nil) return "", "", "", &value } rootPath, err = canonicalExistingPath(context.Root) if err != nil { value := importDiagnostic(CodeImportInvalid, "configuration root could not be canonicalized", nil) return "", "", "", &value } } if !containedPath(rootPath, basePath) || (mainPath != "" && !containedPath(rootPath, mainPath)) { value := importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil) return "", "", "", &value } for _, candidate := range []string{rootPath, basePath} { info, err := os.Stat(candidate) if err != nil || !info.IsDir() { value := importDiagnostic(CodeImportInvalid, "configuration root and import base must be directories", nil) return "", "", "", &value } } return rootPath, basePath, mainPath, 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)) }