Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 58 additions & 34 deletions src/lang/io/io.cc
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#include <sourcemeta/core/io.h>

#include <cerrno> // EACCES, errno
#include <cstddef> // std::byte
#include <filesystem> // std::filesystem
#include <fstream> // std::ofstream
#include <functional> // std::function
#include <ios> // std::ios::binary, std::ios::trunc, std::streamsize
#include <cassert> // assert
#include <cerrno> // EACCES, errno
#include <cstddef> // std::byte
#include <filesystem> // std::filesystem
#include <fstream> // std::ofstream
#include <functional> // std::function
#include <ios> // std::ios::binary, std::ios::trunc, std::ios::goodbit, std::streamsize
#include <ostream> // std::ostream
#include <span> // std::span
#include <string_view> // std::string_view
Expand All @@ -20,27 +21,52 @@

namespace {

auto open_file_for_write(const std::filesystem::path &path) -> std::ofstream {
if (path.has_parent_path()) {
std::filesystem::create_directories(path.parent_path());
class FileWriter {
public:
FileWriter(const std::filesystem::path &path) {
if (path.has_parent_path()) {
std::filesystem::create_directories(path.parent_path());
}

this->stream_.exceptions(std::ofstream::failbit | std::ofstream::badbit);
try {
this->stream_.open(path, std::ios::binary | std::ios::trunc);
} catch (...) {
// Capture before any other syscall can clobber errno
const auto open_errno{errno};
if (open_errno == EACCES) {
throw sourcemeta::core::IOFilePermissionError{path};
}

throw;
}
}

std::ofstream stream;
stream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
try {
stream.open(path, std::ios::binary | std::ios::trunc);
} catch (...) {
// Capture before any other syscall can clobber errno
const auto open_errno{errno};
if (open_errno == EACCES) {
throw sourcemeta::core::IOFilePermissionError{path};
~FileWriter() {
if (!this->committed_ && this->stream_.is_open()) {
this->stream_.exceptions(std::ios::goodbit);
this->stream_.close();
}
}

throw;
FileWriter(const FileWriter &) = delete;
FileWriter(FileWriter &&) = delete;
auto operator=(const FileWriter &) -> FileWriter & = delete;
auto operator=(FileWriter &&) -> FileWriter & = delete;

[[nodiscard]] auto stream() -> std::ostream & { return this->stream_; }

auto commit() -> void {
assert(!this->committed_);
this->stream_.flush();
this->stream_.close();
this->committed_ = true;
}

return stream;
}
private:
std::ofstream stream_;
bool committed_{false};
};

} // namespace

Expand Down Expand Up @@ -123,27 +149,25 @@ auto hardlink_directory(const std::filesystem::path &source,

auto write_file(const std::filesystem::path &path,
const std::string_view contents) -> void {
auto stream{open_file_for_write(path)};
stream.write(contents.data(), static_cast<std::streamsize>(contents.size()));
stream.flush();
stream.close();
FileWriter file{path};
file.stream().write(contents.data(),
static_cast<std::streamsize>(contents.size()));
file.commit();
}

auto write_file(const std::filesystem::path &path,
const std::span<const std::byte> contents) -> void {
auto stream{open_file_for_write(path)};
stream.write(reinterpret_cast<const char *>(contents.data()),
static_cast<std::streamsize>(contents.size()));
stream.flush();
stream.close();
FileWriter file{path};
file.stream().write(reinterpret_cast<const char *>(contents.data()),
static_cast<std::streamsize>(contents.size()));
file.commit();
}

auto write_file(const std::filesystem::path &path,
const std::function<void(std::ostream &)> &writer) -> void {
auto stream{open_file_for_write(path)};
writer(stream);
stream.flush();
stream.close();
FileWriter file{path};
writer(file.stream());
file.commit();
}

auto flush(const std::filesystem::path &path) -> void {
Expand Down
Loading