+package main
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+// writeFileAtomic replaces the file at path with data through a temporary file
+// and a rename. A reader or a crash of the process sees the old file or the new
+// file, never a part of one.
+func writeFileAtomic(path string, data []byte) error {
+ file, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*")
+ if err != nil {
+ return fmt.Errorf("create temporary file: %w", err)
+ }
+
+ _, err = file.Write(data)
+ err = errors.Join(err, file.Close())
+ if err == nil {
+ err = os.Rename(file.Name(), path)
+ }
+
+ if err != nil {
+ return errors.Join(fmt.Errorf("replace file: %w", err), os.Remove(file.Name()))
+ }
+
+ return nil
+}