1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-08-11 17:51:04 +00:00

Try to fix safeWriteToFile producing empty files on Windows (#14085)

Use win32 APIs to write the temporary file before copying to the final
destination. Because we've observed the final file being empty, we
suspect that std::ostream::flush is not flushing.

Also add a test for it.
This commit is contained in:
Gary Miguel 2023-12-13 04:15:37 -08:00 committed by GitHub
parent a98200bb4c
commit 6eb9269741
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 18 deletions

View file

@ -758,14 +758,32 @@ bool safeWriteToFile(const std::string &path, const std::string &content)
std::string tmp_file = path + ".~mt";
// Write to a tmp file
std::ofstream os(tmp_file.c_str(), std::ios::binary);
if (!os.good())
bool tmp_success = false;
#ifdef _WIN32
// We've observed behavior suggesting that the MSVC implementation of std::ofstream::flush doesn't
// actually flush, so we use win32 APIs.
HANDLE tmp_handle = CreateFile(
tmp_file.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (tmp_handle == INVALID_HANDLE_VALUE) {
return false;
}
DWORD bytes_written;
tmp_success = (WriteFile(tmp_handle, content.c_str(), content.size(), &bytes_written, nullptr) &&
FlushFileBuffers(tmp_handle));
CloseHandle(tmp_handle);
#else
std::ofstream os(tmp_file.c_str(), std::ios::binary);
if (!os.good()) {
return false;
}
os << content;
os.flush();
os.close();
if (os.fail()) {
// Remove the temporary file because writing it failed and it's useless.
tmp_success = !os.fail();
#endif
if (!tmp_success) {
remove(tmp_file.c_str());
return false;
}
@ -777,14 +795,12 @@ bool safeWriteToFile(const std::string &path, const std::string &content)
// When creating the file, it can cause Windows Search indexer, virus scanners and other apps
// to query the file. This can make the move file call below fail.
// We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed
int number_attempts = 0;
while (number_attempts < 5) {
for (int attempt = 0; attempt < 5; attempt++) {
rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
if (rename_success)
break;
sleep_ms(1);
++number_attempts;
}
#else
// On POSIX compliant systems rename() is specified to be able to swap the