Preflight report output filenames

This commit is contained in:
2026-08-13 02:33:32 +00:00
parent 44ee389334
commit f4e3a6f26c
6 changed files with 76 additions and 3 deletions

View File

@@ -8,11 +8,28 @@ import (
"path/filepath"
)
const (
maxFileNameBytes = 255
temporaryFilePattern = ".weatherreporter-*.tmp"
)
// ValidateAtomicPath verifies that the final path can be safely used with this
// package's same-directory atomic-write implementation.
func ValidateAtomicPath(path string) error {
if len(filepath.Base(path)) > maxFileNameBytes {
return fmt.Errorf("final file name exceeds the %d-byte limit", maxFileNameBytes)
}
return nil
}
func WriteFileAtomic(path string, data []byte) error {
if err := ValidateAtomicPath(path); err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return fmt.Errorf("create directory %q: %w", filepath.Dir(path), err)
}
tmp, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*.tmp")
tmp, err := os.CreateTemp(filepath.Dir(path), temporaryFilePattern)
if err != nil {
return fmt.Errorf("create temporary file for %q: %w", path, err)
}

View File

@@ -42,6 +42,31 @@ func TestWriteFileAtomicOverwritesTarget(t *testing.T) {
}
}
func TestWriteFileAtomicSupportsLongestFileName(t *testing.T) {
directory := t.TempDir()
name := strings.Repeat("a", maxFileNameBytes-len(".md")) + ".md"
path := filepath.Join(directory, name)
if err := os.WriteFile(path, []byte("old"), 0o600); err != nil {
t.Fatal(err)
}
if err := WriteFileAtomic(path, []byte("new")); err != nil {
t.Fatalf("WriteFileAtomic() error = %v", err)
}
data, err := os.ReadFile(path)
if err != nil || string(data) != "new" {
t.Fatalf("output/error = %q/%v", data, err)
}
info, err := os.Stat(path)
if err != nil || info.Mode().Perm() != 0o600 {
t.Fatalf("output mode/error = %o/%v", info.Mode().Perm(), err)
}
matches, err := filepath.Glob(filepath.Join(directory, ".weatherreporter-*.tmp"))
if err != nil || len(matches) != 0 {
t.Fatalf("temporary files/error = %v/%v", matches, err)
}
}
func TestWriteFileAtomicCleansTemporaryFileAfterRenameError(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "target")
@@ -56,7 +81,7 @@ func TestWriteFileAtomicCleansTemporaryFileAfterRenameError(t *testing.T) {
if !strings.Contains(err.Error(), "save") {
t.Fatalf("error = %q, want save context", err.Error())
}
matches, err := filepath.Glob(filepath.Join(dir, ".target.*.tmp"))
matches, err := filepath.Glob(filepath.Join(dir, ".weatherreporter-*.tmp"))
if err != nil {
t.Fatalf("Glob() error = %v", err)
}