载入中...
搜索中...
未找到
AssetPackageStore.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <fstream>
5#include <iterator>
6#include <limits>
7
8#if defined(_WIN32)
9#include <windows.h>
10#else
11#include <cerrno>
12#include <fcntl.h>
13#include <unistd.h>
14#endif
15
16namespace eve::asset {
17namespace {
18
19Result<void> writeExclusiveAndFlush(const std::filesystem::path& path,
20 std::span<const std::uint8_t> bytes) {
21#if defined(_WIN32)
22 HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW,
23 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, nullptr);
24 if (file == INVALID_HANDLE_VALUE)
25 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Failed, "cannot create temporary package",
26 path.string(), {}, "asset.package.store"));
27 std::size_t cursor = 0;
28 bool written = true;
29 while (cursor < bytes.size()) {
30 const DWORD request = static_cast<DWORD>(std::min<std::size_t>(bytes.size() - cursor, MAXDWORD));
31 DWORD count = 0;
32 if (!WriteFile(file, bytes.data() + cursor, request, &count, nullptr) || count != request) {
33 written = false;
34 break;
35 }
36 cursor += count;
37 }
38 const bool flushed = written && FlushFileBuffers(file) != 0;
39 CloseHandle(file);
40 if (!flushed)
41 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Failed, "cannot flush temporary package",
42 path.string(), {}, "asset.package.store"));
43#else
44 const int file = ::open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
45 if (file < 0)
46 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Failed, "cannot create temporary package",
47 path.string(), {}, "asset.package.store"));
48 std::size_t cursor = 0;
49 bool written = true;
50 while (cursor < bytes.size()) {
51 const ssize_t count = ::write(file, bytes.data() + cursor, bytes.size() - cursor);
52 if (count < 0 && errno == EINTR) continue;
53 if (count <= 0) { written = false; break; }
54 cursor += static_cast<std::size_t>(count);
55 }
56 const bool flushed = written && ::fsync(file) == 0;
57 ::close(file);
58 if (!flushed)
59 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Failed, "cannot flush temporary package",
60 path.string(), {}, "asset.package.store"));
61#endif
62 return Result<void>::success();
63}
64
65Result<std::vector<std::uint8_t>> readBounded(const std::filesystem::path& path, std::uint64_t maximumBytes) {
66 std::error_code ec;
67 const auto size = std::filesystem::file_size(path, ec);
68 if (ec || size > maximumBytes || size > (std::numeric_limits<std::size_t>::max)())
69 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
70 "temporary package size is outside limits",
71 path.string(), {}, "asset.package.store"));
72 std::ifstream input(path, std::ios::binary);
73 if (!input)
74 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(
75 DiagnosticCode::Failed, "cannot reopen temporary package", path.string(), {}, "asset.package.store"));
76 std::vector<std::uint8_t> bytes(static_cast<std::size_t>(size));
77 if (!bytes.empty()) input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
78 if (!input || input.peek() != std::ifstream::traits_type::eof())
79 return Result<std::vector<std::uint8_t>>::failure(Diagnostic::error(DiagnosticCode::Failed,
80 "temporary package changed while reading",
81 path.string(), {}, "asset.package.store"));
82 return Result<std::vector<std::uint8_t>>::success(std::move(bytes));
83}
84
85Result<void> replaceFile(const std::filesystem::path& temporary,
86 const std::filesystem::path& destination) {
87#if defined(_WIN32)
88 if (!MoveFileExW(temporary.c_str(), destination.c_str(),
89 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
91 "atomic package replacement failed",
92 destination.string(), {}, "asset.package.store"));
93#else
94 std::error_code ec;
95 std::filesystem::rename(temporary, destination, ec);
96 if (ec)
98 "atomic package replacement failed: " + ec.message(),
99 destination.string(), {}, "asset.package.store"));
100#endif
101 return Result<void>::success();
102}
103
104std::filesystem::path temporaryPath(const std::filesystem::path& destination, std::uint64_t sequence) {
105#if defined(_WIN32)
106 const auto process = static_cast<std::uint64_t>(GetCurrentProcessId());
107#else
108 const auto process = static_cast<std::uint64_t>(::getpid());
109#endif
110 return destination.parent_path() /
111 ("." + destination.filename().string() + ".tmp." + std::to_string(process) + "." +
112 std::to_string(sequence));
113}
114
115class TemporaryCleanup {
116public:
117 explicit TemporaryCleanup(std::filesystem::path path) : path_(std::move(path)) {}
118 ~TemporaryCleanup() {
119 if (!active_) return;
120 std::error_code ignored;
121 std::filesystem::remove(path_, ignored);
122 }
123 void release() noexcept { active_ = false; }
124private:
125 std::filesystem::path path_;
126 bool active_ = true;
127};
128
129} // namespace
130
132 : beforeReplace_(std::move(beforeReplace)) {}
133
135 const std::filesystem::path& destination, const EvaManifest& manifest,
136 std::vector<EvaArchiveEntry> entries, const EvaArchiveLimits& limits) {
137 if (destination.empty() || destination.filename().empty())
139 Diagnostic::error(DiagnosticCode::InvalidArgument, "package destination is empty", destination.string(), {},
140 "asset.package.store"));
141 auto built = buildEvaArchive(manifest, std::move(entries), limits);
142 if (!built) return Result<PackagePublishReceipt>::failure(built.status());
143 std::error_code ec;
144 if (!destination.parent_path().empty()) std::filesystem::create_directories(destination.parent_path(), ec);
145 if (ec)
147 Diagnostic::error(DiagnosticCode::Failed, ec.message(), destination.string(), {}, "asset.package.store"));
148 const auto temporary = temporaryPath(destination, ++sequence_);
149 TemporaryCleanup cleanup(temporary);
150 auto written = writeExclusiveAndFlush(temporary, built.value());
151 if (!written) return Result<PackagePublishReceipt>::failure(written.status());
152 auto reopened = readBounded(temporary, limits.maximumArchiveBytes);
153 if (!reopened) return Result<PackagePublishReceipt>::failure(reopened.status());
154 auto verified = parseEvaArchive(reopened.value(), limits);
155 if (!verified) return Result<PackagePublishReceipt>::failure(verified.status());
156 if (beforeReplace_ && beforeReplace_(temporary, destination) == PackagePublishGateDecision::Reject)
158 Diagnostic::error(DiagnosticCode::Cancelled, "package replacement rejected before commit",
159 destination.string(), {}, "asset.package.store"));
160 auto replaced = replaceFile(temporary, destination);
161 if (!replaced) return Result<PackagePublishReceipt>::failure(replaced.status());
162 cleanup.release();
164 {destination, verified.value().manifest.packageId, std::nullopt, reopened.value().size()});
165}
166
168 const std::filesystem::path& destination, std::span<const std::uint8_t> bytes,
169 const EvpackLimits& limits, const EvpackTrust& trust) {
170 if (destination.empty() || destination.filename().empty())
172 Diagnostic::error(DiagnosticCode::InvalidArgument, "package destination is empty", destination.string(), {},
173 "asset.package.store"));
174 auto admitted = parseEvpack(bytes, limits, trust);
175 if (!admitted) return Result<PackagePublishReceipt>::failure(admitted.status());
176 std::error_code ec;
177 if (!destination.parent_path().empty()) std::filesystem::create_directories(destination.parent_path(), ec);
178 if (ec)
180 Diagnostic::error(DiagnosticCode::Failed, ec.message(), destination.string(), {}, "asset.package.store"));
181 const auto temporary = temporaryPath(destination, ++sequence_);
182 TemporaryCleanup cleanup(temporary);
183 auto written = writeExclusiveAndFlush(temporary, bytes);
184 if (!written) return Result<PackagePublishReceipt>::failure(written.status());
185 auto reopened = readBounded(temporary, limits.maximumPackageBytes);
186 if (!reopened) return Result<PackagePublishReceipt>::failure(reopened.status());
187 auto verified = parseEvpack(reopened.value(), limits, trust);
188 if (!verified) return Result<PackagePublishReceipt>::failure(verified.status());
189 if (beforeReplace_ && beforeReplace_(temporary, destination) == PackagePublishGateDecision::Reject)
191 Diagnostic::error(DiagnosticCode::Cancelled, "package replacement rejected before commit",
192 destination.string(), {}, "asset.package.store"));
193 auto replaced = replaceFile(temporary, destination);
194 if (!replaced) return Result<PackagePublishReceipt>::failure(replaced.status());
195 cleanup.release();
197 {destination, verified.value().packageId(), verified.value().buildId(), reopened.value().size()});
198}
199
200} // namespace eve::asset
std::string packageId
Reopen-verified atomic publication for .eva and .evpack files.
std::unordered_map< std::string, QuestRuntime > entries
filesystem::File * file
EvpackChunkInput input
Definition Evpack.cpp:170
const GltfImportRequest & request
std::uint64_t bytes
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
std::string path
Definition PlayHost.cpp:110
std::function< eve::Result< void >()> release
Definition Procgen.cpp:86
std::uint32_t count
std::size_t cursor
float size
Definition TreeMesh.cpp:156
const AssetImportLimits & limits
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Result< PackagePublishReceipt > publishEvpack(const std::filesystem::path &destination, std::span< const std::uint8_t > bytes, const EvpackLimits &limits={}, const EvpackTrust &trust={})
Reopen-verify and atomically publish already cooked .evpack bytes.
Result< PackagePublishReceipt > publishEva(const std::filesystem::path &destination, const EvaManifest &manifest, std::vector< EvaArchiveEntry > entries, const EvaArchiveLimits &limits={})
Build, reopen-verify and atomically publish one .eva source archive.
std::function< PackagePublishGateDecision(const std::filesystem::path &temporary, const std::filesystem::path &destination)> BeforeReplace
AtomicAssetPackageStore(BeforeReplace beforeReplace={})
Construct a publisher, optionally with a failure-injection/policy gate.
Result< Evpack > parseEvpack(std::span< const std::uint8_t > bytes, const EvpackLimits &limits, const EvpackTrust &trust)
Parse, bound-check and hash-verify a complete untrusted .evpack.
Definition Evpack.cpp:542
Result< std::vector< std::uint8_t > > buildEvaArchive(const EvaManifest &manifest, std::vector< EvaArchiveEntry > entries, const EvaArchiveLimits &limits)
Build a deterministic ZIP64 .eva, selecting raw Deflate only when smaller than Store.
Result< EvaArchive > parseEvaArchive(std::span< const std::uint8_t > bytes, const EvaArchiveLimits &limits)
Parse and fully verify an untrusted .eva ZIP64 image.
Admission budgets applied before allocating decoded archive entries.
Definition EvaArchive.h:21
Validated, owning .eva manifest.
Definition EvaManifest.h:62
Resource limits checked before package allocations and hash work.
Definition Evpack.h:126
Trust inputs kept separate from size/resource limits.
Definition Evpack.h:75