save: fsync data safely to disk (#2681)

On modern Linux systems, it can take 30 seconds for
the data to actually hit the disk (check
/proc/sys/vm/dirty_expire_centisecs).

If the computer crashes in those 30 seconds, the user
may end up with an empty file as seen here:
https://github.com/neovim/neovim/issues/9888

This is why editors like vim and nano call
the fsync syscall after they wrote the file.
This syscall is available as file.Sync() in Go.

Running strace against micro shows that fsync is
called as expected:

	$ strace -f -p $(pgrep micro) -e fsync
	strace: Process 3284344 attached with 9 threads
	[pid 3284351] fsync(8)                  = 0

Also, we now catch errors returned from w.Flush().
This commit is contained in:
rfjakob
2023-05-26 07:21:19 +02:00
committed by GitHub
parent fa468cac5f
commit 1b4f6ecb12

View File

@@ -59,10 +59,20 @@ func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error,
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
err = fn(w)
w.Flush()
if e := writeCloser.Close(); e != nil && err == nil {
err = e
if err2 := w.Flush(); err2 != nil && err == nil {
err = err2
}
// Call Sync() on the file to make sure the content is safely on disk.
// Does not work with sudo as we don't have direct access to the file.
if !withSudo {
f := writeCloser.(*os.File)
if err2 := f.Sync(); err2 != nil && err == nil {
err = err2
}
}
if err2 := writeCloser.Close(); err2 != nil && err == nil {
err = err2
}
if withSudo {