Everett
Loading...
Searching...
No Matches
object_writer.h
Go to the documentation of this file.
1
13#pragma once
14
15#include <everett/file.h>
16
17#include <algorithm>
18#include <array>
19#include <cerrno>
20#include <cstddef>
21#include <cstdint>
22#include <filesystem>
23#include <limits>
24#include <span>
25#include <string>
26#include <system_error>
27#include <utility>
28
29#if defined(__APPLE__) || defined(__linux__)
30#include <fcntl.h>
31#include <sys/stat.h>
32#include <unistd.h>
33#endif
34
35namespace everett {
36 template <class P, class Ops> struct object_stream;
37 // A caller-reserved, never-reused attempt identity, distinct from the opaque
38 // physical object identity. Neither identity is computed from content here.
40 explicit object_attempt_id(std::string hex) : value_(std::move(hex)) {}
41 std::string const & hex() const noexcept { return value_.hex(); }
42 bool operator==(object_attempt_id const &) const = default;
43 private:
45 };
46
52
54 std::filesystem::path final;
55 std::filesystem::path private_output;
56 };
57
58 inline object_write_paths object_output_paths(std::filesystem::path const & root,
59 object_id const & id, object_attempt_id const & attempt, file_kind kind) {
60 if (id.hex().size() != 32 || attempt.hex().size() != 32)
61 throw std::invalid_argument("invalid Everett object or attempt identity");
62 auto final = root / object_path(id, kind);
63 auto temporary = final.parent_path() /
64 ("." + final.filename().string() + ".attempt-" + attempt.hex());
65 return {std::move(final), std::move(temporary)};
66 }
67
68 // Retain these identities and any surviving names after an error. The stage
69 // records the last acknowledged operation, not the durable outcome. There is
70 // deliberately no retry, adopt-existing, cached-readback recovery or cleanup
71 // method. Recovery and durable identity reservation belong to the caller.
72 struct object_write_error : std::system_error {
73 object_write_error(int error, char const * operation, object_id object,
75 : std::system_error(error, std::generic_category(), operation),
76 object(std::move(object)), attempt(std::move(attempt)), paths(std::move(paths)),
82 char const * operation;
83 };
84
85 // Reports successful envelope writes and the requested OS persistence calls
86 // for this object and its names under root. Not a content address, catalog
87 // adoption, allocator reservation, recovery-root receipt or power-loss test.
96
97 // Bounded syscall seam, with POSIX return values and errno. Custom Ops use
98 // the same contract; methods must not throw. No filesystem recovery policy
99 // hides behind this interface. Windows and other platforms are unsupported.
101#if defined(__APPLE__) || defined(__linux__)
102 static constexpr bool supported = sizeof(off_t) >= sizeof(std::int64_t);
103 object_sync_barrier barrier() const noexcept {
104#if defined(__APPLE__)
106#else
108#endif
109 }
110 int open_root(std::filesystem::path const & path) noexcept {
111 return ::open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
112 }
113 int make_directory(int parent, char const * name) noexcept {
114 return ::mkdirat(parent, name, 0700);
115 }
116 int open_directory(int parent, char const * name) noexcept {
117 return ::openat(parent, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
118 }
119 int create_private(int parent, char const * name) noexcept {
120 return ::openat(parent, name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600);
121 }
122 std::ptrdiff_t write(int fd, std::span<std::byte const> bytes) noexcept {
123 return ::write(fd, bytes.data(), bytes.size());
124 }
125 std::ptrdiff_t write_at(int fd, std::span<std::byte const> bytes, std::uint64_t at) noexcept {
126 return ::pwrite(fd, bytes.data(), bytes.size(), static_cast<off_t>(at));
127 }
128 int make_read_only(int fd) noexcept { return ::fchmod(fd, 0400); }
129 int sync_file(int fd) noexcept {
130#if defined(__APPLE__)
131 // No fsync fallback: unsupported full barriers fail the attempt.
132 return ::fcntl(fd, F_FULLFSYNC);
133#else
134 return ::fsync(fd);
135#endif
136 }
137 int sync_directory(int fd) noexcept { return ::fsync(fd); }
138 int install(int parent, char const * from, char const * to) noexcept {
139 return ::linkat(parent, from, parent, to, 0);
140 }
141 int remove_private(int parent, char const * name) noexcept { return ::unlinkat(parent, name, 0); }
142 int close(int fd) noexcept { return ::close(fd); }
143#else
144 static constexpr bool supported = false;
145#endif
146 };
147
148 // Seal one supplied object into an existing, durably established root. The
149 // caller reserves both identities and retains verified input ownership until
150 // later catalog adoption/recovery. Input spans stay readable and immutable
151 // throughout this call (including mmap-backed bodies). Only 96 header bytes
152 // are buffered; chunks need not be contiguous and empty chunks are allowed.
153 //
154 // Root and shards must share one supported local filesystem/device. Root
155 // ancestry and that filesystem are trusted: no concurrent shard renames,
156 // mount changes, in-place object mutation or hostile directory manipulation.
157 // Component handles reject shard symlinks; they are not a sandbox against
158 // mutation of the supplied root's ancestors. The filesystem must support
159 // hard links and directory fsync. Sealed means our writer stops mutating it;
160 // read-only permissions do not prevent an owner from changing them later.
161 template <class P, class Ops = posix_object_ops> struct object_writer {
162 using policy_type = P;
163
164 static object_seal_receipt seal(std::filesystem::path const & root,
165 object_id const & id, object_attempt_id const & attempt, file_header<P> const & header,
166 std::span<std::span<std::byte const> const> chunks, Ops & ops) {
167 if constexpr (!Ops::supported) {
168 (void)root; (void)id; (void)attempt; (void)header; (void)chunks; (void)ops;
169 throw std::system_error(std::make_error_code(std::errc::operation_not_supported),
170 "Everett object writer is unsupported on this platform");
171 } else {
173 auto body_size = file_detail::body_bytes<P>(header.extent);
174 auto total_size = file_detail::total_bytes<P>(header.extent);
175 // POSIX file offsets are signed. The production implementations use
176 // 64-bit off_t; this stricter common bound also bounds write progress.
177 if (total_size > std::uint64_t(std::numeric_limits<std::int64_t>::max()))
178 throw std::length_error("Everett object exceeds supported file offsets");
179 std::uint64_t supplied = 0;
180 std::byte last{};
181 for (auto chunk : chunks) {
182 if (chunk.size() > body_size - supplied)
183 throw std::invalid_argument("Everett body extent mismatch");
184 supplied += chunk.size();
185 if (!chunk.empty()) last = chunk.back();
186 }
187 if (supplied != body_size) throw std::invalid_argument("Everett body extent mismatch");
188 if constexpr (P::unit == profile_unit::bit)
189 if ((header.extent & 7) && (std::to_integer<unsigned>(last) & ((1u << (8 - (header.extent & 7))) - 1)))
190 throw std::invalid_argument("noncanonical bit-profile tail padding");
191
192 operation run{root, id, attempt, header.kind, ops};
193 run.open();
194 std::array<std::byte, file_detail::header_bytes> placeholder{};
195 run.write_all(placeholder);
196 std::uint32_t crc = 0;
197 for (auto chunk : chunks) {
198 // Limit each CRC/write unit independently of the caller's span size.
199 // This is work streaming, not an allocated copy of the encoded body.
200 while (!chunk.empty()) {
201 auto part = chunk.first(std::min(chunk.size(), std::size_t{1} << 20));
202 run.write_all(part);
203 crc = crc32c<typename P::architecture>(part, crc);
204 chunk = chunk.subspan(part.size());
205 }
206 }
207 auto encoded_header = encode_file_header(header, crc);
208 run.write_header(encoded_header);
210 run.finish();
211 return {id, attempt, run.paths.final, total_size, crc, ops.barrier()};
212 }
213 }
214
215 static object_seal_receipt seal(std::filesystem::path const & root,
216 object_id const & id, object_attempt_id const & attempt, file_header<P> const & header,
217 std::span<std::span<std::byte const> const> chunks) {
218 Ops ops;
219 return seal(root, id, attempt, header, chunks, ops);
220 }
221 static object_seal_receipt seal(std::filesystem::path const & root,
222 object_id const & id, object_attempt_id const & attempt, file_header<P> const & header,
223 std::span<std::byte const> body, Ops & ops) {
224 return seal(root, id, attempt, header, std::span{&body, 1}, ops);
225 }
226 static object_seal_receipt seal(std::filesystem::path const & root,
227 object_id const & id, object_attempt_id const & attempt, file_header<P> const & header,
228 std::span<std::byte const> body) {
229 Ops ops;
230 return seal(root, id, attempt, header, body, ops);
231 }
232
233 private:
234 template <class, class> friend struct object_stream;
235 struct operation {
236 std::filesystem::path const & root;
237 object_id const & id;
239 Ops & ops;
242 std::array<int, 4> fds{-1, -1, -1, -1}; // root, first shard, leaf, object
244
245 operation(std::filesystem::path const & root, object_id const & id,
246 object_attempt_id const & attempt, file_kind kind, Ops & ops)
247 : root(root), id(id), attempt(attempt), ops(ops),
249 first(id.hex().substr(0, 2)), second(id.hex().substr(2, 2)),
250 final_name(paths.final.filename().string()), private_name(paths.private_output.filename().string()) {}
251 operation(operation const &) = delete;
252 operation & operator=(operation const &) = delete;
254 // Error paths close handles once but never unlink outputs or retry any
255 // failed I/O. Preserve the first failure; close can itself lose an ack.
256 for (auto it = fds.rbegin(); it != fds.rend(); ++it)
257 if (*it >= 0) (void)ops.close(std::exchange(*it, -1));
258 }
259 [[noreturn]] void fail(char const * what, int error = errno) const {
260 throw object_write_error(error ? error : EIO, what, id, attempt, paths, stage);
261 }
262 void open() {
263 fds[0] = ops.open_root(root);
264 if (fds[0] < 0) fail("open object root");
265 for (unsigned i = 1; i <= 2; ++i) {
266 auto const & name = i == 1 ? first : second;
267 if (ops.make_directory(fds[i - 1], name.c_str()) < 0 && errno != EEXIST)
268 fail("create object shard");
269 fds[i] = ops.open_directory(fds[i - 1], name.c_str());
270 if (fds[i] < 0) fail("open object shard");
271 }
272 fds[3] = ops.create_private(fds[2], private_name.c_str());
273 if (fds[3] < 0) fail("create private object");
275 }
276 void write_all(std::span<std::byte const> bytes) {
277 while (!bytes.empty()) {
278 auto part = bytes.first(std::min(bytes.size(), std::size_t{1} << 20));
279 auto count = ops.write(fds[3], part);
280 if (count < 0) { if (errno == EINTR) continue; fail("write object body"); }
281 if (count == 0 || std::uint64_t(count) > part.size()) fail("write object body", EIO);
282 bytes = bytes.subspan(static_cast<std::size_t>(count));
283 }
284 }
285 void write_header(std::span<std::byte const> bytes) {
286 write_at(bytes, 0, "write object header");
287 }
288 void write_at(std::span<std::byte const> bytes, std::uint64_t offset, char const * operation) {
289 while (!bytes.empty()) {
290 auto part = bytes.first(std::min(bytes.size(), std::size_t{1} << 20));
291 auto count = ops.write_at(fds[3], part, offset);
292 if (count < 0) { if (errno == EINTR) continue; fail(operation); }
293 if (count == 0 || std::uint64_t(count) > part.size()) fail(operation, EIO);
294 offset += static_cast<std::uint64_t>(count);
295 bytes = bytes.subspan(static_cast<std::size_t>(count));
296 }
297 }
298 void finish() {
299 if (ops.make_read_only(fds[3]) < 0) fail("protect object");
300 // No retry after synchronization failure, even EINTR.
301 if (ops.sync_file(fds[3]) < 0) fail("sync object contents");
303 if (ops.install(fds[2], private_name.c_str(), final_name.c_str()) < 0)
304 fail("install object without replacement");
306 // Sync existing ancestors too: another legitimate creator may have
307 // just made them. Root's own durable parent entry is a caller premise.
308 for (unsigned i = 3; i-- > 0;)
309 if (ops.sync_directory(fds[i]) < 0) fail("sync object directory");
311 if (ops.remove_private(fds[2], private_name.c_str()) < 0) fail("remove private object name");
313 if (ops.sync_directory(fds[2]) < 0) fail("sync private name removal");
314 // Also persists link-count changes; on Apple this full drive barrier
315 // follows the directory writeback requests. No unsupported fallback.
316 if (ops.sync_file(fds[3]) < 0) fail("sync installed object");
317 for (unsigned i = 4; i-- > 0;) {
318 auto fd = std::exchange(fds[i], -1);
319 if (ops.close(fd) < 0) fail("close sealed object handles");
320 }
322 }
323 };
324 };
325}
Declares Everett's file support.
void validate_metadata(file_header< P > const &header)
Definition file.h:93
Definition active_engine.h:18
object_write_stage
Definition object_writer.h:48
object_write_paths object_output_paths(std::filesystem::path const &root, object_id const &id, object_attempt_id const &attempt, file_kind kind)
Definition object_writer.h:58
std::array< std::byte, file_detail::header_bytes > encode_file_header(file_header< P > const &header, std::uint32_t body_crc)
Definition file.h:194
object_sync_barrier
Definition object_writer.h:47
std::filesystem::path object_path(object_id const &id, file_kind kind)
Definition object_path.h:52
file_kind
Definition object_path.h:25
Definition file.h:34
file_kind kind
Definition file.h:36
std::uint64_t extent
Definition file.h:37
Definition object_writer.h:39
std::string const & hex() const noexcept
Definition object_writer.h:41
object_attempt_id(std::string hex)
Definition object_writer.h:40
bool operator==(object_attempt_id const &) const =default
object_id value_
Definition object_writer.h:44
Definition object_path.h:38
std::string const & hex() const noexcept
Definition object_path.h:46
Definition object_writer.h:88
object_attempt_id attempt
Definition object_writer.h:90
std::filesystem::path path
Definition object_writer.h:91
object_id object
Definition object_writer.h:89
std::uint32_t body_crc32c
Definition object_writer.h:93
std::uint64_t bytes
Definition object_writer.h:92
object_sync_barrier barrier
Definition object_writer.h:94
Definition object_stream.h:32
Definition object_writer.h:72
object_attempt_id attempt
Definition object_writer.h:79
char const * operation
Definition object_writer.h:82
object_id object
Definition object_writer.h:78
object_write_stage stage
Definition object_writer.h:81
object_write_paths paths
Definition object_writer.h:80
object_write_error(int error, char const *operation, object_id object, object_attempt_id attempt, object_write_paths paths, object_write_stage stage)
Definition object_writer.h:73
Definition object_writer.h:53
std::filesystem::path private_output
Definition object_writer.h:55
Definition object_writer.h:235
operation(operation const &)=delete
void write_all(std::span< std::byte const > bytes)
Definition object_writer.h:276
object_write_stage stage
Definition object_writer.h:243
void finish()
Definition object_writer.h:298
operation & operator=(operation const &)=delete
std::string private_name
Definition object_writer.h:241
void write_header(std::span< std::byte const > bytes)
Definition object_writer.h:285
object_write_paths paths
Definition object_writer.h:240
object_attempt_id const & attempt
Definition object_writer.h:238
void fail(char const *what, int error=errno) const
Definition object_writer.h:259
void write_at(std::span< std::byte const > bytes, std::uint64_t offset, char const *operation)
Definition object_writer.h:288
std::array< int, 4 > fds
Definition object_writer.h:242
void open()
Definition object_writer.h:262
~operation()
Definition object_writer.h:253
operation(std::filesystem::path const &root, object_id const &id, object_attempt_id const &attempt, file_kind kind, Ops &ops)
Definition object_writer.h:245
std::string final_name
Definition object_writer.h:241
std::string second
Definition object_writer.h:241
std::filesystem::path const & root
Definition object_writer.h:236
Ops & ops
Definition object_writer.h:239
std::string first
Definition object_writer.h:241
object_id const & id
Definition object_writer.h:237
Definition object_writer.h:161
static object_seal_receipt seal(std::filesystem::path const &root, object_id const &id, object_attempt_id const &attempt, file_header< P > const &header, std::span< std::span< std::byte const > const > chunks, Ops &ops)
Definition object_writer.h:164
static object_seal_receipt seal(std::filesystem::path const &root, object_id const &id, object_attempt_id const &attempt, file_header< P > const &header, std::span< std::byte const > body)
Definition object_writer.h:226
static object_seal_receipt seal(std::filesystem::path const &root, object_id const &id, object_attempt_id const &attempt, file_header< P > const &header, std::span< std::span< std::byte const > const > chunks)
Definition object_writer.h:215
static object_seal_receipt seal(std::filesystem::path const &root, object_id const &id, object_attempt_id const &attempt, file_header< P > const &header, std::span< std::byte const > body, Ops &ops)
Definition object_writer.h:221
P policy_type
Definition object_writer.h:162
Definition object_writer.h:100
static constexpr bool supported
Definition object_writer.h:144