Pixie
Loading...
Searching...
No Matches
file_output_sink.h
1#pragma once
2
3#if !defined(__unix__) && !defined(__APPLE__)
4#error "pixie/io/file_output_sink.h requires POSIX file support"
5#endif
6
7#include <fcntl.h>
8#include <unistd.h>
9
10#include <algorithm>
11#include <cerrno>
12#include <cstddef>
13#include <cstdint>
14#include <filesystem>
15#include <limits>
16#include <span>
17#include <stdexcept>
18#include <system_error>
19#include <utility>
20
21namespace pixie::io {
22
31 public:
36 explicit FileOutputSink(const std::filesystem::path& path) {
37 descriptor_ = ::open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
38 if (descriptor_ == -1) {
39 throw std::system_error(errno, std::generic_category(),
40 "Failed to open binary output file");
41 }
42 }
43
44 FileOutputSink(const FileOutputSink&) = delete;
45 FileOutputSink& operator=(const FileOutputSink&) = delete;
46
49 : descriptor_(std::exchange(other.descriptor_, -1)),
50 size_bytes_(std::exchange(other.size_bytes_, 0)) {}
51
54 if (this != &other) {
55 close_noexcept();
56 descriptor_ = std::exchange(other.descriptor_, -1);
57 size_bytes_ = std::exchange(other.size_bytes_, 0);
58 }
59 return *this;
60 }
61
63 ~FileOutputSink() { close_noexcept(); }
64
66 std::size_t size_bytes() const noexcept { return size_bytes_; }
67
74 void write(std::span<const std::byte> bytes) {
75 require_open();
76 require_file_range(size_bytes_, bytes.size());
77 while (!bytes.empty()) {
78 const std::size_t chunk = std::min(
79 bytes.size(),
80 static_cast<std::size_t>(std::numeric_limits<ssize_t>::max()));
81 const ssize_t written = ::write(descriptor_, bytes.data(), chunk);
82 if (written == -1) {
83 if (errno == EINTR) {
84 continue;
85 }
86 throw std::system_error(errno, std::generic_category(),
87 "Failed to write binary output file");
88 }
89 if (written == 0) {
90 throw std::system_error(EIO, std::generic_category(),
91 "Binary output file write made no progress");
92 }
93 const std::size_t count = static_cast<std::size_t>(written);
94 size_bytes_ += count;
95 bytes = bytes.subspan(count);
96 }
97 }
98
106 void write_at(std::size_t position, std::span<const std::byte> bytes) {
107 require_open();
108 if (position > size_bytes_ || bytes.size() > size_bytes_ - position) {
109 throw std::out_of_range("Binary output patch is outside the file");
110 }
111 require_file_range(position, bytes.size());
112 while (!bytes.empty()) {
113 const std::size_t chunk = std::min(
114 bytes.size(),
115 static_cast<std::size_t>(std::numeric_limits<ssize_t>::max()));
116 const off_t offset = static_cast<off_t>(position);
117 const ssize_t written =
118 ::pwrite(descriptor_, bytes.data(), chunk, offset);
119 if (written == -1) {
120 if (errno == EINTR) {
121 continue;
122 }
123 throw std::system_error(errno, std::generic_category(),
124 "Failed to patch binary output file");
125 }
126 if (written == 0) {
127 throw std::system_error(EIO, std::generic_category(),
128 "Binary output file patch made no progress");
129 }
130 const std::size_t count = static_cast<std::size_t>(written);
131 position += count;
132 bytes = bytes.subspan(count);
133 }
134 }
135
141 void finish() {
142 if (descriptor_ == -1) {
143 return;
144 }
145 const int descriptor = std::exchange(descriptor_, -1);
146 if (::close(descriptor) == -1) {
147 throw std::system_error(errno, std::generic_category(),
148 "Failed to close binary output file");
149 }
150 }
151
152 private:
153 static void require_file_range(std::size_t position, std::size_t count) {
154 constexpr auto kMaximumOffset = std::numeric_limits<off_t>::max();
155 const auto maximum = static_cast<std::uintmax_t>(kMaximumOffset);
156 if (position > maximum || count > maximum - position) {
157 throw std::length_error("Binary output file offset is too large");
158 }
159 }
160
161 void require_open() const {
162 if (descriptor_ == -1) {
163 throw std::logic_error("Binary output file is already finished");
164 }
165 }
166
167 void close_noexcept() noexcept {
168 if (descriptor_ != -1) {
169 const int descriptor = std::exchange(descriptor_, -1);
170 (void)::close(descriptor);
171 }
172 }
173
174 int descriptor_ = -1;
175 std::size_t size_bytes_ = 0;
176};
177
178} // namespace pixie::io
Move-only seekable sink for a newly created or truncated POSIX file.
Definition file_output_sink.h:30
std::size_t size_bytes() const noexcept
Return the number of bytes appended to the output.
Definition file_output_sink.h:66
FileOutputSink(const std::filesystem::path &path)
Open path for binary output, creating or truncating it.
Definition file_output_sink.h:36
void write_at(std::size_t position, std::span< const std::byte > bytes)
Replace already-written bytes beginning at position.
Definition file_output_sink.h:106
FileOutputSink & operator=(FileOutputSink &&other) noexcept
Close the current file and transfer ownership from other.
Definition file_output_sink.h:53
~FileOutputSink()
Close the output file without reporting errors.
Definition file_output_sink.h:63
FileOutputSink(FileOutputSink &&other) noexcept
Transfer ownership of an open output file.
Definition file_output_sink.h:48
void finish()
Close the file and report any close error.
Definition file_output_sink.h:141
void write(std::span< const std::byte > bytes)
Append all bytes to the file.
Definition file_output_sink.h:74