cache_file_writer.cpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  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.inventoryBuckets.size() <= kMaximumCount
  24. && domains.socketEntryLists.size() <= kMaximumCount
  25. && domains.socketEntryTables.size() <= kMaximumCount
  26. && domains.abilityBuckets.size() <= kMaximumCount
  27. && domains.progressions.size() <= kMaximumCount
  28. && domains.records.size() <= kMaximumCount
  29. && domains.scenarios.size() <= kMaximumCount
  30. && domains.rosterGroups.size() <= kMaximumCount
  31. && domains.vendorIndex.size() <= kMaximumCount
  32. && domains.vendorDefinitions.size() <= kMaximumCount
  33. && domains.vendorSaleRows.size() <= kMaximumCount
  34. && domains.vendorInstalledRows.size() <= kMaximumCount;
  35. }
  36. /** @return True when the requested final-name rule is one of the declared values. */
  37. [[nodiscard]] bool valid_disposition(WriteDisposition disposition) noexcept {
  38. return disposition == WriteDisposition::createOnly
  39. || disposition == WriteDisposition::replaceStale;
  40. }
  41. } // namespace
  42. /**
  43. * Writes a sibling file first, then creates or replaces the whole cache in one step.
  44. * @param directory Null-terminated cache directory.
  45. * @param path Null-terminated final cache path.
  46. * @param build Current executable and configured-equipment identity.
  47. * @param domains Complete sorted mapping domains.
  48. * @param disposition Create only, or replace a stale cache.
  49. * @return True when the file is on disk under its requested final name.
  50. */
  51. bool write(const wchar_t* directory,
  52. const wchar_t* path,
  53. const BuildIdentity& build,
  54. records::Domains domains,
  55. WriteDisposition disposition) noexcept {
  56. if (directory == nullptr || path == nullptr || build.imageSize == 0
  57. || !valid_disposition(disposition) || !counts_fit_header(domains)
  58. || !records::valid_domains(domains)) {
  59. return false;
  60. }
  61. if (CreateDirectoryW(directory, nullptr) == FALSE && GetLastError() != ERROR_ALREADY_EXISTS) {
  62. return false;
  63. }
  64. const DWORD existingAttributes = GetFileAttributesW(path);
  65. if (existingAttributes != INVALID_FILE_ATTRIBUTES) {
  66. if ((existingAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
  67. return false;
  68. }
  69. } else {
  70. const DWORD error = GetLastError();
  71. if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) {
  72. return false;
  73. }
  74. }
  75. std::uint64_t checksum = 0;
  76. if (!writer::payload_checksum(domains, checksum)) {
  77. return false;
  78. }
  79. core::path::Buffer temporaryPath;
  80. if (!temporary::write(path, build, domains, checksum, temporaryPath)) {
  81. return false;
  82. }
  83. DWORD moveFlags = MOVEFILE_WRITE_THROUGH;
  84. if (disposition == WriteDisposition::replaceStale) {
  85. moveFlags |= MOVEFILE_REPLACE_EXISTING;
  86. }
  87. bool complete = MoveFileExW(temporaryPath.chars.data(), path, moveFlags) != FALSE;
  88. if (!complete && disposition == WriteDisposition::createOnly) {
  89. // The loser of the race succeeds only when the winner wrote these exact bytes.
  90. complete = validation::files_equal(path, temporaryPath.chars.data());
  91. }
  92. if (!complete || GetFileAttributesW(temporaryPath.chars.data()) != INVALID_FILE_ATTRIBUTES) {
  93. (void)DeleteFileW(temporaryPath.chars.data());
  94. }
  95. return complete;
  96. }
  97. } // namespace sunrise::state::build_data::cache