// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 package store import ( "fmt" "os" "path" "path/filepath" "strings" ) // atomicWriteIn replaces name inside the root: a temporary file in the // same directory, fsync, rename, and a directory fsync. func atomicWriteIn(root *os.Root, name string, data []byte) error { dir := path.Dir(name) base := path.Base(name) handle, err := root.OpenFile(path.Join(dir, "."+base+".tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) if err != nil { return fmt.Errorf("create temp file: %w", err) } if _, err := handle.Write(data); err != nil { handle.Close() root.Remove(path.Join(dir, "."+base+".tmp")) return fmt.Errorf("write: %w", err) } if err := handle.Sync(); err != nil { handle.Close() root.Remove(path.Join(dir, "."+base+".tmp")) return fmt.Errorf("sync: %w", err) } if err := handle.Close(); err != nil { root.Remove(path.Join(dir, "."+base+".tmp")) return fmt.Errorf("close: %w", err) } if err := root.Rename(path.Join(dir, "."+base+".tmp"), name); err != nil { root.Remove(path.Join(dir, "."+base+".tmp")) return fmt.Errorf("replace: %w", err) } syncDirIn(root, dir) return nil } // syncDirIn flushes a directory entry inside the root, so the rename is // durable. func syncDirIn(root *os.Root, dir string) { handle, err := root.Open(dir) if err != nil { return } defer handle.Close() _ = handle.Sync() } func absPath(path string) string { abs, err := filepath.Abs(path) if err != nil { return path } return abs } // within reports whether path is inside directory, comparing resolved // paths so that a symlinked content directory is not mistaken for an // escape. A path that does not exist yet is resolved through its // longest existing ancestor, which is what a new post file is. func within(directory, path string) bool { dirResolved := resolveExisting(directory) pathResolved := resolveExisting(path) if pathResolved == dirResolved { return true } rel, err := filepath.Rel(dirResolved, pathResolved) if err != nil { return false } return rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) } // resolveExisting resolves symlinks in the longest existing prefix of // path and appends the remainder unchanged. func resolveExisting(path string) string { if resolved, err := filepath.EvalSymlinks(path); err == nil { return resolved } parent := filepath.Dir(path) if parent == path || parent == "." { return path } return filepath.Join(resolveExisting(parent), filepath.Base(path)) }