activity_sdk_live_publication.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. #include "activity_sdk_live_publication.h"
  2. #include <Windows.h>
  3. #include <array>
  4. #include <cwchar>
  5. #include <limits>
  6. #include <span>
  7. #include <string>
  8. #include <string_view>
  9. #include "activity_sdk_tree_publication.h"
  10. #include "core/runtime/host_environment.h"
  11. namespace sunrise::client::content::activity::sdk_generation::live_publication {
  12. namespace {
  13. /** Names of the staging, backup and marker paths; recovery scans for exactly these. */
  14. constexpr std::wstring_view kStageSuffix = L"\\.activity-sdk-stage.%08lX.%08lX.%08lX";
  15. constexpr std::wstring_view kBackupSuffix = L".activity-sdk-backup";
  16. constexpr std::wstring_view kPendingMarkerSuffix = L"\\.activity-sdk-publication.pending";
  17. constexpr std::wstring_view kCommittedMarkerSuffix = L"\\.activity-sdk-publication.committed";
  18. /** Distinct stage directories tried before giving up on a name collision. */
  19. constexpr std::size_t kAllocationAttempts = 16;
  20. /** Marker file magic, bytes "ASP1"; a file without it is not ours. */
  21. constexpr std::uint32_t kMarkerMagic = 0x31505341U;
  22. volatile LONG g_sequence{};
  23. struct Resource final {
  24. const wchar_t* stage{};
  25. const wchar_t* final{};
  26. std::wstring backup{};
  27. bool directory{};
  28. bool hadFinal{};
  29. bool oldMoved{};
  30. bool newMoved{};
  31. };
  32. struct Marker final {
  33. std::uint32_t magic{kMarkerMagic};
  34. std::uint8_t priorMask{};
  35. std::array<std::uint8_t, 3> reserved{};
  36. };
  37. [[nodiscard]] bool ordinary_directory(const wchar_t* path) noexcept;
  38. /** Resolves one null-terminated lexical path into normalized absolute storage. */
  39. [[nodiscard]] bool full_path(const wchar_t* input, std::wstring& output) noexcept {
  40. output.clear();
  41. if (input == nullptr || input[0] == L'\0') {
  42. return false;
  43. }
  44. const DWORD needed = GetFullPathNameW(input, 0, nullptr, nullptr);
  45. if (needed == 0) {
  46. return false;
  47. }
  48. try {
  49. std::wstring pending(static_cast<std::size_t>(needed), L'\0');
  50. const DWORD written = GetFullPathNameW(input, needed, pending.data(), nullptr);
  51. if (written == 0 || written >= needed) {
  52. return false;
  53. }
  54. pending.resize(written);
  55. while (pending.size() > 3U && (pending.back() == L'\\' || pending.back() == L'/')) {
  56. pending.pop_back();
  57. }
  58. output = std::move(pending);
  59. return true;
  60. } catch (...) {
  61. output.clear();
  62. return false;
  63. }
  64. }
  65. /** Which drive-path component, if any, is not an ordinary directory. */
  66. enum class Ancestry : std::uint8_t {
  67. ready,
  68. pathShape,
  69. driveRoot,
  70. ancestor,
  71. };
  72. /** A drive path is at least a letter, a colon and a separator. */
  73. constexpr std::size_t kDrivePrefixLength = 3;
  74. /** Reports the first drive-path component which is not an ordinary directory. */
  75. [[nodiscard]] Ancestry ordinary_ancestry(const std::wstring& directory) noexcept {
  76. if (directory.size() < kDrivePrefixLength || directory[1] != L':' || directory[2] != L'\\') {
  77. return Ancestry::pathShape;
  78. }
  79. const bool rootOrdinary = ordinary_directory(directory.substr(0, kDrivePrefixLength).c_str());
  80. std::size_t cursor = kDrivePrefixLength;
  81. while (cursor < directory.size()) {
  82. const std::size_t separator = directory.find(L'\\', cursor);
  83. const std::size_t end = separator == std::wstring::npos ? directory.size() : separator;
  84. const std::wstring prefix = directory.substr(0, end);
  85. if (!ordinary_directory(prefix.c_str())) {
  86. return Ancestry::ancestor;
  87. }
  88. if (separator == std::wstring::npos) {
  89. break;
  90. }
  91. cursor = separator + 1U;
  92. }
  93. return rootOrdinary ? Ancestry::ready : Ancestry::driveRoot;
  94. }
  95. /** Wine maps its synthetic drive root as a reparse point; real path components stay checked. */
  96. [[nodiscard]] bool ordinary_ancestry_allowed(Ancestry ancestry) noexcept {
  97. if (ancestry == Ancestry::ready) {
  98. return true;
  99. }
  100. if (ancestry != Ancestry::driveRoot) {
  101. return false;
  102. }
  103. return core::runtime::is_wine();
  104. }
  105. /** Resolves one existing ordinary directory and rejects reparse points in every ancestor. */
  106. [[nodiscard]] bool canonical_directory(const wchar_t* input, std::wstring& output) noexcept {
  107. return full_path(input, output) && ordinary_ancestry_allowed(ordinary_ancestry(output));
  108. }
  109. /** Compares two complete Windows path components without locale-sensitive folding. */
  110. [[nodiscard]] bool same_text(std::wstring_view left, std::wstring_view right) noexcept {
  111. return left.size() == right.size()
  112. && CompareStringOrdinal(left.data(),
  113. static_cast<int>(left.size()),
  114. right.data(),
  115. static_cast<int>(right.size()),
  116. TRUE)
  117. == CSTR_EQUAL;
  118. }
  119. /** Checks an ordinal case-insensitive prefix without reading beyond either span. */
  120. [[nodiscard]] bool starts_text(std::wstring_view value, std::wstring_view prefix) noexcept {
  121. return value.size() >= prefix.size()
  122. && CompareStringOrdinal(value.data(),
  123. static_cast<int>(prefix.size()),
  124. prefix.data(),
  125. static_cast<int>(prefix.size()),
  126. TRUE)
  127. == CSTR_EQUAL;
  128. }
  129. /** Splits one non-root path into borrowed parent and leaf spans. */
  130. [[nodiscard]] bool
  131. split(std::wstring_view path, std::wstring_view& parent, std::wstring_view& leaf) noexcept {
  132. const std::size_t separator = path.find_last_of(L"\\/");
  133. if (path.empty() || separator == std::wstring_view::npos || separator == 0
  134. || separator + 1U >= path.size()) {
  135. return false;
  136. }
  137. parent = path.substr(0, separator);
  138. leaf = path.substr(separator + 1U);
  139. return true;
  140. }
  141. /** Production rename primitive; every transaction path is same-volume by construction. */
  142. [[nodiscard]] bool default_move(void*, const wchar_t* source, const wchar_t* target) noexcept {
  143. return MoveFileExW(source, target, MOVEFILE_WRITE_THROUGH) != FALSE;
  144. }
  145. /** Requires one ordinary directory and rejects a junction or symbolic-link leaf. */
  146. [[nodiscard]] bool ordinary_directory(const wchar_t* path) noexcept {
  147. if (path == nullptr || path[0] == L'\0') {
  148. return false;
  149. }
  150. const DWORD attributes = GetFileAttributesW(path);
  151. return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0
  152. && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0;
  153. }
  154. /** Requires one ordinary file and rejects a reparse-backed leaf. */
  155. [[nodiscard]] bool ordinary_file(const wchar_t* path) noexcept {
  156. if (path == nullptr || path[0] == L'\0') {
  157. return false;
  158. }
  159. const DWORD attributes = GetFileAttributesW(path);
  160. return attributes != INVALID_FILE_ATTRIBUTES
  161. && (attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) == 0;
  162. }
  163. /** Accepts a missing final path or checks its exact expected ordinary kind. */
  164. [[nodiscard]] bool final_state(const wchar_t* path, bool directory, bool& exists) noexcept {
  165. exists = false;
  166. const DWORD attributes = GetFileAttributesW(path);
  167. if (attributes == INVALID_FILE_ATTRIBUTES) {
  168. const DWORD error = GetLastError();
  169. return error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND;
  170. }
  171. exists = true;
  172. return (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0
  173. && ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) == directory;
  174. }
  175. /** Creates one empty ordinary directory or accepts an existing ordinary directory. */
  176. [[nodiscard]] bool ensure_directory(const std::wstring& path) noexcept {
  177. if (CreateDirectoryW(path.c_str(), nullptr) != FALSE) {
  178. return true;
  179. }
  180. return GetLastError() == ERROR_ALREADY_EXISTS && ordinary_directory(path.c_str());
  181. }
  182. /** Builds one collision-resistant sibling backup path without consulting global artifact state. */
  183. [[nodiscard]] bool backup_path(std::wstring_view finalPath, std::wstring& output) noexcept {
  184. try {
  185. output.assign(finalPath);
  186. output.append(kBackupSuffix);
  187. return true;
  188. } catch (...) {
  189. output.clear();
  190. return false;
  191. }
  192. }
  193. /** Writes and flushes the bounded transaction marker before any final path moves. */
  194. [[nodiscard]] bool write_marker(const std::wstring& path, std::uint8_t priorMask) noexcept {
  195. const HANDLE file = CreateFileW(
  196. path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_HIDDEN, nullptr);
  197. if (file == INVALID_HANDLE_VALUE) {
  198. return false;
  199. }
  200. const Marker marker{kMarkerMagic, priorMask, {}};
  201. DWORD written = 0;
  202. const bool complete = WriteFile(file, &marker, sizeof marker, &written, nullptr) != FALSE
  203. && written == sizeof marker && FlushFileBuffers(file) != FALSE;
  204. const bool closed = CloseHandle(file) != FALSE;
  205. if (!complete || !closed) {
  206. (void)DeleteFileW(path.c_str());
  207. return false;
  208. }
  209. return true;
  210. }
  211. /** Reads one exact marker without accepting extra bytes or alternate state. */
  212. [[nodiscard]] bool read_marker(const std::wstring& path, Marker& output) noexcept {
  213. output = {};
  214. if (!ordinary_file(path.c_str())) {
  215. return false;
  216. }
  217. const HANDLE file = CreateFileW(path.c_str(),
  218. GENERIC_READ,
  219. FILE_SHARE_READ | FILE_SHARE_DELETE,
  220. nullptr,
  221. OPEN_EXISTING,
  222. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT,
  223. nullptr);
  224. if (file == INVALID_HANDLE_VALUE) {
  225. return false;
  226. }
  227. DWORD read = 0;
  228. std::byte trailing{};
  229. DWORD trailingRead = 0;
  230. const bool complete =
  231. ReadFile(file, &output, sizeof output, &read, nullptr) != FALSE && read == sizeof output
  232. && ReadFile(file, &trailing, 1, &trailingRead, nullptr) != FALSE && trailingRead == 0;
  233. const bool closed = CloseHandle(file) != FALSE;
  234. return complete && closed && output.magic == kMarkerMagic
  235. && output.reserved == std::array<std::uint8_t, 3>{} && (output.priorMask & 0xF8U) == 0;
  236. }
  237. /** Removes one transaction-created final value before restoring its backup. */
  238. [[nodiscard]] bool remove_value(const Resource& resource) noexcept {
  239. if (!resource.newMoved) {
  240. return true;
  241. }
  242. return resource.directory ? tree_publication::discard(resource.final)
  243. : DeleteFileW(resource.final) != FALSE;
  244. }
  245. /** Restores every already-touched resource in reverse commit order. */
  246. [[nodiscard]] bool rollback(std::span<Resource> resources,
  247. std::size_t touched,
  248. MoveOperation move,
  249. void* moveContext) noexcept {
  250. bool complete = true;
  251. while (touched != 0) {
  252. Resource& resource = resources[--touched];
  253. if (resource.newMoved) {
  254. complete = remove_value(resource) && complete;
  255. resource.newMoved = false;
  256. }
  257. if (resource.oldMoved) {
  258. const bool restored = move(moveContext, resource.backup.c_str(), resource.final);
  259. complete = restored && complete;
  260. resource.oldMoved = !restored;
  261. }
  262. }
  263. return complete;
  264. }
  265. /** Moves one prior value aside and one staged value into its final name. */
  266. [[nodiscard]] Status advance(std::span<Resource> resources,
  267. std::size_t index,
  268. MoveOperation move,
  269. void* moveContext) noexcept {
  270. Resource& resource = resources[index];
  271. if (resource.hadFinal) {
  272. if (!move(moveContext, resource.final, resource.backup.c_str())) {
  273. return rollback(resources, index, move, moveContext) ? Status::backupFailure
  274. : Status::rollbackFailure;
  275. }
  276. resource.oldMoved = true;
  277. }
  278. if (!move(moveContext, resource.stage, resource.final)) {
  279. return rollback(resources, index + 1U, move, moveContext) ? Status::commitFailure
  280. : Status::rollbackFailure;
  281. }
  282. resource.newMoved = true;
  283. return Status::ready;
  284. }
  285. /** Deletes successful backup values; failure leaves only recoverable stale siblings. */
  286. [[nodiscard]] bool discard_backups(std::span<const Resource> resources) noexcept {
  287. bool complete = true;
  288. for (const Resource& resource : resources) {
  289. if (!resource.hadFinal) {
  290. continue;
  291. }
  292. if (resource.directory) {
  293. complete = tree_publication::discard(resource.backup.c_str()) && complete;
  294. } else {
  295. complete = DeleteFileW(resource.backup.c_str()) != FALSE && complete;
  296. }
  297. }
  298. return complete;
  299. }
  300. /** Deletes one final path only when recovery proves it did not exist before the transaction. */
  301. [[nodiscard]] bool remove_final(Resource& resource) noexcept {
  302. bool exists = false;
  303. if (!final_state(resource.final, resource.directory, exists)) {
  304. return false;
  305. }
  306. if (!exists) {
  307. return true;
  308. }
  309. resource.newMoved = true;
  310. const bool removed = remove_value(resource);
  311. resource.newMoved = false;
  312. return removed;
  313. }
  314. /** Resolves a prior interrupted transaction before a new stage can be allocated. */
  315. [[nodiscard]] bool recover(const wchar_t* finalSdkDirectory,
  316. const wchar_t* finalPackPath,
  317. const wchar_t* finalCatalogPath) noexcept {
  318. try {
  319. std::wstring finalLua(finalSdkDirectory);
  320. finalLua.append(L"\\lua");
  321. std::array<Resource, 3> resources{
  322. Resource{nullptr, finalLua.c_str(), {}, true},
  323. Resource{nullptr, finalPackPath, {}, false},
  324. Resource{nullptr, finalCatalogPath, {}, false},
  325. };
  326. for (Resource& resource : resources) {
  327. if (!backup_path(resource.final, resource.backup)) {
  328. return false;
  329. }
  330. }
  331. const std::wstring pending = std::wstring(finalSdkDirectory) + kPendingMarkerSuffix.data();
  332. const std::wstring committed =
  333. std::wstring(finalSdkDirectory) + kCommittedMarkerSuffix.data();
  334. const DWORD pendingAttributes = GetFileAttributesW(pending.c_str());
  335. const DWORD committedAttributes = GetFileAttributesW(committed.c_str());
  336. if (pendingAttributes != INVALID_FILE_ATTRIBUTES
  337. && committedAttributes != INVALID_FILE_ATTRIBUTES) {
  338. return false;
  339. }
  340. if (committedAttributes != INVALID_FILE_ATTRIBUTES) {
  341. Marker committedMarker{};
  342. if (!read_marker(committed, committedMarker)) {
  343. return false;
  344. }
  345. for (Resource& resource : resources) {
  346. bool exists = false;
  347. if (!final_state(resource.final, resource.directory, exists) || !exists) {
  348. return false;
  349. }
  350. }
  351. for (const Resource& resource : resources) {
  352. const DWORD attributes = GetFileAttributesW(resource.backup.c_str());
  353. if (attributes == INVALID_FILE_ATTRIBUTES) {
  354. continue;
  355. }
  356. const bool removed = resource.directory
  357. ? tree_publication::discard(resource.backup.c_str())
  358. : DeleteFileW(resource.backup.c_str()) != FALSE;
  359. if (!removed) {
  360. return false;
  361. }
  362. }
  363. return DeleteFileW(committed.c_str()) != FALSE;
  364. }
  365. Marker marker{};
  366. std::uint8_t priorMask = 0;
  367. bool hasAnyBackup = false;
  368. for (const Resource& resource : resources) {
  369. hasAnyBackup |= GetFileAttributesW(resource.backup.c_str()) != INVALID_FILE_ATTRIBUTES;
  370. }
  371. if (pendingAttributes == INVALID_FILE_ATTRIBUTES && !hasAnyBackup) {
  372. return true;
  373. }
  374. const bool hasPending = pendingAttributes != INVALID_FILE_ATTRIBUTES;
  375. if (hasPending) {
  376. if (!read_marker(pending, marker)) {
  377. return false;
  378. }
  379. priorMask = marker.priorMask;
  380. } else {
  381. for (std::size_t index = 0; index < resources.size(); ++index) {
  382. if (GetFileAttributesW(resources[index].backup.c_str())
  383. != INVALID_FILE_ATTRIBUTES) {
  384. priorMask |= static_cast<std::uint8_t>(1U << index);
  385. }
  386. }
  387. }
  388. for (std::size_t index = resources.size(); index != 0; --index) {
  389. Resource& resource = resources[index - 1U];
  390. const bool hadPrior = (priorMask & (1U << (index - 1U))) != 0;
  391. const bool hasBackup =
  392. GetFileAttributesW(resource.backup.c_str()) != INVALID_FILE_ATTRIBUTES;
  393. if (hasBackup) {
  394. if (!remove_final(resource)
  395. || !default_move(nullptr, resource.backup.c_str(), resource.final)) {
  396. return false;
  397. }
  398. } else if (hasPending) {
  399. if (!hadPrior) {
  400. if (!remove_final(resource)) {
  401. return false;
  402. }
  403. } else {
  404. bool exists = false;
  405. if (!final_state(resource.final, resource.directory, exists) || !exists) {
  406. return false;
  407. }
  408. }
  409. }
  410. }
  411. return pendingAttributes == INVALID_FILE_ATTRIBUTES
  412. || DeleteFileW(pending.c_str()) != FALSE;
  413. } catch (...) {
  414. return false;
  415. }
  416. }
  417. /** Removes bounded writer-owned stage siblings left by an interrupted process. */
  418. [[nodiscard]] bool discard_stale_stages(const std::wstring& parent) noexcept {
  419. WIN32_FIND_DATAW entry{};
  420. const std::wstring search = parent + L"\\.activity-sdk-stage.*";
  421. const HANDLE find = FindFirstFileW(search.c_str(), &entry);
  422. if (find == INVALID_HANDLE_VALUE) {
  423. return GetLastError() == ERROR_FILE_NOT_FOUND;
  424. }
  425. bool complete = true;
  426. do {
  427. const std::wstring_view name(entry.cFileName);
  428. if (name == L"." || name == L"..") {
  429. continue;
  430. }
  431. const std::wstring path = parent + L"\\" + std::wstring(name);
  432. if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0
  433. || (entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0
  434. || !tree_publication::discard(path.c_str())) {
  435. complete = false;
  436. }
  437. } while (FindNextFileW(find, &entry) != FALSE);
  438. const DWORD endError = GetLastError();
  439. return FindClose(find) != FALSE && endError == ERROR_NO_MORE_FILES && complete;
  440. }
  441. } // namespace
  442. /** Returns one stable bounded reason for a live publication result. */
  443. const char* status_name(Status value) noexcept {
  444. switch (value) {
  445. case Status::ready:
  446. return "ready";
  447. case Status::invalidInput:
  448. return "invalid_input";
  449. case Status::stageAllocation:
  450. return "stage_allocation";
  451. case Status::backupCollision:
  452. return "backup_collision";
  453. case Status::backupFailure:
  454. return "backup_failure";
  455. case Status::commitFailure:
  456. return "commit_failure";
  457. case Status::finalizeFailure:
  458. return "finalize_failure";
  459. case Status::rollbackFailure:
  460. return "rollback_failure";
  461. }
  462. return "invalid_input";
  463. }
  464. /** Recovers any bounded prior journal, removes stale stages, and allocates one isolated sibling. */
  465. Status allocate(const wchar_t* finalPackPath, Stage& output) noexcept {
  466. output = {};
  467. if (finalPackPath == nullptr || finalPackPath[0] == L'\0') {
  468. return Status::invalidInput;
  469. }
  470. std::wstring normalizedPack;
  471. if (!full_path(finalPackPath, normalizedPack)) {
  472. return Status::invalidInput;
  473. }
  474. const std::wstring_view pack(normalizedPack);
  475. std::wstring_view parent;
  476. std::wstring_view leaf;
  477. if (!split(pack, parent, leaf) || !same_text(leaf, L"activity_sdk.pack")) {
  478. return Status::invalidInput;
  479. }
  480. try {
  481. const std::wstring lexicalParent(parent);
  482. std::wstring parentPath;
  483. if (!canonical_directory(lexicalParent.c_str(), parentPath)) {
  484. return Status::invalidInput;
  485. }
  486. const std::wstring finalSdkDirectory = parentPath + L"\\sdk";
  487. const std::wstring finalCatalogPath = finalSdkDirectory + L"\\catalog.bin";
  488. if (!ordinary_directory(parentPath.c_str())
  489. || !ordinary_directory(finalSdkDirectory.c_str())
  490. || !recover(finalSdkDirectory.c_str(), normalizedPack.c_str(), finalCatalogPath.c_str())
  491. || !discard_stale_stages(parentPath)) {
  492. return Status::invalidInput;
  493. }
  494. std::array<wchar_t, 80> suffix{};
  495. for (std::size_t attempt = 0; attempt < kAllocationAttempts; ++attempt) {
  496. const DWORD sequence = static_cast<DWORD>(InterlockedIncrement(&g_sequence));
  497. const int length = std::swprintf(suffix.data(),
  498. suffix.size(),
  499. kStageSuffix.data(),
  500. GetCurrentProcessId(),
  501. GetCurrentThreadId(),
  502. sequence);
  503. if (length <= 0 || static_cast<std::size_t>(length) >= suffix.size()) {
  504. return Status::stageAllocation;
  505. }
  506. output.root = parentPath;
  507. output.root.append(suffix.data(), static_cast<std::size_t>(length));
  508. if (CreateDirectoryW(output.root.c_str(), nullptr) == FALSE) {
  509. const DWORD error = GetLastError();
  510. if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS) {
  511. output = {};
  512. continue;
  513. }
  514. output = {};
  515. return Status::stageAllocation;
  516. }
  517. output.sdkDirectory = output.root + L"\\sdk";
  518. output.packPath = output.root + L"\\activity_sdk.pack";
  519. output.catalogPath = output.sdkDirectory + L"\\catalog.bin";
  520. output.luaDirectory = output.sdkDirectory + L"\\lua";
  521. if (!ensure_directory(output.sdkDirectory)) {
  522. (void)discard(output);
  523. output = {};
  524. return Status::stageAllocation;
  525. }
  526. return Status::ready;
  527. }
  528. } catch (...) {
  529. (void)discard(output);
  530. output = {};
  531. return Status::stageAllocation;
  532. }
  533. return Status::stageAllocation;
  534. }
  535. /** Commits Lua, pack, and catalog in order while retaining rollback state through finalization. */
  536. Status publish(const Stage& stage,
  537. const wchar_t* finalSdkDirectory,
  538. const wchar_t* finalPackPath,
  539. const wchar_t* finalCatalogPath,
  540. MoveOperation move,
  541. void* moveContext,
  542. FinalizeOperation finalize,
  543. void* finalizeContext) noexcept {
  544. if (stage.root.empty() || stage.sdkDirectory.empty() || stage.packPath.empty()
  545. || stage.catalogPath.empty() || stage.luaDirectory.empty() || finalSdkDirectory == nullptr
  546. || finalPackPath == nullptr || finalCatalogPath == nullptr) {
  547. return Status::invalidInput;
  548. }
  549. std::wstring canonicalStageRoot;
  550. std::wstring canonicalStageSdk;
  551. std::wstring canonicalStageLua;
  552. std::wstring canonicalFinalSdk;
  553. std::wstring canonicalStagePack;
  554. std::wstring canonicalStageCatalog;
  555. std::wstring canonicalFinalPack;
  556. std::wstring canonicalFinalCatalog;
  557. if (!canonical_directory(stage.root.c_str(), canonicalStageRoot)
  558. || !canonical_directory(stage.sdkDirectory.c_str(), canonicalStageSdk)
  559. || !canonical_directory(stage.luaDirectory.c_str(), canonicalStageLua)
  560. || !canonical_directory(finalSdkDirectory, canonicalFinalSdk)
  561. || !full_path(stage.packPath.c_str(), canonicalStagePack)
  562. || !full_path(stage.catalogPath.c_str(), canonicalStageCatalog)
  563. || !full_path(finalPackPath, canonicalFinalPack)
  564. || !full_path(finalCatalogPath, canonicalFinalCatalog)
  565. || !ordinary_file(canonicalStagePack.c_str())
  566. || !ordinary_file(canonicalStageCatalog.c_str())) {
  567. return Status::invalidInput;
  568. }
  569. const std::wstring expectedStageSdk = canonicalStageRoot + L"\\sdk";
  570. const std::wstring expectedStageLua = expectedStageSdk + L"\\lua";
  571. const std::wstring expectedStagePack = canonicalStageRoot + L"\\activity_sdk.pack";
  572. const std::wstring expectedStageCatalog = expectedStageSdk + L"\\catalog.bin";
  573. const std::wstring expectedFinalCatalog = canonicalFinalSdk + L"\\catalog.bin";
  574. std::wstring_view stageParent;
  575. std::wstring_view stageLeaf;
  576. std::wstring_view finalParent;
  577. std::wstring_view finalLeaf;
  578. if (!split(canonicalStageRoot, stageParent, stageLeaf)
  579. || !split(canonicalFinalPack, finalParent, finalLeaf)) {
  580. return Status::invalidInput;
  581. }
  582. const std::wstring expectedFinalSdk = std::wstring(finalParent) + L"\\sdk";
  583. if (!same_text(stageParent, finalParent) || !starts_text(stageLeaf, L".activity-sdk-stage.")
  584. || !same_text(canonicalStageSdk, expectedStageSdk)
  585. || !same_text(canonicalStageLua, expectedStageLua)
  586. || !same_text(canonicalStagePack, expectedStagePack)
  587. || !same_text(canonicalStageCatalog, expectedStageCatalog)
  588. || !same_text(canonicalFinalSdk, expectedFinalSdk)
  589. || !same_text(canonicalFinalCatalog, expectedFinalCatalog)
  590. || !same_text(finalLeaf, L"activity_sdk.pack")) {
  591. return Status::invalidInput;
  592. }
  593. std::wstring finalLua;
  594. try {
  595. finalLua = canonicalFinalSdk;
  596. finalLua.append(L"\\lua");
  597. } catch (...) {
  598. return Status::invalidInput;
  599. }
  600. std::array<Resource, 3> resources{
  601. Resource{canonicalStageLua.c_str(), finalLua.c_str(), {}, true},
  602. Resource{canonicalStagePack.c_str(), canonicalFinalPack.c_str(), {}, false},
  603. Resource{canonicalStageCatalog.c_str(), canonicalFinalCatalog.c_str(), {}, false},
  604. };
  605. std::wstring pendingMarker;
  606. std::wstring committedMarker;
  607. try {
  608. pendingMarker = canonicalFinalSdk;
  609. pendingMarker.append(kPendingMarkerSuffix);
  610. committedMarker = canonicalFinalSdk;
  611. committedMarker.append(kCommittedMarkerSuffix);
  612. } catch (...) {
  613. return Status::invalidInput;
  614. }
  615. std::uint8_t priorMask = 0;
  616. for (Resource& resource : resources) {
  617. if (!final_state(resource.final, resource.directory, resource.hadFinal)
  618. || !backup_path(resource.final, resource.backup)) {
  619. return Status::invalidInput;
  620. }
  621. if (GetFileAttributesW(resource.backup.c_str()) != INVALID_FILE_ATTRIBUTES) {
  622. return Status::backupCollision;
  623. }
  624. }
  625. for (std::size_t index = 0; index < resources.size(); ++index) {
  626. if (resources[index].hadFinal) {
  627. priorMask |= static_cast<std::uint8_t>(1U << index);
  628. }
  629. }
  630. if (GetFileAttributesW(pendingMarker.c_str()) != INVALID_FILE_ATTRIBUTES
  631. || GetFileAttributesW(committedMarker.c_str()) != INVALID_FILE_ATTRIBUTES
  632. || !write_marker(pendingMarker, priorMask)) {
  633. return Status::backupCollision;
  634. }
  635. const MoveOperation rename = move != nullptr ? move : &default_move;
  636. for (std::size_t index = 0; index < resources.size(); ++index) {
  637. const Status moved = advance(resources, index, rename, moveContext);
  638. if (moved != Status::ready) {
  639. if (moved != Status::rollbackFailure) {
  640. (void)DeleteFileW(pendingMarker.c_str());
  641. }
  642. return moved;
  643. }
  644. }
  645. if (finalize != nullptr && !finalize(finalizeContext)) {
  646. const bool restored = rollback(resources, resources.size(), rename, moveContext);
  647. if (restored) {
  648. (void)DeleteFileW(pendingMarker.c_str());
  649. }
  650. return restored ? Status::finalizeFailure : Status::rollbackFailure;
  651. }
  652. if (!rename(moveContext, pendingMarker.c_str(), committedMarker.c_str())) {
  653. bool restored = rollback(resources, resources.size(), rename, moveContext);
  654. if (finalize != nullptr) {
  655. restored = finalize(finalizeContext) && restored;
  656. }
  657. if (restored) {
  658. (void)DeleteFileW(pendingMarker.c_str());
  659. }
  660. return restored ? Status::commitFailure : Status::rollbackFailure;
  661. }
  662. if (discard_backups(resources)) {
  663. (void)DeleteFileW(committedMarker.c_str());
  664. }
  665. (void)discard(stage);
  666. return Status::ready;
  667. }
  668. bool discard(const Stage& stage) noexcept {
  669. return stage.root.empty() || tree_publication::discard(stage.root.c_str());
  670. }
  671. } // namespace sunrise::client::content::activity::sdk_generation::live_publication