diff --git a/Source/Core/Common/Src/FileUtil.cpp b/Source/Core/Common/Src/FileUtil.cpp index dc07ddd7d7..441c8ce405 100644 --- a/Source/Core/Common/Src/FileUtil.cpp +++ b/Source/Core/Common/Src/FileUtil.cpp @@ -21,7 +21,9 @@ #include #include #include +#include #endif +#include #if defined(__APPLE__) #include @@ -242,16 +244,62 @@ bool Rename(const std::string &srcFilename, const std::string &destFilename) INFO_LOG(COMMON, "Rename: %s --> %s", srcFilename.c_str(), destFilename.c_str()); #ifdef _WIN32 - if (_trename(UTF8ToTStr(srcFilename).c_str(), UTF8ToTStr(destFilename).c_str()) == 0) + auto sf = UTF8ToTStr(srcFilename).c_str(); + auto df = UTF8ToTStr(destFilename).c_str(); + // The Internet seems torn about whether ReplaceFile is atomic or not. + // Hopefully it's atomic enough... + if (ReplaceFile(df, sf, NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) + return true; + // Might have failed because the destination doesn't exist. + if (GetLastError() == ERROR_FILE_NOT_FOUND) + { + if (MoveFile(sf, df)) + return true; + } #else if (rename(srcFilename.c_str(), destFilename.c_str()) == 0) -#endif return true; +#endif ERROR_LOG(COMMON, "Rename: failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); return false; } +#ifndef _WIN32 +static void FSyncPath(const char *path) +{ + int fd = open(path, O_RDONLY); + if (fd != -1) + { + fsync(fd); + close(fd); + } +} +#endif + +bool RenameSync(const std::string &srcFilename, const std::string &destFilename) +{ + if (!Rename(srcFilename, destFilename)) + return false; +#ifdef _WIN32 + int fd = _topen(UTF8ToTStr(srcFilename).c_str(), _O_RDONLY); + if (fd != -1) + { + _commit(fd); + close(fd); + } +#else + char *path = strdup(srcFilename.c_str()); + FSyncPath(path); + FSyncPath(dirname(path)); + free(path); + path = strdup(destFilename.c_str()); + FSyncPath(dirname(path)); + free(path); +#endif + return true; +} + // copies file srcFilename to destFilename, returns true on success bool Copy(const std::string &srcFilename, const std::string &destFilename) { @@ -627,6 +675,21 @@ bool SetCurrentDir(const std::string &directory) return __chdir(directory.c_str()) == 0; } +std::string GetTempFilenameForAtomicWrite(const std::string &path) +{ + std::string abs = path; +#ifdef _WIN32 + TCHAR absbuf[MAX_PATH]; + if (_tfullpath(absbuf, UTF8ToTStr(path).c_str(), MAX_PATH) != NULL) + abs = TStrToUTF8(absbuf); +#else + char absbuf[PATH_MAX]; + if (realpath(path.c_str(), absbuf) != NULL) + abs = absbuf; +#endif + return abs + ".xxx"; +} + #if defined(__APPLE__) std::string GetBundleDirectory() { diff --git a/Source/Core/Common/Src/FileUtil.h b/Source/Core/Common/Src/FileUtil.h index e3263ba233..8de525d64a 100644 --- a/Source/Core/Common/Src/FileUtil.h +++ b/Source/Core/Common/Src/FileUtil.h @@ -97,6 +97,9 @@ bool DeleteDir(const std::string &filename); // renames file srcFilename to destFilename, returns true on success bool Rename(const std::string &srcFilename, const std::string &destFilename); +// ditto, but syncs the source file and, on Unix, syncs the directories after rename +bool RenameSync(const std::string &srcFilename, const std::string &destFilename); + // copies file srcFilename to destFilename, returns true on success bool Copy(const std::string &srcFilename, const std::string &destFilename); @@ -119,6 +122,9 @@ void CopyDir(const std::string &source_path, const std::string &dest_path); // Set the current directory to given directory bool SetCurrentDir(const std::string &directory); +// Get a filename that can hopefully be atomically renamed to the given path. +std::string GetTempFilenameForAtomicWrite(const std::string &path); + // Returns a pointer to a string with a Dolphin data dir in the user's home // directory. To be used in "multi-user" mode (that is, installed). const std::string& GetUserPath(const unsigned int DirIDX, const std::string &newPath=""); diff --git a/Source/Core/Common/Src/IniFile.cpp b/Source/Core/Common/Src/IniFile.cpp index a86e101b01..fbce8254e3 100644 --- a/Source/Core/Common/Src/IniFile.cpp +++ b/Source/Core/Common/Src/IniFile.cpp @@ -391,7 +391,8 @@ bool IniFile::Load(const char* filename, bool keep_current_data) bool IniFile::Save(const char* filename) { std::ofstream out; - OpenFStream(out, filename, std::ios::out); + std::string temp = File::GetTempFilenameForAtomicWrite(filename); + OpenFStream(out, temp, std::ios::out); if (out.fail()) { @@ -425,7 +426,7 @@ bool IniFile::Save(const char* filename) out.close(); - return true; + return File::RenameSync(temp, filename); }