activity_sdk_live_publication.cpp 28 KB

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