From 2b78e99138db77c180263d5b8b602e029c073668 Mon Sep 17 00:00:00 2001 From: Nils Schimmelmann Date: Mon, 20 Apr 2026 08:07:05 -0500 Subject: [PATCH] add atomic saving via .tmp files and ReplaceFileW for Windows --- src/global/io.cpp | 40 +++++++++++++++++++++++++++++++++++- src/global/io.h | 2 ++ src/mapstorage/filesaver.cpp | 15 ++++---------- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/global/io.cpp b/src/global/io.cpp index db56d9749..acbd5d64c 100644 --- a/src/global/io.cpp +++ b/src/global/io.cpp @@ -21,6 +21,9 @@ #ifdef Q_OS_WIN #include "WinSock.h" + +#include +#include #endif namespace io { @@ -61,7 +64,9 @@ bool fsync(QFile &file) CAN_THROW { const int handle = file.handle(); #ifdef Q_OS_WIN - return false; + if (::FlushFileBuffers(reinterpret_cast(::_get_osfhandle(handle))) == 0) { + throw IOException::withErrorNumber(static_cast(::GetLastError())); + } #elif defined(Q_OS_MAC) if (::fcntl(handle, F_FULLFSYNC) == -1) { throw IOException::withCurrentErrno(); @@ -74,6 +79,39 @@ bool fsync(QFile &file) CAN_THROW return true; } +void rename(const QString &from, const QString &to) CAN_THROW +{ +#ifdef Q_OS_WIN + const std::wstring fromW = from.toStdWString(); + const std::wstring toW = to.toStdWString(); + if (::ReplaceFileW(toW.c_str(), + fromW.c_str(), + nullptr, + REPLACEFILE_IGNORE_MERGE_ERRORS, + nullptr, + nullptr) + == 0) { + const auto err = ::GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) { + if (::MoveFileExW(fromW.c_str(), + toW.c_str(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) + == 0) { + throw IOException::withErrorNumber(static_cast(::GetLastError())); + } + } else { + throw IOException::withErrorNumber(static_cast(err)); + } + } +#else + const auto fromEncoded = QFile::encodeName(from); + const auto toEncoded = QFile::encodeName(to); + if (::rename(fromEncoded.data(), toEncoded.data()) == -1) { + throw IOException::withCurrentErrno(); + } +#endif +} + IOResultEnum fsyncNoexcept(QFile &file) noexcept { try { diff --git a/src/global/io.h b/src/global/io.h index f4051d0d9..b6db8e60d 100644 --- a/src/global/io.h +++ b/src/global/io.h @@ -135,6 +135,8 @@ static_assert(sizeof(ErrorNumberMessage) == 1024); NODISCARD extern bool fsync(QFile &) CAN_THROW; +extern void rename(const QString &from, const QString &to) CAN_THROW; + NODISCARD extern IOResultEnum fsyncNoexcept(QFile &) noexcept; NODISCARD extern bool tuneKeepAlive(qintptr socketDescriptor, diff --git a/src/mapstorage/filesaver.cpp b/src/mapstorage/filesaver.cpp index 59200caca..6f7d1c2e7 100644 --- a/src/mapstorage/filesaver.cpp +++ b/src/mapstorage/filesaver.cpp @@ -14,25 +14,18 @@ #include -static constexpr const bool USE_TMP_SUFFIX = CURRENT_PLATFORM != PlatformEnum::Windows; - static const char *const TMP_FILE_SUFFIX = ".tmp"; NODISCARD static auto maybe_add_suffix(const QString &filename) { - return USE_TMP_SUFFIX ? (filename + TMP_FILE_SUFFIX) : filename; + return filename + TMP_FILE_SUFFIX; } static void remove_tmp_suffix(const QString &filename) CAN_THROW { - if (!USE_TMP_SUFFIX) { - return; - } + const QString from = filename + TMP_FILE_SUFFIX; + const QString to = filename; - const auto from = QFile::encodeName(filename + TMP_FILE_SUFFIX); - const auto to = QFile::encodeName(filename); - if (::rename(from.data(), to.data()) == -1) { - throw io::IOException::withCurrentErrno(); - } + io::rename(from, to); } FileSaver::~FileSaver()