fix(server/game): write saves through a temp file

This commit is contained in:
capyb2222 committed 2026-09-12 20:30:27 -07:00
1 parent c16c95db94
commit cc7ec21ab9
2 files changed
+37 -8

No files matched your search

+27 -8
View File
@@ -175,15 +175,34 @@ std::string readFile(const std::string& path, bool* ok) {
} }
bool writeFile(const std::string& path, std::string_view data) { bool writeFile(const std::string& path, std::string_view data) {
std::filesystem::path p(path); std::filesystem::path target(path);
if (p.has_parent_path()) { std::error_code ec;
std::error_code ec; if (target.has_parent_path()) std::filesystem::create_directories(target.parent_path(), ec);
std::filesystem::create_directories(p.parent_path(), ec);
auto writeTo = [data](const std::filesystem::path& file) {
std::ofstream out(file, std::ios::binary | std::ios::trunc);
if (!out) return false;
out.write(data.data(), static_cast<std::streamsize>(data.size()));
out.close();
return !out.fail();
};
// Written beside the target and moved over it in one step, so a crash mid-write leaves
// the old file whole instead of cut short.
std::filesystem::path temp = target;
temp += ".tmp";
if (writeTo(temp)) {
#ifdef _WIN32
if (MoveFileExW(temp.c_str(), target.c_str(), MOVEFILE_REPLACE_EXISTING)) return true;
#else
std::filesystem::rename(temp, target, ec);
if (!ec) return true;
#endif
} }
std::ofstream out(path, std::ios::binary | std::ios::trunc); std::filesystem::remove(temp, ec);
if (!out) return false; // Something is holding the target in a way that blocks the move; a direct write still
out.write(data.data(), static_cast<std::streamsize>(data.size())); // beats dropping the save.
return out.good(); return writeTo(target);
} }
std::string executableDir() { std::string executableDir() {
+10
View File
@@ -311,6 +311,15 @@ void testLogFormat() {
"the banner names its rows"); "the banner names its rows");
} }
void testSafeWrite() {
const std::string path = "build/test-safe-write.json";
check(util::writeFile(path, "first"), "a file writes");
check(util::writeFile(path, "second, and longer"), "and overwrites");
bool ok = false;
check(util::readFile(path, &ok) == "second, and longer" && ok, "with the new content whole");
check(!util::fileExists(path + ".tmp"), "leaving no temp file behind");
}
} // namespace } // namespace
int main() { int main() {
@@ -324,6 +333,7 @@ int main() {
testLineupCmdIds(); testLineupCmdIds();
testIdleTimeout(); testIdleTimeout();
testLogFormat(); testLogFormat();
testSafeWrite();
testFillPresence(); testFillPresence();
testLargeMessage(); testLargeMessage();
testAgainstProtoc(); testAgainstProtoc();