Skip to content

Commit

Permalink
IniSettingsInterface: Make writes atomic
Browse files Browse the repository at this point in the history
Fixes potential settings corruption if we crash while saving.
  • Loading branch information
stenzek committed Aug 10, 2022
1 parent 1db24e8 commit e2ecfa6
Showing 1 changed file with 63 additions and 1 deletion.
64 changes: 63 additions & 1 deletion src/util/ini_settings_interface.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,65 @@
#include "ini_settings_interface.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/path.h"
#include "common/string_util.h"
#include <algorithm>
#include <iterator>
#include <mutex>
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,
// 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);

#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()
Expand Down Expand Up @@ -39,12 +88,25 @@ bool INISettingsInterface::Save()
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::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)
Expand Down

0 comments on commit e2ecfa6

Please sign in to comment.