X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/0821d23c64f4ffc856760e4914fa39ba2e3260e4..fc0503be49438775c5d08499046852df59bd7377:/file.go?ds=inline diff --git a/file.go b/file.go index a7ba5bb..00c8a0c 100644 --- a/file.go +++ b/file.go @@ -28,3 +28,47 @@ func writeFileAtomic(path string, data []byte) error { return nil } + +// writeFileDurable replaces path only after syncing its private temporary file. +// It syncs the directory after rename before it reports success. An error after +// rename has an uncertain outcome, so callers must stop writes until recovery. +func writeFileDurable(path string, data []byte) (err error) { + file, err := os.CreateTemp(filepath.Dir(path), "."+filepath.Base(path)+".*") + if err != nil { + return fmt.Errorf("create durable temporary file: %w", err) + } + + defer func() { + removeErr := os.Remove(file.Name()) + if removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) { + err = errors.Join(err, fmt.Errorf("remove durable temporary file: %w", removeErr)) + } + }() + + _, err = file.Write(data) + if err == nil { + err = file.Sync() + } + + err = errors.Join(err, file.Close()) + if err != nil { + return fmt.Errorf("write durable temporary file: %w", err) + } + + err = os.Rename(file.Name(), path) + if err != nil { + return fmt.Errorf("publish durable file: %w", err) + } + + directory, err := os.Open(filepath.Dir(path)) + if err != nil { + return fmt.Errorf("open durable directory: %w", err) + } + + err = errors.Join(directory.Sync(), directory.Close()) + if err != nil { + return fmt.Errorf("sync durable directory: %w", err) + } + + return nil +}