Common/FileUtil: Add CreateTempFileForAtomicWrite function.

This commit is contained in:
Jordan Woyak 2025-11-07 21:25:42 -06:00
parent 267e081592
commit c84f78f058
3 changed files with 47 additions and 2 deletions

View File

@ -25,6 +25,7 @@
#include "Common/CommonFuncs.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/DirectIOFile.h"
#ifdef __APPLE__
#include "Common/DynamicLibrary.h"
#endif
@ -668,13 +669,42 @@ std::string CreateTempDir()
#endif
}
std::string GetTempFilenameForAtomicWrite(std::string path)
static auto TryToGetAbsolutePath(std::string path)
{
std::error_code error;
auto absolute_path = fs::absolute(StringToPath(path), error);
if (!error)
path = PathToString(absolute_path);
return std::move(path) + ".xxx";
return path;
}
std::string GetTempFilenameForAtomicWrite(std::string path)
{
return TryToGetAbsolutePath(std::move(path)) + ".xxx";
}
std::string CreateTempFileForAtomicWrite(std::string path)
{
path = TryToGetAbsolutePath(std::move(path));
while (true)
{
DirectIOFile file;
// e.g. "/dir/file.txt" -> "/dir/file.txt.189234789.tmp"
const auto timestamp = Clock::now().time_since_epoch().count();
std::string tmp_path = fmt::format("{}.{}.tmp", path, timestamp);
const auto open_result = file.Open(tmp_path, AccessMode::Write, OpenMode::Create);
if (open_result.Succeeded())
return tmp_path;
// In the very unlikely case that the file already exists, we will try again.
if (open_result.Error() == File::OpenError::AlreadyExists)
continue;
// Failure.
return {};
}
}
#if defined(__APPLE__)

View File

@ -223,6 +223,10 @@ std::string CreateTempDir();
// Get a filename that can hopefully be atomically renamed to the given path.
std::string GetTempFilenameForAtomicWrite(std::string path);
// Creates and returns the path to a newly created temporary file next to the given path.
// Returns an empty string on error, generally caused by lack of write permissions.
std::string CreateTempFileForAtomicWrite(std::string path);
// Gets a set user directory path
// Don't call prior to setting the base user directory
const std::string& GetUserPath(unsigned int dir_index);

View File

@ -154,6 +154,17 @@ TEST_F(FileUtilTest, CreateFullPath)
EXPECT_TRUE(File::IsFile(p3file));
}
TEST_F(FileUtilTest, CreateTempFileForAtomicWrite)
{
EXPECT_TRUE(File::Exists(File::CreateTempFileForAtomicWrite(m_file_path)));
#if defined(_WIN32)
EXPECT_FALSE(File::Exists(File::CreateTempFileForAtomicWrite("C:/con/cant_write_here.txt")));
#else
EXPECT_FALSE(File::Exists(File::CreateTempFileForAtomicWrite("/dev/null/cant_write_here.txt")));
#endif
}
TEST_F(FileUtilTest, DirectIOFile)
{
static constexpr std::array<u8, 3> u8_test_data = {42, 7, 99};