node_persistence.cpp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #include "node_persistence.h"
  2. #include <array>
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <vector>
  6. #include <windows.h>
  7. #include "../../../core/logging/log.h"
  8. #include "../../../core/filesystem/path.h"
  9. #include "../runtime.h"
  10. #include "node_catalog.h"
  11. namespace sunrise::state::build_data::nodes {
  12. namespace {
  13. /** The node file lives beside the claim file, in the directory the build data cache already owns. */
  14. constexpr std::wstring_view kNodeFileSuffix = L"\\cache\\node_definitions.bin";
  15. /** Identifies the file on sight, so an unrelated file of the right length cannot be read as one. */
  16. constexpr std::array<char, 8> kMagic{'S', 'N', 'R', 'S', 'N', 'O', 'D', '1'};
  17. core::path::Buffer g_path{};
  18. bool g_pathReady{};
  19. void report(const char* stage, const char* result, std::size_t detail) noexcept {
  20. std::array<char, 128> line{};
  21. const int written = std::snprintf(
  22. line.data(), line.size(), "ev=nodes stage=%s result=%s rows=%zu", stage, result, detail);
  23. if (written > 0) {
  24. core::log::write(core::log::Channel::state,
  25. std::string_view{result} == "ok" ? core::log::Level::info
  26. : core::log::Level::warn,
  27. {line.data(), static_cast<std::size_t>(written)});
  28. }
  29. }
  30. /**
  31. * Header carried ahead of the rows.
  32. *
  33. * The row width is written out and checked on the way back in. The rows are stored as they sit in
  34. * memory, so a build whose Definition has changed shape must not read an older file as if it had
  35. * not: the width mismatch rejects it, extraction runs, and the file is replaced.
  36. */
  37. struct Header {
  38. std::array<char, 8> magic{};
  39. std::uint32_t rows{};
  40. std::uint32_t rowWidth{};
  41. };
  42. } // namespace
  43. /** Derives the node file path and publishes any table already held. */
  44. bool initialize(void* module) noexcept {
  45. g_pathReady = false;
  46. if (!core::path::artifact_directory(module, g_path)
  47. || !core::path::append(g_path, kNodeFileSuffix)) {
  48. report("initialize", "path_fail", 0);
  49. return false;
  50. }
  51. g_pathReady = true;
  52. return true;
  53. }
  54. /** Reads the node table and publishes it. Separate from initialize because a publish is only
  55. * accepted once the build data runtime is up, which is later than path setup. */
  56. bool load_and_publish() noexcept {
  57. if (!g_pathReady) {
  58. return false;
  59. }
  60. const HANDLE file = CreateFileW(
  61. g_path.chars.data(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
  62. if (file == INVALID_HANDLE_VALUE) {
  63. // A first run, not a fault. Extraction will build the table and write it.
  64. report("load", "absent", 0);
  65. return false;
  66. }
  67. Header header{};
  68. DWORD read = 0;
  69. const bool headerRead =
  70. ReadFile(file, &header, sizeof header, &read, nullptr) != FALSE && read == sizeof header;
  71. if (!headerRead || std::memcmp(header.magic.data(), kMagic.data(), kMagic.size()) != 0
  72. || header.rowWidth != sizeof(Definition) || header.rows == 0
  73. || header.rows > kDefinitionCapacity) {
  74. CloseHandle(file);
  75. report("load", headerRead ? "rejected" : "header_fail", header.rows);
  76. return false;
  77. }
  78. std::vector<Definition> rows(header.rows);
  79. const auto expected = static_cast<DWORD>(rows.size() * sizeof(Definition));
  80. const bool rowsRead =
  81. ReadFile(file, rows.data(), expected, &read, nullptr) != FALSE && read == expected;
  82. CloseHandle(file);
  83. if (!rowsRead) {
  84. report("load", "read_fail", rows.size());
  85. return false;
  86. }
  87. // Replaces the catalog directly rather than going through publish_node_definitions. That path
  88. // opens a publication transaction, and a saved cache freezes every domain, so on the warm start
  89. // this exists for it is refused before validation is even reached. The freeze is there to keep
  90. // published domains agreeing with the cache; this domain is not in the cache, and the rows being
  91. // restored are the ones extraction published and wrote here, so the same checks are run and the
  92. // agreement the freeze protects is not touched.
  93. if (!valid(std::span<const Definition>{rows})
  94. || !replace(std::span<const Definition>{rows})) {
  95. report("load", "publish_fail", rows.size());
  96. return false;
  97. }
  98. report("load", "ok", rows.size());
  99. return true;
  100. }
  101. /** Writes the node table so the next start does not need the package pass to rebuild it. */
  102. bool store(std::span<const Definition> definitions) noexcept {
  103. if (!g_pathReady || definitions.empty() || definitions.size() > kDefinitionCapacity) {
  104. return false;
  105. }
  106. Header header{};
  107. header.magic = kMagic;
  108. header.rows = static_cast<std::uint32_t>(definitions.size());
  109. header.rowWidth = static_cast<std::uint32_t>(sizeof(Definition));
  110. const HANDLE file = CreateFileW(g_path.chars.data(),
  111. GENERIC_WRITE,
  112. 0,
  113. nullptr,
  114. CREATE_ALWAYS,
  115. FILE_ATTRIBUTE_NORMAL,
  116. nullptr);
  117. if (file == INVALID_HANDLE_VALUE) {
  118. report("store", "open_fail", definitions.size());
  119. return false;
  120. }
  121. DWORD written = 0;
  122. bool complete =
  123. WriteFile(file, &header, sizeof header, &written, nullptr) != FALSE
  124. && written == sizeof header;
  125. if (complete) {
  126. const auto size = static_cast<DWORD>(definitions.size() * sizeof(Definition));
  127. complete = WriteFile(file, definitions.data(), size, &written, nullptr) != FALSE
  128. && written == size;
  129. }
  130. complete = CloseHandle(file) != FALSE && complete;
  131. report("store", complete ? "ok" : "write_fail", definitions.size());
  132. return complete;
  133. }
  134. } // namespace sunrise::state::build_data::nodes