Bug 1866777 - Disable test_race_cache_with_network.js on windows opt for frequent...
[gecko.git] / netwerk / cache2 / CacheFileMetadata.cpp
blob68d55b19884135031df60767d4079c77ddafc905
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "CacheLog.h"
6 #include "CacheFileMetadata.h"
8 #include "CacheFileIOManager.h"
9 #include "nsICacheEntry.h"
10 #include "CacheHashUtils.h"
11 #include "CacheFileChunk.h"
12 #include "CacheFileUtils.h"
13 #include "nsILoadContextInfo.h"
14 #include "nsICacheEntry.h" // for nsICacheEntryMetaDataVisitor
15 #include "nsIFile.h"
16 #include "mozilla/ScopeExit.h"
17 #include "mozilla/Telemetry.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/IntegerPrintfMacros.h"
20 #include "prnetdb.h"
22 namespace mozilla::net {
24 #define kMinMetadataRead 1024 // TODO find optimal value from telemetry
25 #define kAlignSize 4096
27 // Most of the cache entries fit into one chunk due to current chunk size. Make
28 // sure to tweak this value if kChunkSize is going to change.
29 #define kInitialHashArraySize 1
31 // Initial elements buffer size.
32 #define kInitialBufSize 64
34 // Max size of elements in bytes.
35 #define kMaxElementsSize (64 * 1024)
37 #define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
39 NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
41 CacheFileMetadata::CacheFileMetadata(
42 CacheFileHandle* aHandle, const nsACString& aKey,
43 NotNull<CacheFileUtils::CacheFileLock*> aLock)
44 : CacheMemoryConsumer(NORMAL),
45 mHandle(aHandle),
46 mOffset(-1),
47 mIsDirty(false),
48 mAnonymous(false),
49 mAllocExactSize(false),
50 mFirstRead(true),
51 mLock(aLock) {
52 LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
53 this, aHandle, PromiseFlatCString(aKey).get()));
55 memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
56 mMetaHdr.mVersion = kCacheEntryVersion;
57 mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
58 mKey = aKey;
60 DebugOnly<nsresult> rv{};
61 rv = ParseKey(aKey);
62 MOZ_ASSERT(NS_SUCCEEDED(rv));
65 CacheFileMetadata::CacheFileMetadata(
66 bool aMemoryOnly, bool aPinned, const nsACString& aKey,
67 NotNull<CacheFileUtils::CacheFileLock*> aLock)
68 : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL),
69 mIsDirty(true),
70 mAnonymous(false),
71 mAllocExactSize(false),
72 mFirstRead(true),
73 mLock(aLock) {
74 LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]", this,
75 PromiseFlatCString(aKey).get()));
77 memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
78 mMetaHdr.mVersion = kCacheEntryVersion;
79 if (aPinned) {
80 AddFlags(kCacheEntryIsPinned);
82 mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
83 mKey = aKey;
84 mMetaHdr.mKeySize = mKey.Length();
86 DebugOnly<nsresult> rv{};
87 rv = ParseKey(aKey);
88 MOZ_ASSERT(NS_SUCCEEDED(rv));
91 CacheFileMetadata::CacheFileMetadata()
92 : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */),
93 mIsDirty(false),
94 mAnonymous(false),
95 mAllocExactSize(false),
96 mFirstRead(true),
97 mLock(new CacheFileUtils::CacheFileLock()) {
98 LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]", this));
100 memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
103 CacheFileMetadata::~CacheFileMetadata() {
104 LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
106 MOZ_ASSERT(!mListener);
108 if (mHashArray) {
109 CacheFileUtils::FreeBuffer(mHashArray);
110 mHashArray = nullptr;
111 mHashArraySize = 0;
114 if (mBuf) {
115 CacheFileUtils::FreeBuffer(mBuf);
116 mBuf = nullptr;
117 mBufSize = 0;
121 void CacheFileMetadata::SetHandle(CacheFileHandle* aHandle) {
122 LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle));
124 MOZ_ASSERT(!mHandle);
126 mHandle = aHandle;
129 void CacheFileMetadata::ReadMetadata(CacheFileMetadataListener* aListener) {
130 LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this,
131 aListener));
133 MOZ_ASSERT(!mListener);
134 MOZ_ASSERT(!mHashArray);
135 MOZ_ASSERT(!mBuf);
136 MOZ_ASSERT(!mWriteBuf);
138 nsresult rv;
140 int64_t size = mHandle->FileSize();
141 MOZ_ASSERT(size != -1);
143 if (size == 0) {
144 // this is a new entry
145 LOG(
146 ("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
147 "metadata. [this=%p]",
148 this));
150 InitEmptyMetadata();
151 aListener->OnMetadataRead(NS_OK);
152 return;
155 if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2 * sizeof(uint32_t))) {
156 // there must be at least checksum, header and offset
157 LOG(
158 ("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
159 "empty metadata. [this=%p, filesize=%" PRId64 "]",
160 this, size));
162 InitEmptyMetadata();
163 aListener->OnMetadataRead(NS_OK);
164 return;
167 // Set offset so that we read at least kMinMetadataRead if the file is big
168 // enough.
169 int64_t offset;
170 if (size < kMinMetadataRead) {
171 offset = 0;
172 } else {
173 offset = size - kMinMetadataRead;
176 // round offset to kAlignSize blocks
177 offset = (offset / kAlignSize) * kAlignSize;
179 mBufSize = size - offset;
180 mBuf = static_cast<char*>(moz_xmalloc(mBufSize));
182 DoMemoryReport(MemoryUsage());
184 LOG(
185 ("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
186 "offset=%" PRId64 ", filesize=%" PRId64 " [this=%p]",
187 offset, size, this));
189 mReadStart = mozilla::TimeStamp::Now();
190 mListener = aListener;
191 rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
192 if (NS_FAILED(rv)) {
193 LOG(
194 ("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
195 " synchronously, creating empty metadata. [this=%p, rv=0x%08" PRIx32
196 "]",
197 this, static_cast<uint32_t>(rv)));
199 mListener = nullptr;
200 InitEmptyMetadata();
201 aListener->OnMetadataRead(NS_OK);
205 uint32_t CacheFileMetadata::CalcMetadataSize(uint32_t aElementsSize,
206 uint32_t aHashCount) {
207 return sizeof(uint32_t) + // hash of the metadata
208 aHashCount * sizeof(CacheHash::Hash16_t) + // array of chunk hashes
209 sizeof(CacheFileMetadataHeader) + // metadata header
210 mKey.Length() + 1 + // key with trailing null
211 aElementsSize + // elements
212 sizeof(uint32_t); // offset
215 nsresult CacheFileMetadata::WriteMetadata(
216 uint32_t aOffset, CacheFileMetadataListener* aListener) {
217 LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
218 this, aOffset, aListener));
220 MOZ_ASSERT(!mListener);
221 MOZ_ASSERT(!mWriteBuf);
223 nsresult rv;
225 mIsDirty = false;
227 mWriteBuf =
228 static_cast<char*>(malloc(CalcMetadataSize(mElementsSize, mHashCount)));
229 if (!mWriteBuf) {
230 return NS_ERROR_OUT_OF_MEMORY;
233 char* p = mWriteBuf + sizeof(uint32_t);
234 if (mHashCount) {
235 memcpy(p, mHashArray, mHashCount * sizeof(CacheHash::Hash16_t));
236 p += mHashCount * sizeof(CacheHash::Hash16_t);
238 mMetaHdr.WriteToBuf(p);
239 p += sizeof(CacheFileMetadataHeader);
240 memcpy(p, mKey.get(), mKey.Length());
241 p += mKey.Length();
242 *p = 0;
243 p++;
244 if (mElementsSize) {
245 memcpy(p, mBuf, mElementsSize);
246 p += mElementsSize;
249 CacheHash::Hash32_t hash;
250 hash = CacheHash::Hash(mWriteBuf + sizeof(uint32_t),
251 p - mWriteBuf - sizeof(uint32_t));
252 NetworkEndian::writeUint32(mWriteBuf, hash);
254 NetworkEndian::writeUint32(p, aOffset);
255 p += sizeof(uint32_t);
257 char* writeBuffer = mWriteBuf;
258 if (aListener) {
259 mListener = aListener;
260 } else {
261 // We are not going to pass |this| as a callback so the buffer will be
262 // released by CacheFileIOManager. Just null out mWriteBuf here.
263 mWriteBuf = nullptr;
266 rv = CacheFileIOManager::Write(mHandle, aOffset, writeBuffer, p - writeBuffer,
267 true, true, aListener ? this : nullptr);
268 if (NS_FAILED(rv)) {
269 LOG(
270 ("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
271 "failed synchronously. [this=%p, rv=0x%08" PRIx32 "]",
272 this, static_cast<uint32_t>(rv)));
274 mListener = nullptr;
275 if (mWriteBuf) {
276 CacheFileUtils::FreeBuffer(mWriteBuf);
277 mWriteBuf = nullptr;
279 NS_ENSURE_SUCCESS(rv, rv);
282 DoMemoryReport(MemoryUsage());
284 return NS_OK;
287 nsresult CacheFileMetadata::SyncReadMetadata(nsIFile* aFile) {
288 LOG(("CacheFileMetadata::SyncReadMetadata() [this=%p]", this));
290 MOZ_ASSERT(!mListener);
291 MOZ_ASSERT(!mHandle);
292 MOZ_ASSERT(!mHashArray);
293 MOZ_ASSERT(!mBuf);
294 MOZ_ASSERT(!mWriteBuf);
295 MOZ_ASSERT(mKey.IsEmpty());
297 nsresult rv;
299 int64_t fileSize;
300 rv = aFile->GetFileSize(&fileSize);
301 if (NS_FAILED(rv)) {
302 // Don't bloat the console
303 return rv;
306 PRFileDesc* fd;
307 rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
308 NS_ENSURE_SUCCESS(rv, rv);
310 int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET);
311 if (offset == -1) {
312 PR_Close(fd);
313 return NS_ERROR_FAILURE;
316 uint32_t metaOffset;
317 int32_t bytesRead = PR_Read(fd, &metaOffset, sizeof(uint32_t));
318 if (bytesRead != sizeof(uint32_t)) {
319 PR_Close(fd);
320 return NS_ERROR_FAILURE;
323 metaOffset = NetworkEndian::readUint32(&metaOffset);
324 if (metaOffset > fileSize) {
325 PR_Close(fd);
326 return NS_ERROR_FAILURE;
329 mBuf = static_cast<char*>(malloc(fileSize - metaOffset));
330 if (!mBuf) {
331 return NS_ERROR_OUT_OF_MEMORY;
333 mBufSize = fileSize - metaOffset;
335 DoMemoryReport(MemoryUsage());
337 offset = PR_Seek64(fd, metaOffset, PR_SEEK_SET);
338 if (offset == -1) {
339 PR_Close(fd);
340 return NS_ERROR_FAILURE;
343 bytesRead = PR_Read(fd, mBuf, mBufSize);
344 PR_Close(fd);
345 if (bytesRead != static_cast<int32_t>(mBufSize)) {
346 return NS_ERROR_FAILURE;
349 rv = ParseMetadata(metaOffset, 0, false);
350 NS_ENSURE_SUCCESS(rv, rv);
352 return NS_OK;
355 const char* CacheFileMetadata::GetElement(const char* aKey) {
356 const char* data = mBuf;
357 const char* limit = mBuf + mElementsSize;
359 while (data != limit) {
360 size_t maxLen = limit - data;
361 size_t keyLen = strnlen(data, maxLen);
362 MOZ_RELEASE_ASSERT(keyLen != maxLen,
363 "Metadata elements corrupted. Key "
364 "isn't null terminated!");
365 MOZ_RELEASE_ASSERT(keyLen + 1 != maxLen,
366 "Metadata elements corrupted. "
367 "There is no value for the key!");
369 const char* value = data + keyLen + 1;
370 maxLen = limit - value;
371 size_t valueLen = strnlen(value, maxLen);
372 MOZ_RELEASE_ASSERT(valueLen != maxLen,
373 "Metadata elements corrupted. Value "
374 "isn't null terminated!");
376 if (strcmp(data, aKey) == 0) {
377 LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
378 this, aKey));
379 return value;
382 // point to next pair
383 data += keyLen + valueLen + 2;
385 LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
386 this, aKey));
387 return nullptr;
390 nsresult CacheFileMetadata::SetElement(const char* aKey, const char* aValue) {
391 LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]", this,
392 aKey, aValue));
394 mLock->Lock().AssertCurrentThreadOwns();
396 MarkDirty();
398 nsresult rv;
400 const uint32_t keySize = strlen(aKey) + 1;
401 char* pos = const_cast<char*>(GetElement(aKey));
403 if (!aValue) {
404 // No value means remove the key/value pair completely, if existing
405 if (pos) {
406 uint32_t oldValueSize = strlen(pos) + 1;
407 uint32_t offset = pos - mBuf;
408 uint32_t remainder = mElementsSize - (offset + oldValueSize);
410 memmove(pos - keySize, pos + oldValueSize, remainder);
411 mElementsSize -= keySize + oldValueSize;
413 return NS_OK;
416 const uint32_t valueSize = strlen(aValue) + 1;
417 uint32_t newSize = mElementsSize + valueSize;
418 if (pos) {
419 const uint32_t oldValueSize = strlen(pos) + 1;
420 const uint32_t offset = pos - mBuf;
421 const uint32_t remainder = mElementsSize - (offset + oldValueSize);
423 // Update the value in place
424 newSize -= oldValueSize;
425 rv = EnsureBuffer(newSize);
426 if (NS_FAILED(rv)) {
427 return rv;
430 // Move the remainder to the right place
431 pos = mBuf + offset;
432 memmove(pos + valueSize, pos + oldValueSize, remainder);
433 } else {
434 // allocate new meta data element
435 newSize += keySize;
436 rv = EnsureBuffer(newSize);
437 if (NS_FAILED(rv)) {
438 return rv;
441 // Add after last element
442 pos = mBuf + mElementsSize;
443 memcpy(pos, aKey, keySize);
444 pos += keySize;
447 // Update value
448 memcpy(pos, aValue, valueSize);
449 mElementsSize = newSize;
451 return NS_OK;
454 void CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor* aVisitor) {
455 const char* data = mBuf;
456 const char* limit = mBuf + mElementsSize;
458 while (data < limit) {
459 // Point to the value part
460 const char* value = data + strlen(data) + 1;
461 MOZ_ASSERT(value < limit, "Metadata elements corrupted");
463 aVisitor->OnMetaDataElement(data, value);
465 // Skip value part
466 data = value + strlen(value) + 1;
469 MOZ_ASSERT(data == limit, "Metadata elements corrupted");
472 CacheHash::Hash16_t CacheFileMetadata::GetHash(uint32_t aIndex) {
473 mLock->Lock().AssertCurrentThreadOwns();
475 MOZ_ASSERT(aIndex < mHashCount);
476 return NetworkEndian::readUint16(&mHashArray[aIndex]);
479 nsresult CacheFileMetadata::SetHash(uint32_t aIndex,
480 CacheHash::Hash16_t aHash) {
481 LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]", this, aIndex,
482 aHash));
484 mLock->Lock().AssertCurrentThreadOwns();
486 MarkDirty();
488 MOZ_ASSERT(aIndex <= mHashCount);
490 if (aIndex > mHashCount) {
491 return NS_ERROR_INVALID_ARG;
493 if (aIndex == mHashCount) {
494 if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) {
495 // reallocate hash array buffer
496 if (mHashArraySize == 0) {
497 mHashArraySize = kInitialHashArraySize * sizeof(CacheHash::Hash16_t);
498 } else {
499 mHashArraySize *= 2;
501 mHashArray = static_cast<CacheHash::Hash16_t*>(
502 moz_xrealloc(mHashArray, mHashArraySize));
505 mHashCount++;
508 NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
510 DoMemoryReport(MemoryUsage());
512 return NS_OK;
515 nsresult CacheFileMetadata::RemoveHash(uint32_t aIndex) {
516 LOG(("CacheFileMetadata::RemoveHash() [this=%p, idx=%d]", this, aIndex));
518 mLock->Lock().AssertCurrentThreadOwns();
520 MarkDirty();
522 MOZ_ASSERT((aIndex + 1) == mHashCount, "Can remove only last hash!");
524 if (aIndex + 1 != mHashCount) {
525 return NS_ERROR_INVALID_ARG;
528 mHashCount--;
529 return NS_OK;
532 void CacheFileMetadata::AddFlags(uint32_t aFlags) {
533 MarkDirty(false);
534 mMetaHdr.mFlags |= aFlags;
537 void CacheFileMetadata::RemoveFlags(uint32_t aFlags) {
538 MarkDirty(false);
539 mMetaHdr.mFlags &= ~aFlags;
542 void CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime) {
543 LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
544 this, aExpirationTime));
546 MarkDirty(false);
547 mMetaHdr.mExpirationTime = aExpirationTime;
550 void CacheFileMetadata::SetFrecency(uint32_t aFrecency) {
551 LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]", this,
552 (double)aFrecency));
554 MarkDirty(false);
555 mMetaHdr.mFrecency = aFrecency;
558 void CacheFileMetadata::OnFetched() {
559 MarkDirty(false);
561 mMetaHdr.mLastFetched = NOW_SECONDS();
562 ++mMetaHdr.mFetchCount;
565 void CacheFileMetadata::MarkDirty(bool aUpdateLastModified) {
566 mIsDirty = true;
567 if (aUpdateLastModified) {
568 mMetaHdr.mLastModified = NOW_SECONDS();
572 nsresult CacheFileMetadata::OnFileOpened(CacheFileHandle* aHandle,
573 nsresult aResult) {
574 MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
575 return NS_ERROR_UNEXPECTED;
578 nsresult CacheFileMetadata::OnDataWritten(CacheFileHandle* aHandle,
579 const char* aBuf, nsresult aResult) {
580 LOG(
581 ("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, "
582 "result=0x%08" PRIx32 "]",
583 this, aHandle, static_cast<uint32_t>(aResult)));
585 nsCOMPtr<CacheFileMetadataListener> listener;
587 MutexAutoLock lock(mLock->Lock());
589 MOZ_ASSERT(mListener);
590 MOZ_ASSERT(mWriteBuf);
592 CacheFileUtils::FreeBuffer(mWriteBuf);
593 mWriteBuf = nullptr;
595 mListener.swap(listener);
596 DoMemoryReport(MemoryUsage());
599 listener->OnMetadataWritten(aResult);
601 return NS_OK;
604 nsresult CacheFileMetadata::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
605 nsresult aResult) {
606 LOG((
607 "CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32
608 "]",
609 this, aHandle, static_cast<uint32_t>(aResult)));
611 MOZ_ASSERT(mListener);
613 nsresult rv;
614 nsCOMPtr<CacheFileMetadataListener> listener;
616 auto notifyListenerOutsideLock = mozilla::MakeScopeExit([&listener] {
617 if (listener) {
618 listener->OnMetadataRead(NS_OK);
622 MutexAutoLock lock(mLock->Lock());
624 if (NS_FAILED(aResult)) {
625 LOG(
626 ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
627 ", creating empty metadata. [this=%p, rv=0x%08" PRIx32 "]",
628 this, static_cast<uint32_t>(aResult)));
630 InitEmptyMetadata();
632 mListener.swap(listener);
633 return NS_OK;
636 if (mFirstRead) {
637 Telemetry::AccumulateTimeDelta(
638 Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_TIME_MS, mReadStart);
639 } else {
640 Telemetry::AccumulateTimeDelta(
641 Telemetry::NETWORK_CACHE_METADATA_SECOND_READ_TIME_MS, mReadStart);
644 // check whether we have read all necessary data
645 uint32_t realOffset =
646 NetworkEndian::readUint32(mBuf + mBufSize - sizeof(uint32_t));
648 int64_t size = mHandle->FileSize();
649 MOZ_ASSERT(size != -1);
651 if (realOffset >= size) {
652 LOG(
653 ("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating "
654 "empty metadata. [this=%p, realOffset=%u, size=%" PRId64 "]",
655 this, realOffset, size));
657 InitEmptyMetadata();
659 mListener.swap(listener);
660 return NS_OK;
663 uint32_t maxHashCount = size / kChunkSize;
664 uint32_t maxMetadataSize = CalcMetadataSize(kMaxElementsSize, maxHashCount);
665 if (size - realOffset > maxMetadataSize) {
666 LOG(
667 ("CacheFileMetadata::OnDataRead() - Invalid realOffset, metadata would "
668 "be too big, creating empty metadata. [this=%p, realOffset=%u, "
669 "maxMetadataSize=%u, size=%" PRId64 "]",
670 this, realOffset, maxMetadataSize, size));
672 InitEmptyMetadata();
674 mListener.swap(listener);
675 return NS_OK;
678 uint32_t usedOffset = size - mBufSize;
680 if (realOffset < usedOffset) {
681 uint32_t missing = usedOffset - realOffset;
682 // we need to read more data
683 char* newBuf = static_cast<char*>(realloc(mBuf, mBufSize + missing));
684 if (!newBuf) {
685 LOG(
686 ("CacheFileMetadata::OnDataRead() - Error allocating %d more bytes "
687 "for the missing part of the metadata, creating empty metadata. "
688 "[this=%p]",
689 missing, this));
691 InitEmptyMetadata();
693 mListener.swap(listener);
694 return NS_OK;
697 mBuf = newBuf;
698 memmove(mBuf + missing, mBuf, mBufSize);
699 mBufSize += missing;
701 DoMemoryReport(MemoryUsage());
703 LOG(
704 ("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
705 "have full metadata. [this=%p]",
706 missing, this));
708 mFirstRead = false;
709 mReadStart = mozilla::TimeStamp::Now();
710 rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
711 if (NS_FAILED(rv)) {
712 LOG(
713 ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() "
714 "failed synchronously, creating empty metadata. [this=%p, "
715 "rv=0x%08" PRIx32 "]",
716 this, static_cast<uint32_t>(rv)));
718 InitEmptyMetadata();
720 mListener.swap(listener);
721 return NS_OK;
724 return NS_OK;
727 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_METADATA_SIZE_2,
728 size - realOffset);
730 // We have all data according to offset information at the end of the entry.
731 // Try to parse it.
732 rv = ParseMetadata(realOffset, realOffset - usedOffset, true);
733 if (NS_FAILED(rv)) {
734 LOG(
735 ("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
736 "empty metadata. [this=%p]",
737 this));
738 InitEmptyMetadata();
739 } else {
740 // Shrink elements buffer.
741 mBuf = static_cast<char*>(moz_xrealloc(mBuf, mElementsSize));
742 mBufSize = mElementsSize;
744 // There is usually no or just one call to SetMetadataElement() when the
745 // metadata is parsed from disk. Avoid allocating power of two sized buffer
746 // which we do in case of newly created metadata.
747 mAllocExactSize = true;
750 mListener.swap(listener);
752 return NS_OK;
755 nsresult CacheFileMetadata::OnFileDoomed(CacheFileHandle* aHandle,
756 nsresult aResult) {
757 MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
758 return NS_ERROR_UNEXPECTED;
761 nsresult CacheFileMetadata::OnEOFSet(CacheFileHandle* aHandle,
762 nsresult aResult) {
763 MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
764 return NS_ERROR_UNEXPECTED;
767 nsresult CacheFileMetadata::OnFileRenamed(CacheFileHandle* aHandle,
768 nsresult aResult) {
769 MOZ_CRASH("CacheFileMetadata::OnFileRenamed should not be called!");
770 return NS_ERROR_UNEXPECTED;
773 void CacheFileMetadata::InitEmptyMetadata() {
774 if (mBuf) {
775 CacheFileUtils::FreeBuffer(mBuf);
776 mBuf = nullptr;
777 mBufSize = 0;
779 mAllocExactSize = false;
780 mOffset = 0;
781 mMetaHdr.mVersion = kCacheEntryVersion;
782 mMetaHdr.mFetchCount = 0;
783 mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
784 mMetaHdr.mKeySize = mKey.Length();
786 // Deliberately not touching the "kCacheEntryIsPinned" flag.
788 DoMemoryReport(MemoryUsage());
790 // We're creating a new entry. If there is any old data truncate it.
791 if (mHandle) {
792 mHandle->SetPinned(Pinned());
793 // We can pronounce the handle as invalid now, because it simply
794 // doesn't have the correct metadata. This will cause IO operations
795 // be bypassed during shutdown (mainly dooming it, when a channel
796 // is canceled by closing the window.)
797 mHandle->SetInvalid();
798 if (mHandle->FileExists() && mHandle->FileSize()) {
799 CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
804 nsresult CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset,
805 uint32_t aBufOffset, bool aHaveKey) {
806 LOG(
807 ("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
808 "bufOffset=%d, haveKey=%u]",
809 this, aMetaOffset, aBufOffset, aHaveKey));
811 nsresult rv;
813 uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
814 uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
815 uint32_t hashCount = aMetaOffset / kChunkSize;
816 if (aMetaOffset % kChunkSize) hashCount++;
817 uint32_t hashesLen = hashCount * sizeof(CacheHash::Hash16_t);
818 uint32_t hdrOffset = hashesOffset + hashesLen;
819 uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);
821 LOG(
822 ("CacheFileMetadata::ParseMetadata() [this=%p]\n metaposOffset=%d\n "
823 "hashesOffset=%d\n hashCount=%d\n hashesLen=%d\n hdfOffset=%d\n "
824 "keyOffset=%d\n",
825 this, metaposOffset, hashesOffset, hashCount, hashesLen, hdrOffset,
826 keyOffset));
828 if (keyOffset > metaposOffset) {
829 LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
830 this));
831 return NS_ERROR_FILE_CORRUPTED;
834 mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
836 if (mMetaHdr.mVersion == 1) {
837 // Backward compatibility before we've added flags to the header
838 keyOffset -= sizeof(uint32_t);
839 } else if (mMetaHdr.mVersion == 2) {
840 // Version 2 just lacks the ability to store alternative data. Nothing to do
841 // here.
842 } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
843 LOG(
844 ("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
845 "[version=0x%x, this=%p]",
846 mMetaHdr.mVersion, this));
847 return NS_ERROR_UNEXPECTED;
850 // Update the version stored in the header to make writes
851 // store the header in the current version form.
852 mMetaHdr.mVersion = kCacheEntryVersion;
854 uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
856 if (elementsOffset > metaposOffset) {
857 LOG(
858 ("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
859 "[this=%p]",
860 elementsOffset, this));
861 return NS_ERROR_FILE_CORRUPTED;
864 // check that key ends with \0
865 if (mBuf[elementsOffset - 1] != 0) {
866 LOG(
867 ("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
868 "[this=%p]",
869 this));
870 return NS_ERROR_FILE_CORRUPTED;
873 if (!aHaveKey) {
874 // get the key form metadata
875 mKey.Assign(mBuf + keyOffset, mMetaHdr.mKeySize);
877 rv = ParseKey(mKey);
878 if (NS_FAILED(rv)) return rv;
879 } else {
880 if (mMetaHdr.mKeySize != mKey.Length()) {
881 LOG(
882 ("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s "
883 "[this=%p]",
884 nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
885 return NS_ERROR_FILE_CORRUPTED;
888 if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
889 LOG(
890 ("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s "
891 "[this=%p]",
892 nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
893 return NS_ERROR_FILE_CORRUPTED;
897 // check metadata hash (data from hashesOffset to metaposOffset)
898 CacheHash::Hash32_t hashComputed, hashExpected;
899 hashComputed =
900 CacheHash::Hash(mBuf + hashesOffset, metaposOffset - hashesOffset);
901 hashExpected = NetworkEndian::readUint32(mBuf + aBufOffset);
903 if (hashComputed != hashExpected) {
904 LOG(
905 ("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
906 "the metadata is %x, hash in file is %x [this=%p]",
907 hashComputed, hashExpected, this));
908 return NS_ERROR_FILE_CORRUPTED;
911 // check elements
912 rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
913 if (NS_FAILED(rv)) return rv;
915 if (mHandle) {
916 if (!mHandle->SetPinned(Pinned())) {
917 LOG(
918 ("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
919 "pinning state, truncate the file [this=%p, pinned=%d]",
920 this, Pinned()));
921 return NS_ERROR_FILE_CORRUPTED;
925 mHashArraySize = hashesLen;
926 mHashCount = hashCount;
927 if (mHashArraySize) {
928 mHashArray = static_cast<CacheHash::Hash16_t*>(moz_xmalloc(mHashArraySize));
929 memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
932 MarkDirty();
934 mElementsSize = metaposOffset - elementsOffset;
935 memmove(mBuf, mBuf + elementsOffset, mElementsSize);
936 mOffset = aMetaOffset;
938 DoMemoryReport(MemoryUsage());
940 return NS_OK;
943 nsresult CacheFileMetadata::CheckElements(const char* aBuf, uint32_t aSize) {
944 if (aSize) {
945 // Check if the metadata ends with a zero byte.
946 if (aBuf[aSize - 1] != 0) {
947 NS_ERROR("Metadata elements are not null terminated");
948 LOG(
949 ("CacheFileMetadata::CheckElements() - Elements are not null "
950 "terminated. [this=%p]",
951 this));
952 return NS_ERROR_FILE_CORRUPTED;
954 // Check that there are an even number of zero bytes
955 // to match the pattern { key \0 value \0 }
956 bool odd = false;
957 for (uint32_t i = 0; i < aSize; i++) {
958 if (aBuf[i] == 0) odd = !odd;
960 if (odd) {
961 NS_ERROR("Metadata elements are malformed");
962 LOG(
963 ("CacheFileMetadata::CheckElements() - Elements are malformed. "
964 "[this=%p]",
965 this));
966 return NS_ERROR_FILE_CORRUPTED;
969 return NS_OK;
972 nsresult CacheFileMetadata::EnsureBuffer(uint32_t aSize) {
973 if (aSize > kMaxElementsSize) {
974 return NS_ERROR_FAILURE;
977 if (mBufSize < aSize) {
978 if (mAllocExactSize) {
979 // If this is not the only allocation, use power of two for following
980 // allocations.
981 mAllocExactSize = false;
982 } else {
983 // find smallest power of 2 greater than or equal to aSize
984 --aSize;
985 aSize |= aSize >> 1;
986 aSize |= aSize >> 2;
987 aSize |= aSize >> 4;
988 aSize |= aSize >> 8;
989 aSize |= aSize >> 16;
990 ++aSize;
993 if (aSize < kInitialBufSize) {
994 aSize = kInitialBufSize;
997 char* newBuf = static_cast<char*>(realloc(mBuf, aSize));
998 if (!newBuf) {
999 return NS_ERROR_OUT_OF_MEMORY;
1001 mBufSize = aSize;
1002 mBuf = newBuf;
1004 DoMemoryReport(MemoryUsage());
1007 return NS_OK;
1010 nsresult CacheFileMetadata::ParseKey(const nsACString& aKey) {
1011 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
1012 NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);
1014 mAnonymous = info->IsAnonymous();
1015 mOriginAttributes = *info->OriginAttributesPtr();
1017 return NS_OK;
1020 // Memory reporting
1022 size_t CacheFileMetadata::SizeOfExcludingThis(
1023 mozilla::MallocSizeOf mallocSizeOf) const {
1024 size_t n = 0;
1025 // mHandle reported via CacheFileIOManager.
1026 n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1027 n += mallocSizeOf(mHashArray);
1028 n += mallocSizeOf(mBuf);
1029 // Ignore mWriteBuf, it's not safe to access it when metadata is being
1030 // written and it's null otherwise.
1031 // mListener is usually the owning CacheFile.
1033 return n;
1036 size_t CacheFileMetadata::SizeOfIncludingThis(
1037 mozilla::MallocSizeOf mallocSizeOf) const {
1038 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1041 } // namespace mozilla::net