Skip to content

Commit bc5bdaf

Browse files
committed
Fix HTTP cache checksum mismatch for resources
1 parent 3d1ff37 commit bc5bdaf

1 file changed

Lines changed: 130 additions & 6 deletions

File tree

Server/mods/deathmatch/logic/CResource.cpp

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,108 @@ static unzFile unzOpenUtf8(const char* path)
8181
#endif
8282
}
8383

84+
namespace
85+
{
86+
struct SHttpCacheCopyResult
87+
{
88+
CChecksum checksum;
89+
uint64 size = 0;
90+
SString tempPath;
91+
SString error;
92+
};
93+
94+
bool ReplaceHttpCacheFile(const SString& strTempPath, const SString& strCachedFilePath, int* pOutErrorCode)
95+
{
96+
#ifdef WIN32
97+
if (MoveFileExW(FromUTF8(strTempPath), FromUTF8(strCachedFilePath), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0)
98+
{
99+
if (pOutErrorCode)
100+
*pOutErrorCode = GetLastError();
101+
return false;
102+
}
103+
104+
return true;
105+
#else
106+
if (rename(strTempPath, strCachedFilePath) != 0)
107+
{
108+
if (pOutErrorCode)
109+
*pOutErrorCode = errno;
110+
return false;
111+
}
112+
113+
return true;
114+
#endif
115+
}
116+
117+
SHttpCacheCopyResult CopyToHttpCacheAndChecksum(const SString& strPath, const SString& strCachedFilePath)
118+
{
119+
SHttpCacheCopyResult result;
120+
SString strTempPath = MakeUniquePath(SString("%s.tmp", *strCachedFilePath));
121+
122+
MakeSureDirExists(strTempPath);
123+
124+
FILE* pSource = File::FopenExclusive(strPath, "rb");
125+
if (!pSource)
126+
{
127+
result.error = SString("Could not read '%s' for HTTP cache\n", *strPath);
128+
return result;
129+
}
130+
131+
FILE* pTemp = File::Fopen(strTempPath, "wb");
132+
if (!pTemp)
133+
{
134+
fclose(pSource);
135+
result.error = SString("Could not write temporary HTTP cache file '%s'\n", *strTempPath);
136+
return result;
137+
}
138+
139+
CMD5Hasher md5;
140+
unsigned long crc = 0;
141+
char buffer[65536];
142+
bool ok = true;
143+
144+
md5.Init();
145+
146+
while (true)
147+
{
148+
size_t bytesRead = fread(buffer, 1, sizeof(buffer), pSource);
149+
if (bytesRead == 0)
150+
{
151+
if (ferror(pSource))
152+
ok = false;
153+
break;
154+
}
155+
156+
if (fwrite(buffer, 1, bytesRead, pTemp) != bytesRead)
157+
{
158+
ok = false;
159+
break;
160+
}
161+
162+
crc = CRCGenerator::GetCRCFromBuffer(buffer, bytesRead, crc);
163+
md5.Update(reinterpret_cast<unsigned char*>(buffer), static_cast<unsigned int>(bytesRead));
164+
result.size += bytesRead;
165+
}
166+
167+
bool closeOk = fclose(pTemp) == 0;
168+
fclose(pSource);
169+
170+
if (!ok || !closeOk)
171+
{
172+
FileDelete(strTempPath);
173+
result.error = SString("Could not copy '%s' to temporary HTTP cache file '%s'\n", *strPath, *strTempPath);
174+
return result;
175+
}
176+
177+
md5.Finalize();
178+
result.checksum.ulCRC = crc;
179+
memcpy(result.checksum.md5.data, md5.GetResult(), sizeof(result.checksum.md5.data));
180+
result.tempPath = strTempPath;
181+
182+
return result;
183+
}
184+
} // namespace
185+
84186
CResource::CResource(CResourceManager* pResourceManager, bool bIsZipped, const char* szAbsPath, const char* szResourceName)
85187
: m_pResourceManager(pResourceManager), m_bResourceIsZip(bIsZipped), m_strResourceName(SStringX(szResourceName)), m_strAbsPath(SStringX(szAbsPath))
86188
{
@@ -535,12 +637,12 @@ std::future<SString> CResource::GenerateChecksumForFile(CResourceFile* pResource
535637
return SString(std::get<std::string>(checksumOrError));
536638
}
537639

538-
pResourceFile->SetLastChecksum(std::get<CChecksum>(checksumOrError));
539-
pResourceFile->SetLastFileSizeHint(static_cast<uint>(FileSize(strPath)));
640+
CChecksum checksum = std::get<CChecksum>(checksumOrError);
641+
uint64 fileSize = FileSize(strPath);
540642

541643
// Check if file is blocked
542644
char szHashResult[33];
543-
CMD5Hasher::ConvertToHex(pResourceFile->GetLastChecksum().md5, szHashResult);
645+
CMD5Hasher::ConvertToHex(checksum.md5, szHashResult);
544646
SString strBlockReason = m_pResourceManager->GetBlockedFileReason(szHashResult);
545647

546648
if (!strBlockReason.empty())
@@ -565,13 +667,32 @@ std::future<SString> CResource::GenerateChecksumForFile(CResourceFile* pResource
565667

566668
CChecksum cachedChecksum = CChecksum::GenerateChecksumFromFileUnsafe(strCachedFilePath);
567669

568-
if (pResourceFile->GetLastChecksum() != cachedChecksum)
670+
if (checksum != cachedChecksum)
569671
{
570-
if (!FileCopy(strPath, strCachedFilePath))
672+
SHttpCacheCopyResult copyResult = CopyToHttpCacheAndChecksum(strPath, strCachedFilePath);
673+
if (!copyResult.error.empty())
674+
return copyResult.error;
675+
676+
CMD5Hasher::ConvertToHex(copyResult.checksum.md5, szHashResult);
677+
strBlockReason = m_pResourceManager->GetBlockedFileReason(szHashResult);
678+
679+
if (!strBlockReason.empty())
571680
{
572-
return SString("Could not copy '%s' to '%s'\n", *strPath, *strCachedFilePath);
681+
FileDelete(copyResult.tempPath);
682+
return SString("file '%s' is blocked (%s)", pResourceFile->GetName(), *strBlockReason);
573683
}
574684

685+
// Publish only after the complete cached file matches the checksum we will advertise.
686+
int replaceError = 0;
687+
if (!ReplaceHttpCacheFile(copyResult.tempPath, strCachedFilePath, &replaceError))
688+
{
689+
FileDelete(copyResult.tempPath);
690+
return SString("Could not replace HTTP cache file '%s' (error %d)\n", *strCachedFilePath, replaceError);
691+
}
692+
693+
checksum = copyResult.checksum;
694+
fileSize = copyResult.size;
695+
575696
// If script is 'no client cache', make sure there is no trace of it in the output dir
576697
if (pResourceFile->IsNoClientCache())
577698
FileDelete(pResourceFile->GetCachedPathFilename(true));
@@ -583,6 +704,9 @@ std::future<SString> CResource::GenerateChecksumForFile(CResourceFile* pResource
583704
break;
584705
}
585706

707+
pResourceFile->SetLastChecksum(checksum);
708+
pResourceFile->SetLastFileSizeHint(fileSize);
709+
586710
return SString();
587711
});
588712
}

0 commit comments

Comments
 (0)