Try to atomically save config files.

This commit is contained in:
comex 2013-10-15 02:52:06 -04:00
parent f3af8ee0f0
commit 72c1e143f3
3 changed files with 74 additions and 4 deletions

View File

@ -21,7 +21,9 @@
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <libgen.h>
#endif
#include <fcntl.h>
#if defined(__APPLE__)
#include <CoreFoundation/CFString.h>
@ -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()
{

View File

@ -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="");

View File

@ -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);
}