package files import ( "fmt" "os" "path/filepath" "runtime" ) func EnsureDir(path string, perm os.FileMode) error { if err := os.MkdirAll(path, perm); err != nil { return fmt.Errorf("mkdir %s: %w", path, err) } return nil } func WriteFileAtomic(path string, data []byte, perm os.FileMode) error { dir := filepath.Dir(path) if err := EnsureDir(dir, 0o755); err != nil { return err } f, err := os.CreateTemp(dir, ".tmp-*") if err != nil { return fmt.Errorf("create temp: %w", err) } tmp := f.Name() ok := false defer func() { _ = f.Close() if !ok { _ = os.Remove(tmp) } }() if _, err := f.Write(data); err != nil { return fmt.Errorf("write temp: %w", err) } if err := f.Chmod(perm); err != nil { return fmt.Errorf("chmod temp: %w", err) } if err := f.Sync(); err != nil { return fmt.Errorf("fsync temp: %w", err) } if err := f.Close(); err != nil { return fmt.Errorf("close temp: %w", err) } if err := ReplaceFile(tmp, path); err != nil { return err } ok = true return nil } func ReplaceFile(tmpPath, dstPath string) error { if err := os.Rename(tmpPath, dstPath); err == nil { return nil } else if runtime.GOOS == "windows" { _ = os.Remove(dstPath) if err2 := os.Rename(tmpPath, dstPath); err2 != nil { return fmt.Errorf("rename %s -> %s: %w", tmpPath, dstPath, err2) } return nil } else { return fmt.Errorf("rename %s -> %s: %w", tmpPath, dstPath, err) } }