mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-17 22:25:37 +00:00
IniSettingsInterface: Make writes atomic
Fixes potential settings corruption if we crash while saving.
This commit is contained in:
parent
1db24e8014
commit
e2ecfa64e9
|
@ -1,16 +1,65 @@
|
||||||
#include "ini_settings_interface.h"
|
#include "ini_settings_interface.h"
|
||||||
#include "common/file_system.h"
|
#include "common/file_system.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
#include "common/path.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
Log_SetChannel(INISettingsInterface);
|
Log_SetChannel(INISettingsInterface);
|
||||||
|
|
||||||
|
#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,
|
// 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.
|
// we only allow one ini to be parsed at any point in time.
|
||||||
static std::mutex s_ini_load_save_mutex;
|
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);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// On UWP, preserve the extension, as it affects permissions.
|
||||||
|
#ifdef _UWP
|
||||||
|
const std::string_view original_view(original_filename);
|
||||||
|
const std::string_view extension(Path::GetExtension(original_view));
|
||||||
|
if (extension.length() < original_filename.length())
|
||||||
|
{
|
||||||
|
temporary_filename.append(original_view.substr(0, original_filename.length() - extension.length() - 1));
|
||||||
|
temporary_filename.append("_XXXXXX");
|
||||||
|
_mktemp_s(temporary_filename.data(), temporary_filename.length() + 1);
|
||||||
|
temporary_filename += '.';
|
||||||
|
temporary_filename.append(extension);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
temporary_filename.append(original_filename);
|
||||||
|
temporary_filename.append(".XXXXXXX");
|
||||||
|
_mktemp_s(temporary_filename.data(), temporary_filename.length() + 1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
temporary_filename.append(original_filename);
|
||||||
|
temporary_filename.append(".XXXXXXX");
|
||||||
|
_mktemp_s(temporary_filename.data(), temporary_filename.length() + 1);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
temporary_filename.append(original_filename);
|
||||||
|
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)), m_ini(true, true) {}
|
INISettingsInterface::INISettingsInterface(std::string filename) : m_filename(std::move(filename)), m_ini(true, true) {}
|
||||||
|
|
||||||
INISettingsInterface::~INISettingsInterface()
|
INISettingsInterface::~INISettingsInterface()
|
||||||
|
@ -39,12 +88,25 @@ bool INISettingsInterface::Save()
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::unique_lock lock(s_ini_load_save_mutex);
|
std::unique_lock lock(s_ini_load_save_mutex);
|
||||||
|
std::string temp_filename(GetTemporaryFileName(m_filename));
|
||||||
SI_Error err = SI_FAIL;
|
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)
|
if (fp)
|
||||||
{
|
{
|
||||||
err = m_ini.SaveFile(fp, false);
|
err = m_ini.SaveFile(fp, false);
|
||||||
std::fclose(fp);
|
std::fclose(fp);
|
||||||
|
|
||||||
|
if (err != SI_OK)
|
||||||
|
{
|
||||||
|
// remove temporary file
|
||||||
|
FileSystem::DeleteFile(temp_filename.c_str());
|
||||||
|
}
|
||||||
|
else if (!FileSystem::RenamePath(temp_filename.c_str(), m_filename.c_str()))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to rename '%s' to '%s'", temp_filename.c_str(), m_filename.c_str());
|
||||||
|
FileSystem::DeleteFile(temp_filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != SI_OK)
|
if (err != SI_OK)
|
||||||
|
|
Loading…
Reference in a new issue