From 408dcab92e8cbfbdf0eed32ca547b72670f6e111 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 11 Nov 2022 22:37:02 +0100 Subject: [PATCH 1/2] Port fsync error handling from restic This ignores several different combinations of errnos which are returned if the storage destination is not able to fsync correctly. See also https://github.com/restic/restic/pull/4021 --- repo/repo.go | 27 ++++++++++++++++++++++----- repo/repo_unix.go | 19 +++++++++++++++++++ repo/repo_windows.go | 4 ++++ 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 repo/repo_unix.go create mode 100644 repo/repo_windows.go diff --git a/repo/repo.go b/repo/repo.go index e59cc37..1d1a565 100644 --- a/repo/repo.go +++ b/repo/repo.go @@ -604,7 +604,8 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) { return } - if err := tf.Sync(); err != nil { + syncNotSup, err := syncFile(tf) + if err != nil { _ = tf.Close() _ = os.Remove(tf.Name()) h.incrementRepoSpaceUsage(-written) @@ -626,10 +627,12 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) { return } - if err := syncDir(filepath.Dir(path)); err != nil { - // Don't call os.Remove(path) as this is prone to race conditions with parallel upload retries - h.internalServerError(w, err) - return + if !syncNotSup { + if err := syncDir(filepath.Dir(path)); err != nil { + // Don't call os.Remove(path) as this is prone to race conditions with parallel upload retries + h.internalServerError(w, err) + return + } } h.sendMetric(objectType, BlobWrite, uint64(written)) @@ -648,6 +651,16 @@ func tempFile(fn string, perm os.FileMode) (f *os.File, err error) { return } +func syncFile(f *os.File) (bool, error) { + err := f.Sync() + // Ignore error if filesystem does not support fsync. + syncNotSup := err != nil && (errors.Is(err, syscall.ENOTSUP) || isMacENOTTY(err)) + if syncNotSup { + err = nil + } + return syncNotSup, err +} + func syncDir(dirname string) error { if runtime.GOOS == "windows" { // syncing a directory is not possible on windows @@ -659,6 +672,10 @@ func syncDir(dirname string) error { return err } err = dir.Sync() + // Ignore error if filesystem does not support fsync. + if errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.ENOENT) || errors.Is(err, syscall.EINVAL) { + err = nil + } if err != nil { _ = dir.Close() return err diff --git a/repo/repo_unix.go b/repo/repo_unix.go new file mode 100644 index 0000000..c2efca9 --- /dev/null +++ b/repo/repo_unix.go @@ -0,0 +1,19 @@ +//go:build !windows +// +build !windows + +package repo + +import ( + "errors" + "runtime" + "syscall" +) + +// The ExFAT driver on some versions of macOS can return ENOTTY, +// "inappropriate ioctl for device", for fsync. +// +// https://github.com/restic/restic/issues/4016 +// https://github.com/realm/realm-core/issues/5789 +func isMacENOTTY(err error) bool { + return runtime.GOOS == "darwin" && errors.Is(err, syscall.ENOTTY) +} diff --git a/repo/repo_windows.go b/repo/repo_windows.go new file mode 100644 index 0000000..5091b11 --- /dev/null +++ b/repo/repo_windows.go @@ -0,0 +1,4 @@ +package repo + +// Windows is not macOS. +func isMacENOTTY(err error) bool { return false } From 80babf98e7ee83489fd1545ae08a7d4b2de526fb Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 2 Dec 2022 21:08:15 +0100 Subject: [PATCH 2/2] add fsync warning --- repo/repo.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/repo/repo.go b/repo/repo.go index 1d1a565..5fc9883 100644 --- a/repo/repo.go +++ b/repo/repo.go @@ -16,6 +16,7 @@ import ( "runtime" "strconv" "strings" + "sync" "syscall" "time" @@ -73,6 +74,8 @@ func New(path string, opt Options) (*Handler, error) { type Handler struct { path string // filesystem path of repo opt Options + + fsyncWarning sync.Once } // httpDefaultError write a HTTP error with the default description @@ -633,6 +636,9 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) { h.internalServerError(w, err) return } + h.fsyncWarning.Do(func() { + log.Print("WARNING: fsync is not supported by the data storage. This can lead to data loss, if the system crashes or the storage is unexpectedly disconnected.") + }) } h.sendMetric(objectType, BlobWrite, uint64(written))