From c967253f80dcf3ab65bac3fd7d38f0a8000adc10 Mon Sep 17 00:00:00 2001 From: Andrew Ayer Date: Sun, 4 May 2025 20:44:36 -0400 Subject: [PATCH] monitor: fsync state files before renaming them Without fsync, there's a risk of zero-length files being persisted if there's a power failure. Don't bother fsyncing the parent directory because it's OK if the data rolls back to the previous version; we only need to avoid data corruption. Closes: #101 --- monitor/fileutils.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/monitor/fileutils.go b/monitor/fileutils.go index 5d0ee28..26a7919 100644 --- a/monitor/fileutils.go +++ b/monitor/fileutils.go @@ -25,9 +25,24 @@ func randomFileSuffix() string { return hex.EncodeToString(randomBytes[:]) } +func writeSyncFile(filename string, data []byte, perm os.FileMode) error { + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + _, err = f.Write(data) + if err2 := f.Sync(); err2 != nil && err == nil { + err = err2 + } + if err2 := f.Close(); err2 != nil && err == nil { + err = err2 + } + return err +} + func writeFile(filename string, data []byte, perm os.FileMode) error { tempname := filename + ".tmp." + randomFileSuffix() - if err := os.WriteFile(tempname, data, perm); err != nil { + if err := writeSyncFile(tempname, data, perm); err != nil { return fmt.Errorf("error writing %s: %w", filename, err) } if err := os.Rename(tempname, filename); err != nil {