package engine import ( "io/fs" "os" "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) { resolved, diagnostic := resolveNativeFilesystemContext(source, context) if diagnostic != nil { return diagnostic } return validateResolvedFilesystemDirectories(resolved) } resolved, diagnostic := resolveHostFilesystemContext(source, context, main, nil) if diagnostic != nil { return diagnostic } if diagnostic := validateResolvedFilesystemDirectories(resolved); diagnostic != nil { return diagnostic } root, err := os.OpenRoot(resolved.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 } 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)) }