Files
FESADev/src/fesa/io/hdf5/hdf5_atomic_file.cpp
T

89 lines
2.8 KiB
C++

#define NOMINMAX
#include "io/hdf5/hdf5_atomic_file.h"
#include <Windows.h>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>
#include <utility>
#include "io/hdf5/hdf5_primitives.h"
#include "io/hdf5/hdf5_raii.h"
namespace fesa::hdf5_internal {
namespace {
bool AtomicReplace(const std::filesystem::path& candidate_path,
const std::filesystem::path& output_path) {
const DWORD attributes = GetFileAttributesW(output_path.c_str());
if (attributes != INVALID_FILE_ATTRIBUTES) {
return ReplaceFileW(output_path.c_str(), candidate_path.c_str(), nullptr,
0U, nullptr, nullptr) != FALSE;
}
const DWORD error = GetLastError();
if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) {
return false;
}
return MoveFileExW(candidate_path.c_str(), output_path.c_str(),
MOVEFILE_WRITE_THROUGH) != FALSE;
}
} // namespace
CandidateFileGuard::CandidateFileGuard(std::filesystem::path path)
: path_{std::move(path)} {}
CandidateFileGuard::~CandidateFileGuard() {
if (active_) {
std::error_code ignored;
(void)std::filesystem::remove(path_, ignored);
}
}
void CandidateFileGuard::Release() noexcept { active_ = false; }
std::filesystem::path MakeCandidatePath(
const std::filesystem::path& output_path) {
static std::atomic<std::uint64_t> sequence{0U};
const std::filesystem::path parent = output_path.parent_path();
for (std::size_t attempt = 0U; attempt < 1024U; ++attempt) {
const std::wstring filename =
L"." + output_path.filename().wstring() + L".tmp." +
std::to_wstring(GetCurrentProcessId()) + L"." +
std::to_wstring(sequence.fetch_add(1U));
const std::filesystem::path candidate = parent / filename;
std::error_code error;
const bool exists = std::filesystem::exists(candidate, error);
if (error) {
throw Hdf5Failure{"Unable to inspect the temporary output path."};
}
if (!exists) {
return candidate;
}
}
throw Hdf5Failure{"Unable to allocate a unique temporary output path."};
}
Status AtomicFinalizeValidatedCandidate(
const std::filesystem::path& candidate_path,
const std::filesystem::path& output_path, const Status& validation) {
if (!validation.IsOk()) {
std::error_code ignored;
(void)std::filesystem::remove(candidate_path, ignored);
return validation;
}
if (!AtomicReplace(candidate_path, output_path)) {
std::error_code ignored;
(void)std::filesystem::remove(candidate_path, ignored);
return OutputFailure("hdf5-finalization-failure",
"The checked temporary HDF5 file could not replace "
"the final output.");
}
return Status::Ok();
}
} // namespace fesa::hdf5_internal