18#include <system_error>
53 explicit AtomicOutputFile(
const std::filesystem::path& destination,
bool binary =
false)
54 : destination_(destination) {
55 namespace fs = std::filesystem;
59 if (fs::is_symlink(destination_, ec)) {
60 const auto resolved = fs::canonical(destination_, ec);
62 fail(
"resolve destination", ec);
65 destination_ = resolved;
66 }
else if (ec && ec != std::errc::no_such_file_or_directory) {
67 fail(
"inspect destination", ec);
71 const auto status = fs::status(destination_, ec);
72 const bool exists = fs::exists(status);
73 if (ec && ec != std::errc::no_such_file_or_directory) {
74 fail(
"inspect destination", ec);
77 if (exists && !fs::is_regular_file(status)) {
78 fail(
"replace a non-regular destination",
79 std::make_error_code(std::errc::invalid_argument));
83 fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write;
84 if (exists && (status.permissions() & writable) == fs::perms::none) {
85 fail(
"write a read-only destination",
86 std::make_error_code(std::errc::permission_denied));
89 static std::atomic<unsigned long long> sequence{0};
90 const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
91 for (
int attempt = 0; attempt < 32; ++attempt) {
92 const auto candidate =
93 destination_.parent_path() /
94 (
".openswmm-save-" + std::to_string(stamp) +
"-" + std::to_string(sequence++));
95 file_ =
fopen_path(candidate, binary ?
"wbx" :
"wx");
104 fail(
"create temporary output", systemError());
108 fs::permissions(staging_, status.permissions(), ec);
110 fail(
"preserve destination permissions", ec);
145 const std::string&
error()
const {
return error_; }
160 bool ok = std::ferror(file_) == 0;
162 fail(
"write output", systemError());
163 if (std::fflush(file_) != 0) {
165 fail(
"flush output", systemError());
170 const int sync = ::_commit(::_fileno(file_));
174 sync = ::fsync(::fileno(file_));
175 }
while (sync != 0 && errno == EINTR);
178 fail(
"sync output", systemError());
182 if (std::fclose(file_) != 0) {
184 fail(
"close output", systemError());
191 std::filesystem::rename(staging_, destination_, ec);
193 fail(
"publish output", ec);
207 static std::error_code systemError() {
208 return errno ? std::error_code(errno, std::generic_category())
209 : std::make_error_code(std::errc::io_error);
220 void fail(
const char* operation,
const std::error_code& ec) {
221 error_ =
"Cannot " + std::string(operation) +
" '" +
path_utf8(destination_) +
222 "': " + ec.message();
231 void closeAndDiscard() {
236 if (!staging_.empty()) {
238 std::filesystem::remove(staging_, ec);
241 std::filesystem::path destination_, staging_;
242 FILE* file_ =
nullptr;
Opening files by UTF-8 path on every platform.
AtomicOutputFile & operator=(const AtomicOutputFile &)=delete
Disallow copy replacement of a pending file publication.
FILE * stream() const
Borrow the writable staging stream.
Definition AtomicOutputFile.hpp:139
AtomicOutputFile(const std::filesystem::path &destination, bool binary=false)
Create an exclusive staging sibling for a single output destination.
Definition AtomicOutputFile.hpp:53
~AtomicOutputFile()
Close any open staging stream and best-effort discard unpublished output.
Definition AtomicOutputFile.hpp:122
AtomicOutputFile(const AtomicOutputFile &)=delete
Disallow copying ownership of the staging stream and publication state.
const std::string & error() const
Borrow the latest stored file-operation diagnostic.
Definition AtomicOutputFile.hpp:145
bool commit()
Flush, sync, close and attempt to rename staging over the destination.
Definition AtomicOutputFile.hpp:157
Definition AtomicOutputFile.hpp:25
std::string path_utf8(const std::filesystem::path &p, bool generic=false)
Copy a native path into the engine byte-string representation.
Definition FileIO.hpp:99
std::FILE * fopen_path(const std::filesystem::path &p, const char *mode)
Open a borrowed native path using platform C stdio.
Definition FileIO.hpp:135