Files
volumen/internal/store/file.go
T
petrbalvin f8ed33df83
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Initial commit
Assisted-by: GLM 5.3
2026-09-29 10:03:32 +02:00

93 lines
2.6 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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))
}