10 // writeFileAtomic replaces the file at path with data through a temporary file
11 // and a rename. A reader or a crash of the process sees the old file or the new
12 // file, never a part of one.
13 func writeFileAtomic(path string, data []byte) error {
14 file, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*")
16 return fmt.Errorf("create temporary file: %w", err)
19 _, err = file.Write(data)
20 err = errors.Join(err, file.Close())
22 err = os.Rename(file.Name(), path)
26 return errors.Join(fmt.Errorf("replace file: %w", err), os.Remove(file.Name()))
32 // writeFileDurable replaces path only after syncing its private temporary file.
33 // It syncs the directory after rename before it reports success. An error after
34 // rename has an uncertain outcome, so callers must stop writes until recovery.
35 func writeFileDurable(path string, data []byte) (err error) {
36 file, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*")
38 return fmt.Errorf("create durable temporary file: %w", err)
42 removeErr := os.Remove(file.Name())
43 if removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
44 err = errors.Join(err, fmt.Errorf("remove durable temporary file: %w", removeErr))
48 _, err = file.Write(data)
53 err = errors.Join(err, file.Close())
55 return fmt.Errorf("write durable temporary file: %w", err)
58 err = os.Rename(file.Name(), path)
60 return fmt.Errorf("publish durable file: %w", err)
63 directory, err := os.Open(filepath.Dir(path))
65 return fmt.Errorf("open durable directory: %w", err)
68 err = errors.Join(directory.Sync(), directory.Close())
70 return fmt.Errorf("sync durable directory: %w", err)