From f00fb4706c4c2459d528fb5f8ad0fa112b00bdd0 Mon Sep 17 00:00:00 2001
From: Simon Ruderich <simon@ruderich.org>
Date: Sat, 16 Jan 2021 11:34:59 +0100
Subject: [PATCH] Guarantee durability after renaming temporary files

---
 file.go  |  6 +++++-
 main.go  |  6 +++++-
 misc.go  | 22 ++++++++++++++++++++++
 state.go |  6 +++++-
 4 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/file.go b/file.go
index 1d102a4..3884bba 100644
--- a/file.go
+++ b/file.go
@@ -198,5 +198,9 @@ func deployFile(file *File) error {
 	if err != nil {
 		return err
 	}
-	return f.CloseAtomicallyReplace()
+	err = f.CloseAtomicallyReplace()
+	if err != nil {
+		return err
+	}
+	return syncPath(filepath.Dir(file.Path))
 }
diff --git a/main.go b/main.go
index 9b12752..7a7d8b0 100644
--- a/main.go
+++ b/main.go
@@ -151,5 +151,9 @@ func mainConvert(typ, srcPath, dstPath string) error {
 		return err
 	}
 
-	return f.CloseAtomicallyReplace()
+	err = f.CloseAtomicallyReplace()
+	if err != nil {
+		return err
+	}
+	return syncPath(filepath.Dir(dstPath))
 }
diff --git a/misc.go b/misc.go
index 897a764..3cbe90f 100644
--- a/misc.go
+++ b/misc.go
@@ -20,6 +20,7 @@ package main
 import (
 	"bytes"
 	"fmt"
+	"os"
 )
 
 func alignBufferTo(b *bytes.Buffer, align int) {
@@ -35,3 +36,24 @@ func alignBufferTo(b *bytes.Buffer, align int) {
 		b.WriteByte(0)
 	}
 }
+
+// syncPath syncs path, which should be a directory. To guarantee durability
+// it must be called on a parent directory after adding, renaming or removing
+// files therein.
+//
+// Calling sync on the files itself is not enough according to POSIX; man 2
+// fsync: "Calling fsync() does not necessarily ensure that the entry in the
+// directory containing the file has also reached disk. For that an explicit
+// fsync() on a file descriptor for the directory is also needed."
+func syncPath(path string) error {
+	x, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	err = x.Sync()
+	closeErr := x.Close()
+	if err != nil {
+		return err
+	}
+	return closeErr
+}
diff --git a/state.go b/state.go
index be08e72..59c885a 100644
--- a/state.go
+++ b/state.go
@@ -80,5 +80,9 @@ func WriteState(path string, state *State) error {
 	if err != nil {
 		return err
 	}
-	return f.CloseAtomicallyReplace()
+	err = f.CloseAtomicallyReplace()
+	if err != nil {
+		return err
+	}
+	return syncPath(filepath.Dir(path))
 }
-- 
2.49.0