cache_file_writer.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. #include <Windows.h>
  2. #include <cstdint>
  3. #include <limits>
  4. #include "../../../../core/filesystem/path.h"
  5. #include "../internal.h"
  6. #include "../records/validation.h"
  7. #include "cache_payload_writer.h"
  8. #include "temporary/temporary_cache_file.h"
  9. #include "validation/cache_file_comparison.h"
  10. namespace sunrise::state::build_data::cache {
  11. namespace {
  12. /** @return True when every row count fits the disk header. */
  13. [[nodiscard]] bool counts_fit_header(records::Domains domains) noexcept {
  14. /** The header uses unsigned 32-bit row counts for every domain. */
  15. constexpr std::size_t kMaximumCount = (std::numeric_limits<std::uint32_t>::max)();
  16. return domains.named.size() <= kMaximumCount && domains.items.size() <= kMaximumCount
  17. && domains.collectibles.size() <= kMaximumCount
  18. && domains.materialRequirementSets.size() <= kMaximumCount
  19. && domains.itemDetails.size() <= kMaximumCount
  20. && domains.socketPlugRules.size() <= kMaximumCount
  21. && domains.socketPlugPools.size() <= kMaximumCount
  22. && domains.socketPlugMembers.size() <= kMaximumCount
  23. && domains.exoticCatalysts.size() <= kMaximumCount
  24. && domains.inventoryBuckets.size() <= kMaximumCount
  25. && domains.socketEntryLists.size() <= kMaximumCount
  26. && domains.socketEntryTables.size() <= kMaximumCount
  27. && domains.abilityBuckets.size() <= kMaximumCount
  28. && domains.progressions.size() <= kMaximumCount
  29. && domains.records.size() <= kMaximumCount && domains.nodes.size() <= kMaximumCount
  30. && domains.sobjects.size() <= kMaximumCount && domains.scenarios.size() <= kMaximumCount
  31. && domains.rosterGroups.size() <= kMaximumCount
  32. && domains.spawnStems.size() <= kMaximumCount
  33. && domains.spawnNameHashes.size() <= kMaximumCount
  34. && domains.spawnPoints.size() <= kMaximumCount
  35. && domains.hashNames.size() <= kMaximumCount
  36. && domains.vendorIndex.size() <= kMaximumCount
  37. && domains.vendorDefinitions.size() <= kMaximumCount
  38. && domains.vendorSaleRows.size() <= kMaximumCount
  39. && domains.vendorInstalledRows.size() <= kMaximumCount
  40. && domains.positionProfiles.size() <= kMaximumCount
  41. && domains.objectTypes.size() <= kMaximumCount
  42. && domains.recordObjectives.size() <= kMaximumCount
  43. && domains.recordIntervals.size() <= kMaximumCount
  44. && domains.recordRewards.size() <= kMaximumCount
  45. && domains.progressionSteps.size() <= kMaximumCount
  46. && domains.seasonPassRewards.size() <= kMaximumCount
  47. && domains.bounties.size() <= kMaximumCount
  48. && domains.rewardPools.size() <= kMaximumCount
  49. && domains.rewardEntries.size() <= kMaximumCount
  50. && domains.rewardItems.size() <= kMaximumCount
  51. && domains.rewardInstructions.size() <= kMaximumCount
  52. && domains.rewardModifiers.size() <= kMaximumCount
  53. && domains.rewardSockets.size() <= kMaximumCount;
  54. }
  55. /** @return True when the requested final-name rule is one of the declared values. */
  56. [[nodiscard]] bool valid_disposition(WriteDisposition disposition) noexcept {
  57. return disposition == WriteDisposition::createOnly
  58. || disposition == WriteDisposition::replaceStale;
  59. }
  60. } // namespace
  61. /**
  62. * Writes a sibling file first, then creates or replaces the whole cache in one step.
  63. * @param directory Null-terminated cache directory.
  64. * @param path Null-terminated final cache path.
  65. * @param build Current executable and configured-equipment identity.
  66. * @param domains Complete sorted mapping domains.
  67. * @param disposition Create only, or replace a stale cache.
  68. * @return True when the file is on disk under its requested final name.
  69. */
  70. bool write(const wchar_t* directory,
  71. const wchar_t* path,
  72. const BuildIdentity& build,
  73. records::Domains domains,
  74. WriteDisposition disposition) noexcept {
  75. if (directory == nullptr || path == nullptr || build.imageSize == 0
  76. || !valid_disposition(disposition) || !counts_fit_header(domains)
  77. || !records::valid_domains(build, domains)) {
  78. return false;
  79. }
  80. if (CreateDirectoryW(directory, nullptr) == FALSE && GetLastError() != ERROR_ALREADY_EXISTS) {
  81. return false;
  82. }
  83. const DWORD existingAttributes = GetFileAttributesW(path);
  84. if (existingAttributes != INVALID_FILE_ATTRIBUTES) {
  85. if ((existingAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
  86. return false;
  87. }
  88. } else {
  89. const DWORD error = GetLastError();
  90. if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) {
  91. return false;
  92. }
  93. }
  94. std::uint64_t checksum = 0;
  95. if (!writer::payload_checksum(domains, checksum)) {
  96. return false;
  97. }
  98. core::path::Buffer temporaryPath;
  99. if (!temporary::write(path, build, domains, checksum, temporaryPath)) {
  100. return false;
  101. }
  102. DWORD moveFlags = MOVEFILE_WRITE_THROUGH;
  103. if (disposition == WriteDisposition::replaceStale) {
  104. moveFlags |= MOVEFILE_REPLACE_EXISTING;
  105. }
  106. bool complete = MoveFileExW(temporaryPath.chars.data(), path, moveFlags) != FALSE;
  107. if (!complete && disposition == WriteDisposition::createOnly) {
  108. // The loser of the race succeeds only when the winner wrote these exact bytes.
  109. complete = validation::files_equal(path, temporaryPath.chars.data());
  110. }
  111. if (!complete || GetFileAttributesW(temporaryPath.chars.data()) != INVALID_FILE_ATTRIBUTES) {
  112. (void)DeleteFileW(temporaryPath.chars.data());
  113. }
  114. return complete;
  115. }
  116. } // namespace sunrise::state::build_data::cache