yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
source_artifact_publisher.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <atomic>
6#include <cctype>
7#include <cerrno>
8#include <chrono>
9#include <cstdint>
10#include <cstring>
11#include <exception>
12#include <filesystem>
13#include <fstream>
14#include <iterator>
15#include <limits>
16#include <memory>
17#include <optional>
18#include <semaphore>
19#include <string>
20#include <string_view>
21#include <system_error>
22#include <utility>
23#include <vector>
24
25#include "absl/status/status.h"
26#include "absl/strings/ascii.h"
27#include "absl/strings/str_cat.h"
28#include "absl/strings/str_format.h"
29#include "util/macro.h"
30
31#if defined(_WIN32)
32#ifndef NOMINMAX
33#define NOMINMAX
34#endif
35#include <windows.h>
36#elif !defined(__EMSCRIPTEN__)
37#include <fcntl.h>
38#include <sys/file.h>
39#include <sys/stat.h>
40#include <unistd.h>
41#endif
42
43namespace yaze::core {
44namespace {
45
46namespace fs = std::filesystem;
47
48std::string Capitalized(std::string value) {
49 if (!value.empty()) {
50 value.front() = static_cast<char>(
51 std::toupper(static_cast<unsigned char>(value.front())));
52 }
53 return value;
54}
55
57 fs::path target;
58 std::string content;
59 std::optional<std::string> original_content;
60 fs::path temp;
61 std::optional<fs::path> backup;
62 bool published = false;
63};
64
65std::binary_semaphore& SourceArtifactWriteSemaphore() {
66 static std::binary_semaphore semaphore{1};
67 return semaphore;
68}
69
71 public:
72 static absl::StatusOr<std::unique_ptr<SourceArtifactWriteLocks>> Acquire(
73 const std::vector<fs::path>& lock_paths,
74 const SourceArtifactPublisherLabels& labels) {
75 if (lock_paths.empty()) {
76 return absl::InvalidArgumentError(
77 absl::StrFormat("%s publication requires at least one lock",
78 Capitalized(labels.subject)));
79 }
80 auto lock = std::unique_ptr<SourceArtifactWriteLocks>(
83 lock->process_lock_acquired_ = true;
84
85#if defined(_WIN32)
86 lock->handles_.reserve(lock_paths.size());
87 struct WindowsFileIdentity {
88 DWORD volume_serial;
89 DWORD file_index_high;
90 DWORD file_index_low;
91 };
92 std::vector<WindowsFileIdentity> lock_identities;
93 lock_identities.reserve(lock_paths.size());
94 for (const fs::path& path : lock_paths) {
95 HANDLE handle = CreateFileW(
96 path.wstring().c_str(), GENERIC_READ | GENERIC_WRITE,
97 FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS,
98 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
99 if (handle == INVALID_HANDLE_VALUE) {
100 const DWORD error = GetLastError();
101 return absl::PermissionDeniedError(absl::StrFormat(
102 "Cannot open %s lock %s: %s", labels.subject, path.string(),
103 std::error_code(static_cast<int>(error), std::system_category())
104 .message()));
105 }
106 BY_HANDLE_FILE_INFORMATION file_info = {};
107 if (!GetFileInformationByHandle(handle, &file_info)) {
108 const DWORD error = GetLastError();
109 CloseHandle(handle);
110 return absl::FailedPreconditionError(absl::StrFormat(
111 "Cannot inspect %s lock %s: %s", labels.subject, path.string(),
112 std::error_code(static_cast<int>(error), std::system_category())
113 .message()));
114 }
115 if ((file_info.dwFileAttributes &
116 (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) != 0) {
117 CloseHandle(handle);
118 return absl::FailedPreconditionError(
119 absl::StrFormat("%s lock must be a regular file: %s",
120 Capitalized(labels.subject), path.string()));
121 }
122 if (file_info.nNumberOfLinks != 1) {
123 CloseHandle(handle);
124 return absl::FailedPreconditionError(
125 absl::StrFormat("%s lock must have exactly one hard link: %s",
126 Capitalized(labels.subject), path.string()));
127 }
128 const WindowsFileIdentity identity = {file_info.dwVolumeSerialNumber,
129 file_info.nFileIndexHigh,
130 file_info.nFileIndexLow};
131 const bool duplicate_identity = std::any_of(
132 lock_identities.begin(), lock_identities.end(),
133 [&](const WindowsFileIdentity& existing) {
134 return existing.volume_serial == identity.volume_serial &&
135 existing.file_index_high == identity.file_index_high &&
136 existing.file_index_low == identity.file_index_low;
137 });
138 if (duplicate_identity) {
139 CloseHandle(handle);
140 return absl::InvalidArgumentError(
141 absl::StrFormat("%s lock aliases another lock path: %s",
142 Capitalized(labels.subject), path.string()));
143 }
144 OVERLAPPED overlapped = {};
145 if (!LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD,
146 &overlapped)) {
147 const DWORD error = GetLastError();
148 CloseHandle(handle);
149 return absl::UnavailableError(absl::StrFormat(
150 "Cannot acquire %s lock %s: %s", labels.subject, path.string(),
151 std::error_code(static_cast<int>(error), std::system_category())
152 .message()));
153 }
154 lock_identities.push_back(identity);
155 lock->handles_.push_back(handle);
156 }
157#elif defined(__EMSCRIPTEN__)
158 return absl::FailedPreconditionError(absl::StrFormat(
159 "Durable %s locking is unavailable in browser builds", labels.subject));
160#else
161 lock->fds_.reserve(lock_paths.size());
162 struct PosixFileIdentity {
163 dev_t device;
164 ino_t inode;
165 };
166 std::vector<PosixFileIdentity> lock_identities;
167 lock_identities.reserve(lock_paths.size());
168 for (const fs::path& path : lock_paths) {
169 int open_flags = O_RDWR | O_CREAT;
170#ifdef O_CLOEXEC
171 open_flags |= O_CLOEXEC;
172#endif
173#ifdef O_NOFOLLOW
174 open_flags |= O_NOFOLLOW;
175#endif
176 const int fd = open(path.c_str(), open_flags, S_IRUSR | S_IWUSR);
177 if (fd < 0) {
178 return absl::PermissionDeniedError(absl::StrFormat(
179 "Cannot open %s lock %s: %s", labels.subject, path.string(),
180 std::error_code(errno, std::generic_category()).message()));
181 }
182 struct stat lock_stat{};
183 if (fstat(fd, &lock_stat) != 0) {
184 const int error = errno;
185 close(fd);
186 return absl::FailedPreconditionError(absl::StrFormat(
187 "Cannot inspect %s lock %s: %s", labels.subject, path.string(),
188 std::error_code(error, std::generic_category()).message()));
189 }
190 if (!S_ISREG(lock_stat.st_mode)) {
191 close(fd);
192 return absl::FailedPreconditionError(
193 absl::StrFormat("%s lock must be a regular file: %s",
194 Capitalized(labels.subject), path.string()));
195 }
196 if (lock_stat.st_nlink != 1) {
197 close(fd);
198 return absl::FailedPreconditionError(
199 absl::StrFormat("%s lock must have exactly one hard link: %s",
200 Capitalized(labels.subject), path.string()));
201 }
202 const PosixFileIdentity identity = {lock_stat.st_dev, lock_stat.st_ino};
203 const bool duplicate_identity =
204 std::any_of(lock_identities.begin(), lock_identities.end(),
205 [&](const PosixFileIdentity& existing) {
206 return existing.device == identity.device &&
207 existing.inode == identity.inode;
208 });
209 if (duplicate_identity) {
210 close(fd);
211 return absl::InvalidArgumentError(
212 absl::StrFormat("%s lock aliases another lock path: %s",
213 Capitalized(labels.subject), path.string()));
214 }
215 if (fchmod(fd, S_IRUSR | S_IWUSR) != 0) {
216 const int error = errno;
217 close(fd);
218 return absl::PermissionDeniedError(absl::StrFormat(
219 "Cannot secure %s lock %s: %s", labels.subject, path.string(),
220 std::error_code(error, std::generic_category()).message()));
221 }
222 while (flock(fd, LOCK_EX) != 0) {
223 if (errno == EINTR) {
224 continue;
225 }
226 const int error = errno;
227 close(fd);
228 return absl::UnavailableError(absl::StrFormat(
229 "Cannot acquire %s lock %s: %s", labels.subject, path.string(),
230 std::error_code(error, std::generic_category()).message()));
231 }
232 lock_identities.push_back(identity);
233 lock->fds_.push_back(fd);
234 }
235#endif
236 return lock;
237 }
238
240#if defined(_WIN32)
241 for (auto it = handles_.rbegin(); it != handles_.rend(); ++it) {
242 OVERLAPPED overlapped = {};
243 UnlockFileEx(*it, 0, MAXDWORD, MAXDWORD, &overlapped);
244 CloseHandle(*it);
245 }
246#elif !defined(__EMSCRIPTEN__)
247 for (auto it = fds_.rbegin(); it != fds_.rend(); ++it) {
248 while (flock(*it, LOCK_UN) != 0 && errno == EINTR) {}
249 close(*it);
250 }
251#endif
252 if (process_lock_acquired_) {
254 }
255 }
256
259
260 private:
262
263 bool process_lock_acquired_ = false;
264#if defined(_WIN32)
265 std::vector<HANDLE> handles_;
266#elif !defined(__EMSCRIPTEN__)
267 std::vector<int> fds_;
268#endif
269};
270
271constexpr uint32_t kSha256Constants[64] = {
272 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
273 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
274 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
275 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
276 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
277 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
278 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
279 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
280 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
281 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
282 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
283
284constexpr uint32_t RotateRight(uint32_t value, uint32_t count) {
285 return (value >> count) | (value << (32 - count));
286}
287
288void TransformSha256(uint32_t state[8], const uint8_t block[64]) {
289 uint32_t schedule[64];
290 for (int index = 0; index < 16; ++index) {
291 schedule[index] = (static_cast<uint32_t>(block[index * 4]) << 24) |
292 (static_cast<uint32_t>(block[index * 4 + 1]) << 16) |
293 (static_cast<uint32_t>(block[index * 4 + 2]) << 8) |
294 static_cast<uint32_t>(block[index * 4 + 3]);
295 }
296 for (int index = 16; index < 64; ++index) {
297 const uint32_t low = RotateRight(schedule[index - 15], 7) ^
298 RotateRight(schedule[index - 15], 18) ^
299 (schedule[index - 15] >> 3);
300 const uint32_t high = RotateRight(schedule[index - 2], 17) ^
301 RotateRight(schedule[index - 2], 19) ^
302 (schedule[index - 2] >> 10);
303 schedule[index] = schedule[index - 16] + low + schedule[index - 7] + high;
304 }
305
306 uint32_t a = state[0];
307 uint32_t b = state[1];
308 uint32_t c = state[2];
309 uint32_t d = state[3];
310 uint32_t e = state[4];
311 uint32_t f = state[5];
312 uint32_t g = state[6];
313 uint32_t h = state[7];
314 for (int index = 0; index < 64; ++index) {
315 const uint32_t sigma1 =
316 RotateRight(e, 6) ^ RotateRight(e, 11) ^ RotateRight(e, 25);
317 const uint32_t choose = (e & f) ^ ((~e) & g);
318 const uint32_t temp1 =
319 h + sigma1 + choose + kSha256Constants[index] + schedule[index];
320 const uint32_t sigma0 =
321 RotateRight(a, 2) ^ RotateRight(a, 13) ^ RotateRight(a, 22);
322 const uint32_t majority = (a & b) ^ (a & c) ^ (b & c);
323 const uint32_t temp2 = sigma0 + majority;
324 h = g;
325 g = f;
326 f = e;
327 e = d + temp1;
328 d = c;
329 c = b;
330 b = a;
331 a = temp1 + temp2;
332 }
333
334 state[0] += a;
335 state[1] += b;
336 state[2] += c;
337 state[3] += d;
338 state[4] += e;
339 state[5] += f;
340 state[6] += g;
341 state[7] += h;
342}
343
344std::string Sha256Hex(std::string_view content) {
345 uint32_t state[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
346 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
347 const auto* cursor = reinterpret_cast<const uint8_t*>(content.data());
348 size_t remaining = content.size();
349 while (remaining >= 64) {
350 TransformSha256(state, cursor);
351 cursor += 64;
352 remaining -= 64;
353 }
354
355 uint8_t block[64] = {};
356 if (remaining > 0) {
357 std::memcpy(block, cursor, remaining);
358 }
359 block[remaining] = 0x80;
360 if (remaining >= 56) {
361 TransformSha256(state, block);
362 std::memset(block, 0, sizeof(block));
363 }
364 const uint64_t bit_length = static_cast<uint64_t>(content.size()) * 8;
365 for (int index = 0; index < 8; ++index) {
366 block[63 - index] =
367 static_cast<uint8_t>(bit_length >> (static_cast<uint64_t>(index) * 8));
368 }
369 TransformSha256(state, block);
370
371 std::string digest;
372 digest.reserve(64);
373 for (uint32_t value : state) {
374 absl::StrAppend(&digest, absl::StrFormat("%08x", value));
375 }
376 return digest;
377}
378
379absl::StatusOr<std::string> ReadTextFile(const fs::path& path,
380 absl::string_view label) {
381 std::ifstream input(path, std::ios::binary);
382 if (!input.is_open()) {
383 return absl::NotFoundError(
384 absl::StrFormat("Cannot open %s: %s", label, path.string()));
385 }
386 std::string content{std::istreambuf_iterator<char>(input),
387 std::istreambuf_iterator<char>()};
388 if (!input.good() && !input.eof()) {
389 return absl::DataLossError(
390 absl::StrFormat("Failed while reading %s: %s", label, path.string()));
391 }
392 return content;
393}
394
395std::string LowercasePath(const fs::path& path) {
396 return absl::AsciiStrToLower(path.generic_string());
397}
398
399absl::StatusOr<fs::path> ResolvePublicationTarget(
400 const fs::path& target, const SourceArtifactPublisherLabels& labels) {
401 if (target.empty() || !target.is_absolute()) {
402 return absl::InvalidArgumentError(
403 absl::StrFormat("%s target must be an absolute path: %s",
404 Capitalized(labels.subject), target.string()));
405 }
406
407 std::error_code status_ec;
408 const fs::file_status link_status = fs::symlink_status(target, status_ec);
409 if (status_ec != std::errc::no_such_file_or_directory && status_ec) {
410 return absl::FailedPreconditionError(
411 absl::StrFormat("Cannot inspect %s target %s: %s", labels.subject,
412 target.string(), status_ec.message()));
413 }
414 if (!status_ec && fs::is_symlink(link_status)) {
415 return absl::FailedPreconditionError(
416 absl::StrFormat("%s target may not be a symbolic link: %s",
417 Capitalized(labels.subject), target.string()));
418 }
419
420 std::error_code canonical_ec;
421 fs::path resolved = fs::weakly_canonical(target, canonical_ec);
422 if (canonical_ec) {
423 return absl::FailedPreconditionError(
424 absl::StrFormat("Cannot resolve %s target %s: %s", labels.subject,
425 target.string(), canonical_ec.message()));
426 }
427 return resolved.lexically_normal();
428}
429
430absl::StatusOr<std::vector<fs::path>> ResolvePublicationTargets(
431 const std::vector<fs::path>& targets,
432 const SourceArtifactPublisherLabels& labels) {
433 if (targets.empty()) {
434 return absl::InvalidArgumentError(
435 absl::StrFormat("%s publication requires at least one target",
436 Capitalized(labels.subject)));
437 }
438 std::vector<fs::path> resolved_targets;
439 resolved_targets.reserve(targets.size());
440 for (const fs::path& target : targets) {
441 fs::path resolved;
442 ASSIGN_OR_RETURN(resolved, ResolvePublicationTarget(target, labels));
443 resolved_targets.push_back(std::move(resolved));
444 }
445 return resolved_targets;
446}
447
449 const std::vector<fs::path>& targets,
450 const SourceArtifactPublisherLabels& labels) {
451 for (size_t left_index = 0; left_index < targets.size(); ++left_index) {
452 for (size_t right_index = left_index + 1; right_index < targets.size();
453 ++right_index) {
454 const fs::path& left = targets[left_index];
455 const fs::path& right = targets[right_index];
456 if (left == right || LowercasePath(left) == LowercasePath(right)) {
457 return absl::InvalidArgumentError(absl::StrFormat(
458 "%s publication targets must be distinct: %s and %s",
459 Capitalized(labels.subject), left.string(), right.string()));
460 }
461
462 std::error_code left_exists_ec;
463 const bool left_exists = fs::exists(left, left_exists_ec);
464 std::error_code right_exists_ec;
465 const bool right_exists = fs::exists(right, right_exists_ec);
466 if (left_exists_ec || right_exists_ec) {
467 return absl::FailedPreconditionError(absl::StrFormat(
468 "Cannot inspect %s publication targets %s and %s: %s",
469 labels.subject, left.string(), right.string(),
470 left_exists_ec ? left_exists_ec.message()
471 : right_exists_ec.message()));
472 }
473 if (!left_exists || !right_exists) {
474 continue;
475 }
476
477 std::error_code equivalent_ec;
478 const bool equivalent = fs::equivalent(left, right, equivalent_ec);
479 if (equivalent_ec) {
480 return absl::FailedPreconditionError(absl::StrFormat(
481 "Cannot compare %s publication targets %s and %s: %s",
482 labels.subject, left.string(), right.string(),
483 equivalent_ec.message()));
484 }
485 if (equivalent) {
486 return absl::InvalidArgumentError(absl::StrFormat(
487 "%s publication targets alias each other: %s and %s",
488 Capitalized(labels.subject), left.string(), right.string()));
489 }
490 }
491 }
492 return absl::OkStatus();
493}
494
495absl::StatusOr<std::vector<fs::path>> PublicationLockPaths(
496 const std::vector<fs::path>& targets,
497 const SourceArtifactPublisherLabels& labels) {
498 if (targets.empty()) {
499 return absl::InvalidArgumentError(
500 absl::StrFormat("%s publication requires at least one target",
501 Capitalized(labels.subject)));
502 }
503 std::vector<fs::path> lock_paths;
504 for (const fs::path& target : targets) {
505 std::error_code canonical_ec;
506 const fs::path parent = fs::canonical(target.parent_path(), canonical_ec);
507 if (canonical_ec) {
508 return absl::FailedPreconditionError(absl::StrFormat(
509 "Cannot resolve %s target directory %s: %s", labels.subject,
510 target.parent_path().string(), canonical_ec.message()));
511 }
512
513 bool duplicate = false;
514 for (const fs::path& existing_lock : lock_paths) {
515 std::error_code equivalent_ec;
516 const bool equivalent =
517 fs::equivalent(parent, existing_lock.parent_path(), equivalent_ec);
518 if (equivalent_ec) {
519 return absl::FailedPreconditionError(absl::StrFormat(
520 "Cannot compare %s target directories %s and %s: %s",
521 labels.subject, parent.string(),
522 existing_lock.parent_path().string(), equivalent_ec.message()));
523 }
524 if (equivalent) {
525 duplicate = true;
526 break;
527 }
528 }
529 if (!duplicate) {
530 lock_paths.push_back(parent / kSourceArtifactPublicationLockBasename);
531 }
532 }
533
534 std::sort(lock_paths.begin(), lock_paths.end(),
535 [](const fs::path& left, const fs::path& right) {
536 const std::string left_lower = LowercasePath(left);
537 const std::string right_lower = LowercasePath(right);
538 return left_lower == right_lower
539 ? left.generic_string() < right.generic_string()
540 : left_lower < right_lower;
541 });
542 return lock_paths;
543}
544
546 const std::vector<fs::path>& lock_paths,
547 const std::vector<fs::path>& targets,
548 const SourceArtifactPublisherLabels& labels) {
549 for (const fs::path& lock_path : lock_paths) {
550 for (const fs::path& target : targets) {
551 if (target == lock_path ||
552 LowercasePath(target) == LowercasePath(lock_path)) {
553 return absl::InvalidArgumentError(
554 absl::StrFormat("%s targets may not use a persistent lock path: %s",
555 Capitalized(labels.subject), lock_path.string()));
556 }
557 }
558
559 std::error_code lock_exists_ec;
560 const bool lock_exists = fs::exists(lock_path, lock_exists_ec);
561 if (lock_exists_ec) {
562 return absl::FailedPreconditionError(absl::StrFormat(
563 "Cannot inspect persistent %s lock %s: %s", labels.subject,
564 lock_path.string(), lock_exists_ec.message()));
565 }
566 if (!lock_exists) {
567 continue;
568 }
569
570 for (const fs::path& target : targets) {
571 std::error_code target_exists_ec;
572 const bool target_exists = fs::exists(target, target_exists_ec);
573 if (target_exists_ec) {
574 return absl::FailedPreconditionError(
575 absl::StrFormat("Cannot inspect %s target %s: %s", labels.subject,
576 target.string(), target_exists_ec.message()));
577 }
578 if (!target_exists) {
579 continue;
580 }
581 std::error_code equivalent_ec;
582 const bool equivalent = fs::equivalent(lock_path, target, equivalent_ec);
583 if (equivalent_ec) {
584 return absl::FailedPreconditionError(absl::StrFormat(
585 "Cannot compare %s target %s with persistent lock %s: %s",
586 labels.subject, target.string(), lock_path.string(),
587 equivalent_ec.message()));
588 }
589 if (equivalent) {
590 return absl::InvalidArgumentError(
591 absl::StrFormat("%s target aliases a persistent lock path: %s",
592 Capitalized(labels.subject), target.string()));
593 }
594 }
595 }
596 return absl::OkStatus();
597}
598
599absl::StatusOr<std::vector<fs::path>> PreparePublicationTargets(
600 const std::vector<fs::path>& targets,
601 const SourceArtifactPublisherLabels& labels) {
602 std::vector<fs::path> resolved_targets;
603 ASSIGN_OR_RETURN(resolved_targets,
604 ResolvePublicationTargets(targets, labels));
605 RETURN_IF_ERROR(ValidateDistinctPublicationTargets(resolved_targets, labels));
606
607 std::vector<fs::path> lock_paths;
608 ASSIGN_OR_RETURN(lock_paths, PublicationLockPaths(resolved_targets, labels));
610 ValidateTargetsDoNotAliasLocks(lock_paths, resolved_targets, labels));
611 return resolved_targets;
612}
613
614fs::path NextSiblingPath(const fs::path& target, absl::string_view purpose) {
615 static std::atomic<uint64_t> sequence{0};
616 const uint64_t tick = static_cast<uint64_t>(
617 std::chrono::steady_clock::now().time_since_epoch().count());
618 const uint64_t id = sequence.fetch_add(1, std::memory_order_relaxed);
619 fs::path name = target.filename();
620 name += absl::StrFormat(".yaze-%s-%016x-%016x", purpose, tick, id);
621 return target.parent_path() / name;
622}
623
624absl::StatusOr<fs::path> WriteExclusiveTemp(const fs::path& target,
625 std::string_view content) {
626 constexpr int kMaxAttempts = 100;
627#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
628 mode_t create_mode =
629 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
630 bool preserve_mode = false;
631 struct stat target_stat{};
632 if (stat(target.c_str(), &target_stat) == 0) {
633 if (!S_ISREG(target_stat.st_mode)) {
634 return absl::FailedPreconditionError(absl::StrFormat(
635 "Publication target must be a regular file: %s", target.string()));
636 }
637 create_mode = target_stat.st_mode & 07777;
638 preserve_mode = true;
639 } else if (errno != ENOENT) {
640 return absl::FailedPreconditionError(absl::StrFormat(
641 "Cannot inspect publication target mode %s: %s", target.string(),
642 std::error_code(errno, std::generic_category()).message()));
643 }
644#endif
645 for (int attempt = 0; attempt < kMaxAttempts; ++attempt) {
646 const fs::path temp = NextSiblingPath(target, "tmp");
647#if defined(_WIN32)
648 HANDLE file = CreateFileW(
649 temp.wstring().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW,
650 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, nullptr);
651 if (file == INVALID_HANDLE_VALUE) {
652 const DWORD error = GetLastError();
653 if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS) {
654 continue;
655 }
656 return absl::PermissionDeniedError(absl::StrFormat(
657 "Cannot create temporary file for %s: %s", target.string(),
658 std::error_code(static_cast<int>(error), std::system_category())
659 .message()));
660 }
661 size_t written_total = 0;
662 while (written_total < content.size()) {
663 const DWORD chunk = static_cast<DWORD>(std::min<size_t>(
664 content.size() - written_total, std::numeric_limits<DWORD>::max()));
665 DWORD written = 0;
666 if (!WriteFile(file, content.data() + written_total, chunk, &written,
667 nullptr) ||
668 written == 0) {
669 const DWORD error = GetLastError();
670 CloseHandle(file);
671 std::error_code cleanup_ec;
672 fs::remove(temp, cleanup_ec);
673 return absl::InternalError(absl::StrFormat(
674 "Failed to write temporary file for %s: %s", target.string(),
675 std::error_code(static_cast<int>(error), std::system_category())
676 .message()));
677 }
678 written_total += written;
679 }
680 if (!FlushFileBuffers(file)) {
681 const DWORD error = GetLastError();
682 CloseHandle(file);
683 std::error_code cleanup_ec;
684 fs::remove(temp, cleanup_ec);
685 return absl::InternalError(absl::StrFormat(
686 "Failed to flush temporary file for %s: %s", target.string(),
687 std::error_code(static_cast<int>(error), std::system_category())
688 .message()));
689 }
690 if (!CloseHandle(file)) {
691 const DWORD error = GetLastError();
692 std::error_code cleanup_ec;
693 fs::remove(temp, cleanup_ec);
694 return absl::InternalError(absl::StrFormat(
695 "Failed to flush temporary file for %s: %s", target.string(),
696 std::error_code(static_cast<int>(error), std::system_category())
697 .message()));
698 }
699#elif defined(__EMSCRIPTEN__)
700 std::ofstream output(temp, std::ios::binary | std::ios::trunc);
701 if (!output.is_open()) {
702 return absl::PermissionDeniedError(absl::StrFormat(
703 "Cannot create temporary file for %s", target.string()));
704 }
705 output.write(content.data(), static_cast<std::streamsize>(content.size()));
706 if (!output.good()) {
707 output.close();
708 std::error_code cleanup_ec;
709 fs::remove(temp, cleanup_ec);
710 return absl::InternalError(absl::StrFormat(
711 "Failed to write temporary file for %s", target.string()));
712 }
713 output.close();
714#else
715 const int fd = open(temp.c_str(), O_WRONLY | O_CREAT | O_EXCL, create_mode);
716 if (fd < 0) {
717 if (errno == EEXIST) {
718 continue;
719 }
720 return absl::PermissionDeniedError(absl::StrFormat(
721 "Cannot create temporary file for %s: %s", target.string(),
722 std::error_code(errno, std::generic_category()).message()));
723 }
724 if (preserve_mode && fchmod(fd, create_mode) != 0) {
725 const int error = errno;
726 close(fd);
727 std::error_code cleanup_ec;
728 fs::remove(temp, cleanup_ec);
729 return absl::InternalError(absl::StrFormat(
730 "Cannot preserve publication target mode for %s: %s", target.string(),
731 std::error_code(error, std::generic_category()).message()));
732 }
733 size_t written_total = 0;
734 while (written_total < content.size()) {
735 const ssize_t written =
736 write(fd, content.data() + written_total,
737 std::min<size_t>(content.size() - written_total, 1024 * 1024));
738 if (written < 0 && errno == EINTR) {
739 continue;
740 }
741 if (written <= 0) {
742 const int error = written < 0 ? errno : EIO;
743 close(fd);
744 std::error_code cleanup_ec;
745 fs::remove(temp, cleanup_ec);
746 return absl::InternalError(absl::StrFormat(
747 "Failed to write temporary file for %s: %s", target.string(),
748 std::error_code(error, std::generic_category()).message()));
749 }
750 written_total += static_cast<size_t>(written);
751 }
752 if (fsync(fd) != 0) {
753 const int error = errno;
754 close(fd);
755 std::error_code cleanup_ec;
756 fs::remove(temp, cleanup_ec);
757 return absl::InternalError(absl::StrFormat(
758 "Failed to flush temporary file for %s: %s", target.string(),
759 std::error_code(error, std::generic_category()).message()));
760 }
761 if (close(fd) != 0) {
762 const int error = errno;
763 std::error_code cleanup_ec;
764 fs::remove(temp, cleanup_ec);
765 return absl::InternalError(absl::StrFormat(
766 "Failed to flush temporary file for %s: %s", target.string(),
767 std::error_code(error, std::generic_category()).message()));
768 }
769#endif
770 return temp;
771 }
772 return absl::ResourceExhaustedError(absl::StrFormat(
773 "Could not allocate a unique temporary file for %s", target.string()));
774}
775
776absl::Status SyncParentDirectory(const fs::path& target) {
777#if defined(_WIN32) || defined(__EMSCRIPTEN__)
778 return absl::OkStatus();
779#else
780 int open_flags = O_RDONLY;
781#ifdef O_CLOEXEC
782 open_flags |= O_CLOEXEC;
783#endif
784#ifdef O_DIRECTORY
785 open_flags |= O_DIRECTORY;
786#endif
787 const fs::path parent = target.parent_path();
788 const int fd = open(parent.c_str(), open_flags);
789 if (fd < 0) {
790 return absl::InternalError(absl::StrFormat(
791 "Cannot open parent directory for durable publication %s: %s",
792 parent.string(),
793 std::error_code(errno, std::generic_category()).message()));
794 }
795 int sync_result = 0;
796 do {
797 sync_result = fsync(fd);
798 } while (sync_result != 0 && errno == EINTR);
799 const int sync_error = sync_result == 0 ? 0 : errno;
800 const int close_result = close(fd);
801 const int close_error = close_result == 0 ? 0 : errno;
802 if (sync_error != 0) {
803 return absl::InternalError(absl::StrFormat(
804 "Cannot sync parent directory after publishing %s: %s", target.string(),
805 std::error_code(sync_error, std::generic_category()).message()));
806 }
807 if (close_result != 0) {
808 return absl::InternalError(absl::StrFormat(
809 "Cannot close parent directory after publishing %s: %s",
810 target.string(),
811 std::error_code(close_error, std::generic_category()).message()));
812 }
813 return absl::OkStatus();
814#endif
815}
816
817absl::Status ReplaceFromTemp(const fs::path& temp, const fs::path& target,
818 bool* replaced = nullptr) {
819 if (replaced != nullptr) {
820 *replaced = false;
821 }
822#if defined(_WIN32)
823 if (!MoveFileExW(temp.wstring().c_str(), target.wstring().c_str(),
824 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
825 const DWORD error = GetLastError();
826 return absl::InternalError(absl::StrFormat(
827 "Failed to publish %s: %s", target.string(),
828 std::error_code(static_cast<int>(error), std::system_category())
829 .message()));
830 }
831 if (replaced != nullptr) {
832 *replaced = true;
833 }
834#else
835 std::error_code rename_ec;
836 fs::rename(temp, target, rename_ec);
837 if (rename_ec) {
838 return absl::InternalError(absl::StrFormat(
839 "Failed to publish %s: %s", target.string(), rename_ec.message()));
840 }
841 if (replaced != nullptr) {
842 *replaced = true;
843 }
844#endif
845 return SyncParentDirectory(target);
846}
847
848absl::StatusOr<fs::path> CreateBackup(const fs::path& target) {
849 constexpr int kMaxAttempts = 100;
850 for (int attempt = 0; attempt < kMaxAttempts; ++attempt) {
851 const fs::path backup = NextSiblingPath(target, "backup");
852 std::error_code copy_ec;
853 fs::copy_file(target, backup, fs::copy_options::none, copy_ec);
854 if (!copy_ec) {
855 return backup;
856 }
857 if (copy_ec != std::errc::file_exists) {
858 std::error_code cleanup_ec;
859 fs::remove(backup, cleanup_ec);
860 if (cleanup_ec) {
861 return absl::DataLossError(absl::StrFormat(
862 "Cannot back up %s (%s) or remove incomplete backup %s (%s)",
863 target.string(), copy_ec.message(), backup.string(),
864 cleanup_ec.message()));
865 }
866 return absl::FailedPreconditionError(absl::StrFormat(
867 "Cannot back up %s: %s", target.string(), copy_ec.message()));
868 }
869 }
870 return absl::ResourceExhaustedError(absl::StrFormat(
871 "Could not allocate a backup path for %s", target.string()));
872}
873
875 const std::vector<fs::path>& paths, absl::string_view artifact_kind,
876 const SourceArtifactPublisherLabels& labels) {
877 std::string failures;
878 for (const fs::path& path : paths) {
879 if (path.empty()) {
880 continue;
881 }
882 std::error_code ec;
883 fs::remove(path, ec);
884 if (!ec) {
885 continue;
886 }
887 if (!failures.empty()) {
888 absl::StrAppend(&failures, "; ");
889 }
890 absl::StrAppend(&failures, path.string(), " (", ec.message(), ")");
891 }
892 if (!failures.empty()) {
893 return absl::InternalError(absl::StrFormat(
894 "Could not remove %s %s: %s", labels.subject, artifact_kind, failures));
895 }
896 return absl::OkStatus();
897}
898
899absl::Status CleanupTemps(const std::vector<PublicationArtifact>& artifacts,
900 const SourceArtifactPublisherLabels& labels) {
901 std::vector<fs::path> paths;
902 paths.reserve(artifacts.size());
903 for (const auto& artifact : artifacts) {
904 if (!artifact.temp.empty()) {
905 paths.push_back(artifact.temp);
906 }
907 }
908 return CleanupPublicationPaths(paths, "temporary files", labels);
909}
910
911absl::Status CleanupBackups(std::vector<PublicationArtifact>* artifacts,
912 const SourceArtifactPublisherLabels& labels) {
913 std::string failures;
914 for (auto& artifact : *artifacts) {
915 if (!artifact.backup.has_value()) {
916 continue;
917 }
918 std::error_code ec;
919 fs::remove(*artifact.backup, ec);
920 if (!ec) {
921 artifact.backup.reset();
922 continue;
923 }
924 if (!failures.empty()) {
925 absl::StrAppend(&failures, "; ");
926 }
927 absl::StrAppend(&failures, artifact.backup->string(), " (", ec.message(),
928 ")");
929 }
930 if (!failures.empty()) {
931 return absl::InternalError(absl::StrFormat(
932 "Could not remove %s backup files: %s", labels.subject, failures));
933 }
934 return absl::OkStatus();
935}
936
938 const std::vector<PublicationArtifact>& artifacts) {
939 std::string paths;
940 for (const auto& artifact : artifacts) {
941 if (!artifact.backup.has_value()) {
942 continue;
943 }
944 if (!paths.empty()) {
945 absl::StrAppend(&paths, ", ");
946 }
947 absl::StrAppend(&paths, artifact.backup->string());
948 }
949 return paths.empty() ? "<none>" : paths;
950}
951
952std::string CleanupStatusSummary(const absl::Status& status) {
953 return status.ok() ? "ok" : std::string(status.message());
954}
955
957 std::vector<PublicationArtifact>* artifacts,
958 const absl::Status& publication_failure,
959 const SourceArtifactPublisherLabels& labels) {
960 const absl::Status temp_cleanup = CleanupTemps(*artifacts, labels);
961 const absl::Status backup_cleanup = CleanupBackups(artifacts, labels);
962 if (temp_cleanup.ok() && backup_cleanup.ok()) {
963 return publication_failure;
964 }
965 return absl::DataLossError(absl::StrFormat(
966 "%s publication failed (%s), then artifact cleanup failed "
967 "(temporary files: %s; backup files: %s)",
968 Capitalized(labels.subject), publication_failure.message(),
969 CleanupStatusSummary(temp_cleanup),
970 CleanupStatusSummary(backup_cleanup)));
971}
972
973absl::Status RestoreArtifact(const PublicationArtifact& artifact) {
974 if (artifact.original_content.has_value()) {
975 fs::path restore_temp;
977 restore_temp,
978 WriteExclusiveTemp(artifact.target, *artifact.original_content));
979 const absl::Status status = ReplaceFromTemp(restore_temp, artifact.target);
980 if (!status.ok()) {
981 std::error_code cleanup_ec;
982 fs::remove(restore_temp, cleanup_ec);
983 if (cleanup_ec) {
984 return absl::DataLossError(absl::StrFormat(
985 "Could not restore %s (%s) or remove restore file %s (%s)",
986 artifact.target.string(), status.message(), restore_temp.string(),
987 cleanup_ec.message()));
988 }
989 }
990 return status;
991 }
992 std::error_code remove_ec;
993 const bool removed = fs::remove(artifact.target, remove_ec);
994 if (remove_ec || !removed) {
995 return absl::InternalError(absl::StrFormat(
996 "Could not remove newly published file %s during rollback: %s",
997 artifact.target.string(),
998 remove_ec ? remove_ec.message() : "file was missing"));
999 }
1000 return SyncParentDirectory(artifact.target);
1001}
1002
1003absl::Status RollBackPublication(std::vector<PublicationArtifact>* artifacts,
1004 const absl::Status& publication_failure,
1005 const SourceArtifactPublisherLabels& labels) {
1006 absl::Status rollback_failure = absl::OkStatus();
1007 for (auto it = artifacts->rbegin(); it != artifacts->rend(); ++it) {
1008 if (!it->published) {
1009 continue;
1010 }
1011 const absl::Status restore_status = RestoreArtifact(*it);
1012 if (!restore_status.ok() && rollback_failure.ok()) {
1013 rollback_failure = restore_status;
1014 }
1015 it->published = false;
1016 }
1017 const absl::Status temp_cleanup = CleanupTemps(*artifacts, labels);
1018 if (!rollback_failure.ok()) {
1019 return absl::DataLossError(absl::StrFormat(
1020 "%s publication failed (%s) and rollback failed (%s). "
1021 "Temporary-file cleanup: %s. Preserved recovery backups: %s",
1022 Capitalized(labels.subject), publication_failure.message(),
1023 rollback_failure.message(), CleanupStatusSummary(temp_cleanup),
1024 BackupRecoveryPaths(*artifacts)));
1025 }
1026 const absl::Status backup_cleanup = CleanupBackups(artifacts, labels);
1027 if (!temp_cleanup.ok() || !backup_cleanup.ok()) {
1028 return absl::DataLossError(absl::StrFormat(
1029 "%s publication failed (%s); rollback completed, but "
1030 "artifact cleanup failed (temporary files: %s; backup files: %s)",
1031 Capitalized(labels.subject), publication_failure.message(),
1032 CleanupStatusSummary(temp_cleanup),
1033 CleanupStatusSummary(backup_cleanup)));
1034 }
1035 return publication_failure;
1036}
1037
1039 const std::vector<PublicationArtifact>& artifacts,
1040 std::string_view expected_source_sha256,
1041 const SourceArtifactPublisherLabels& labels) {
1042 for (const auto& artifact : artifacts) {
1043 std::error_code exists_ec;
1044 const bool exists = fs::exists(artifact.target, exists_ec);
1045 if (exists_ec) {
1046 return absl::FailedPreconditionError(
1047 absl::StrFormat("Cannot recheck publication target %s: %s",
1048 artifact.target.string(), exists_ec.message()));
1049 }
1050 if (artifact.original_content.has_value()) {
1051 if (!exists) {
1052 return absl::AbortedError(absl::StrFormat(
1053 "Publication target disappeared after preflight: %s",
1054 artifact.target.string()));
1055 }
1056 std::string current;
1057 ASSIGN_OR_RETURN(current,
1058 ReadTextFile(artifact.target, "publication target"));
1059 if (current != *artifact.original_content) {
1060 return absl::AbortedError(
1061 absl::StrFormat("Publication target changed after preflight: %s",
1062 artifact.target.string()));
1063 }
1064 } else if (exists) {
1065 return absl::AbortedError(
1066 absl::StrFormat("Publication target appeared after preflight: %s",
1067 artifact.target.string()));
1068 }
1069 }
1070 if (Sha256Hex(*artifacts.front().original_content) !=
1071 expected_source_sha256) {
1072 return absl::AbortedError(absl::StrFormat(
1073 "Canonical %s changed after SHA-256 preflight", labels.subject));
1074 }
1075 return absl::OkStatus();
1076}
1077
1078absl::Status PublishArtifactSet(std::vector<PublicationArtifact>* artifacts,
1079 std::string_view expected_source_sha256,
1080 const SourceArtifactPublisherLabels& labels) {
1081 for (auto& artifact : *artifacts) {
1082 auto temp_or = WriteExclusiveTemp(artifact.target, artifact.content);
1083 if (!temp_or.ok()) {
1084 return ReturnFailureAfterCleanup(artifacts, temp_or.status(), labels);
1085 }
1086 artifact.temp = std::move(*temp_or);
1087 if (artifact.original_content.has_value()) {
1088 auto backup_or = CreateBackup(artifact.target);
1089 if (!backup_or.ok()) {
1090 return ReturnFailureAfterCleanup(artifacts, backup_or.status(), labels);
1091 }
1092 artifact.backup = std::move(*backup_or);
1093 }
1094 }
1095
1096 const absl::Status unchanged_status = VerifyUnchangedBeforePublication(
1097 *artifacts, expected_source_sha256, labels);
1098 if (!unchanged_status.ok()) {
1099 return ReturnFailureAfterCleanup(artifacts, unchanged_status, labels);
1100 }
1101
1102 for (auto& artifact : *artifacts) {
1103 bool replaced = false;
1104 const absl::Status replace_status =
1105 ReplaceFromTemp(artifact.temp, artifact.target, &replaced);
1106 artifact.published = replaced;
1107 if (!replace_status.ok()) {
1108 return RollBackPublication(artifacts, replace_status, labels);
1109 }
1110 artifact.temp.clear();
1111 }
1112
1113 for (const auto& artifact : *artifacts) {
1114 std::string reopened;
1115 auto reopened_or = ReadTextFile(artifact.target, labels.published_file);
1116 if (!reopened_or.ok()) {
1117 return RollBackPublication(artifacts, reopened_or.status(), labels);
1118 }
1119 reopened = std::move(*reopened_or);
1120 if (reopened != artifact.content) {
1121 return RollBackPublication(
1122 artifacts,
1123 absl::DataLossError(absl::StrFormat(
1124 "%s failed exact readback: %s",
1125 Capitalized(labels.published_file), artifact.target.string())),
1126 labels);
1127 }
1128 }
1129 const absl::Status temp_cleanup = CleanupTemps(*artifacts, labels);
1130 if (!temp_cleanup.ok()) {
1131 return RollBackPublication(artifacts, temp_cleanup, labels);
1132 }
1133 return absl::OkStatus();
1134}
1135
1136} // namespace
1137
1140 std::vector<fs::path> targets;
1141 std::unique_ptr<SourceArtifactWriteLocks> write_locks;
1142 std::atomic_flag publication_in_progress = ATOMIC_FLAG_INIT;
1143};
1144
1145namespace {
1146
1148 public:
1150 std::atomic_flag* publication_in_progress) noexcept
1151 : publication_in_progress_(publication_in_progress) {}
1152
1154 publication_in_progress_->clear(std::memory_order_release);
1155 }
1156
1159
1160 private:
1161 std::atomic_flag* publication_in_progress_;
1162};
1163
1164} // namespace
1165
1167 std::unique_ptr<Impl> impl)
1168 : impl_(std::move(impl)) {}
1169
1171
1173 const std::vector<fs::path>& targets,
1174 const SourceArtifactPublisherLabels& labels) {
1175 if (labels.subject.empty() || labels.published_file.empty()) {
1176 return absl::InvalidArgumentError(
1177 "Source artifact diagnostic labels must not be empty");
1178 }
1179 return PreparePublicationTargets(targets, labels).status();
1180}
1181
1182absl::StatusOr<std::unique_ptr<SourceArtifactPublicationLock>>
1184 const std::vector<fs::path>& targets,
1185 const SourceArtifactPublisherLabels& labels) {
1186 if (labels.subject.empty() || labels.published_file.empty()) {
1187 return absl::InvalidArgumentError(
1188 "Source artifact diagnostic labels must not be empty");
1189 }
1190
1191 std::vector<fs::path> resolved_targets;
1192 ASSIGN_OR_RETURN(resolved_targets,
1193 PreparePublicationTargets(targets, labels));
1194
1195 std::vector<fs::path> lock_paths;
1196 ASSIGN_OR_RETURN(lock_paths, PublicationLockPaths(resolved_targets, labels));
1197 std::unique_ptr<SourceArtifactWriteLocks> write_locks;
1198 ASSIGN_OR_RETURN(write_locks,
1199 SourceArtifactWriteLocks::Acquire(lock_paths, labels));
1200
1201 std::vector<fs::path> resolved_after_lock;
1202 ASSIGN_OR_RETURN(resolved_after_lock,
1203 ResolvePublicationTargets(targets, labels));
1204 if (resolved_after_lock != resolved_targets) {
1205 return absl::AbortedError(absl::StrFormat(
1206 "%s publication target changed while acquiring its lock",
1207 Capitalized(labels.subject)));
1208 }
1209
1210 auto impl = std::make_unique<SourceArtifactPublicationLock::Impl>();
1211 impl->labels = labels;
1212 impl->targets = std::move(resolved_targets);
1213 impl->write_locks = std::move(write_locks);
1214 return std::unique_ptr<SourceArtifactPublicationLock>(
1215 new SourceArtifactPublicationLock(std::move(impl)));
1216}
1217
1218std::string ComputeSourceArtifactSha256(std::string_view content) {
1219 return Sha256Hex(content);
1220}
1221
1224 std::vector<SourceArtifactUpdate> updates,
1225 std::string_view expected_primary_sha256,
1226 SourceArtifactReadbackValidator readback_validator) {
1227 if (lock.impl_ == nullptr) {
1228 return absl::FailedPreconditionError(
1229 "Source artifact publication lock is not initialized");
1230 }
1231 if (lock.impl_->publication_in_progress.test_and_set(
1232 std::memory_order_acquire)) {
1233 return absl::FailedPreconditionError(
1234 "Source artifact publication lock is already in use");
1235 }
1236 const ScopedPublicationUse publication_use(
1237 &lock.impl_->publication_in_progress);
1238 if (updates.empty()) {
1239 return absl::InvalidArgumentError(
1240 "Source artifact publication requires at least one update");
1241 }
1242 if (!updates.front().before.has_value()) {
1243 return absl::InvalidArgumentError(
1244 "Primary source artifact must contain its preflight bytes");
1245 }
1246
1247 std::vector<std::string> seen_targets;
1248 seen_targets.reserve(updates.size());
1249 for (SourceArtifactUpdate& update : updates) {
1250 fs::path resolved_target;
1251 ASSIGN_OR_RETURN(resolved_target, ResolvePublicationTarget(
1252 update.target, lock.impl_->labels));
1253 const auto covered = std::find(lock.impl_->targets.begin(),
1254 lock.impl_->targets.end(), resolved_target);
1255 if (covered == lock.impl_->targets.end()) {
1256 return absl::FailedPreconditionError(absl::StrFormat(
1257 "Publication target is not covered by the acquired lock: %s",
1258 update.target.string()));
1259 }
1260 update.target = *covered;
1261 const std::string normalized_target = LowercasePath(resolved_target);
1262 if (std::find(seen_targets.begin(), seen_targets.end(),
1263 normalized_target) != seen_targets.end()) {
1264 return absl::InvalidArgumentError(absl::StrFormat(
1265 "Source artifact publication target is duplicated: %s",
1266 update.target.string()));
1267 }
1268 seen_targets.push_back(normalized_target);
1269 }
1270
1271 std::vector<PublicationArtifact> artifacts;
1272 artifacts.reserve(updates.size());
1273 for (SourceArtifactUpdate& update : updates) {
1274 artifacts.push_back(PublicationArtifact{
1275 .target = std::move(update.target),
1276 .content = std::move(update.after),
1277 .original_content = std::move(update.before),
1278 });
1279 }
1280
1281 RETURN_IF_ERROR(PublishArtifactSet(&artifacts, expected_primary_sha256,
1282 lock.impl_->labels));
1283 if (readback_validator) {
1284 absl::Status validation_status;
1285 try {
1286 validation_status = readback_validator();
1287 } catch (const std::exception& error) {
1288 validation_status = absl::InternalError(absl::StrFormat(
1289 "%s readback validator threw an exception: %s",
1290 Capitalized(lock.impl_->labels.subject), error.what()));
1291 } catch (...) {
1292 validation_status = absl::InternalError(
1293 absl::StrFormat("%s readback validator threw an unknown exception",
1294 Capitalized(lock.impl_->labels.subject)));
1295 }
1296 if (!validation_status.ok()) {
1297 return RollBackPublication(&artifacts, validation_status,
1298 lock.impl_->labels);
1299 }
1300 }
1301
1302 const absl::Status backup_cleanup =
1303 CleanupBackups(&artifacts, lock.impl_->labels);
1304 if (!backup_cleanup.ok()) {
1305 return RollBackPublication(&artifacts, backup_cleanup, lock.impl_->labels);
1306 }
1307 return absl::OkStatus();
1308}
1309
1310} // namespace yaze::core
SourceArtifactPublicationLock(const SourceArtifactPublicationLock &)=delete
ScopedPublicationUse & operator=(const ScopedPublicationUse &)=delete
static absl::StatusOr< std::unique_ptr< SourceArtifactWriteLocks > > Acquire(const std::vector< fs::path > &lock_paths, const SourceArtifactPublisherLabels &labels)
SourceArtifactWriteLocks & operator=(const SourceArtifactWriteLocks &)=delete
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::string BackupRecoveryPaths(const std::vector< PublicationArtifact > &artifacts)
absl::StatusOr< fs::path > CreateBackup(const fs::path &target)
absl::StatusOr< std::vector< fs::path > > ResolvePublicationTargets(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels)
absl::StatusOr< std::vector< fs::path > > PreparePublicationTargets(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels)
absl::StatusOr< fs::path > WriteExclusiveTemp(const fs::path &target, std::string_view content)
absl::Status ValidateDistinctPublicationTargets(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels)
absl::StatusOr< std::vector< fs::path > > PublicationLockPaths(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels)
absl::Status PublishArtifactSet(std::vector< PublicationArtifact > *artifacts, std::string_view expected_source_sha256, const SourceArtifactPublisherLabels &labels)
constexpr uint32_t RotateRight(uint32_t value, uint32_t count)
fs::path NextSiblingPath(const fs::path &target, absl::string_view purpose)
absl::Status CleanupPublicationPaths(const std::vector< fs::path > &paths, absl::string_view artifact_kind, const SourceArtifactPublisherLabels &labels)
absl::Status RestoreArtifact(const PublicationArtifact &artifact)
void TransformSha256(uint32_t state[8], const uint8_t block[64])
absl::Status CleanupTemps(const std::vector< PublicationArtifact > &artifacts, const SourceArtifactPublisherLabels &labels)
absl::Status CleanupBackups(std::vector< PublicationArtifact > *artifacts, const SourceArtifactPublisherLabels &labels)
absl::Status RollBackPublication(std::vector< PublicationArtifact > *artifacts, const absl::Status &publication_failure, const SourceArtifactPublisherLabels &labels)
absl::Status ReplaceFromTemp(const fs::path &temp, const fs::path &target, bool *replaced=nullptr)
absl::Status ReturnFailureAfterCleanup(std::vector< PublicationArtifact > *artifacts, const absl::Status &publication_failure, const SourceArtifactPublisherLabels &labels)
absl::StatusOr< fs::path > ResolvePublicationTarget(const fs::path &target, const SourceArtifactPublisherLabels &labels)
absl::Status ValidateTargetsDoNotAliasLocks(const std::vector< fs::path > &lock_paths, const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels)
absl::Status VerifyUnchangedBeforePublication(const std::vector< PublicationArtifact > &artifacts, std::string_view expected_source_sha256, const SourceArtifactPublisherLabels &labels)
absl::Status PublishSourceArtifacts(const SourceArtifactPublicationLock &lock, std::vector< SourceArtifactUpdate > updates, std::string_view expected_primary_sha256, SourceArtifactReadbackValidator readback_validator)
constexpr char kSourceArtifactPublicationLockBasename[]
std::string ComputeSourceArtifactSha256(std::string_view content)
absl::Status ValidateSourceArtifactPublicationTargets(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels)
std::function< absl::Status()> SourceArtifactReadbackValidator
absl::StatusOr< std::unique_ptr< SourceArtifactPublicationLock > > AcquireSourceArtifactPublicationLock(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::unique_ptr< SourceArtifactWriteLocks > write_locks