fix(server/game): write saves through a temp file
This commit is contained in:
1 parent
c16c95db94
commit
cc7ec21ab9
2 files changed
+37
-8
No files matched your search
+27
-8
@@ -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() {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in new issue
Block a user