INISettingsInterface: Write changes atomically

This commit is contained in:
Connor McLaughlin 2022-10-10 23:36:07 +10:00 committed by refractionpcsx2
parent 7433cec2c4
commit 6f99ac676e
1 changed files with 48 additions and 1 deletions

View File

@ -21,6 +21,38 @@
#include "common/StringUtil.h"
#include <algorithm>
#include <iterator>
#include <mutex>
#ifdef _WIN32
#include <io.h> // _mktemp_s
#else
#include <stdlib.h> // mktemp
#endif
// To prevent races between saving and loading settings, particularly with game settings,
// we only allow one ini to be parsed at any point in time.
static std::mutex s_ini_load_save_mutex;
static std::string GetTemporaryFileName(const std::string& original_filename)
{
std::string temporary_filename;
temporary_filename.reserve(original_filename.length() + 8);
temporary_filename.append(original_filename);
#ifdef _WIN32
temporary_filename.append(".XXXXXXX");
_mktemp_s(temporary_filename.data(), temporary_filename.length() + 1);
#else
temporary_filename.append(".XXXXXX");
#if defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__)
mkstemp(temporary_filename.data());
#else
mktemp(temporary_filename.data());
#endif
#endif
return temporary_filename;
}
INISettingsInterface::INISettingsInterface(std::string filename)
: m_filename(std::move(filename))
@ -39,6 +71,7 @@ bool INISettingsInterface::Load()
if (m_filename.empty())
return false;
std::unique_lock lock(s_ini_load_save_mutex);
SI_Error err = SI_FAIL;
auto fp = FileSystem::OpenManagedCFile(m_filename.c_str(), "rb");
if (fp)
@ -52,12 +85,26 @@ bool INISettingsInterface::Save()
if (m_filename.empty())
return false;
std::unique_lock lock(s_ini_load_save_mutex);
std::string temp_filename(GetTemporaryFileName(m_filename));
SI_Error err = SI_FAIL;
std::FILE* fp = FileSystem::OpenCFile(m_filename.c_str(), "wb");
std::FILE* fp = FileSystem::OpenCFile(temp_filename.c_str(), "wb");
if (fp)
{
err = m_ini.SaveFile(fp, false);
std::fclose(fp);
if (err != SI_OK)
{
// remove temporary file
FileSystem::DeleteFilePath(temp_filename.c_str());
}
else if (!FileSystem::RenamePath(temp_filename.c_str(), m_filename.c_str()))
{
Console.Error("Failed to rename '%s' to '%s'", temp_filename.c_str(), m_filename.c_str());
FileSystem::DeleteFilePath(temp_filename.c_str());
return false;
}
}
if (err != SI_OK)