Bug 1494162 - Part 45: Lazy load Menu and MenuItem in TabBar. r=pbro
[gecko.git] / netwerk / cache2 / CacheFileMetadata.cpp
blob611bfe6a7835ccd673cc6725fe2adef19363612f
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 "../cache/nsCacheUtils.h"
16 #include "nsIFile.h"
17 #include "mozilla/Telemetry.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/IntegerPrintfMacros.h"
20 #include "prnetdb.h"
23 namespace mozilla {
24 namespace net {
26 #define kMinMetadataRead 1024 // TODO find optimal value from telemetry
27 #define kAlignSize 4096
29 // Most of the cache entries fit into one chunk due to current chunk size. Make
30 // sure to tweak this value if kChunkSize is going to change.
31 #define kInitialHashArraySize 1
33 // Initial elements buffer size.
34 #define kInitialBufSize 64
36 // Max size of elements in bytes.
37 #define kMaxElementsSize 64*1024
39 #define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
41 NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
43 CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey)
44 : CacheMemoryConsumer(NORMAL)
45 , mHandle(aHandle)
46 , mHashArray(nullptr)
47 , mHashArraySize(0)
48 , mHashCount(0)
49 , mOffset(-1)
50 , mBuf(nullptr)
51 , mBufSize(0)
52 , mWriteBuf(nullptr)
53 , mElementsSize(0)
54 , mIsDirty(false)
55 , mAnonymous(false)
56 , mAllocExactSize(false)
57 , mFirstRead(true)
59 LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
60 this, aHandle, PromiseFlatCString(aKey).get()));
62 memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
63 mMetaHdr.mVersion = kCacheEntryVersion;
64 mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
65 mKey = aKey;
67 DebugOnly<nsresult> rv;
68 rv = ParseKey(aKey);
69 MOZ_ASSERT(NS_SUCCEEDED(rv));
72 CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, bool aPinned, const nsACString &aKey)
73 : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL)
74 , mHandle(nullptr)
75 , mHashArray(nullptr)
76 , mHashArraySize(0)
77 , mHashCount(0)
78 , mOffset(0)
79 , mBuf(nullptr)
80 , mBufSize(0)
81 , mWriteBuf(nullptr)
82 , mElementsSize(0)
83 , mIsDirty(true)
84 , mAnonymous(false)
85 , mAllocExactSize(false)
86 , mFirstRead(true)
88 LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]",
89 this, PromiseFlatCString(aKey).get()));
91 memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
92 mMetaHdr.mVersion = kCacheEntryVersion;
93 if (aPinned) {
94 AddFlags(kCacheEntryIsPinned);
96 mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
97 mKey = aKey;
98 mMetaHdr.mKeySize = mKey.Length();
100 DebugOnly<nsresult> rv;
101 rv = ParseKey(aKey);
102 MOZ_ASSERT(NS_SUCCEEDED(rv));
105 CacheFileMetadata::CacheFileMetadata()
106 : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */)
107 , mHandle(nullptr)
108 , mHashArray(nullptr)
109 , mHashArraySize(0)
110 , mHashCount(0)
111 , mOffset(0)
112 , mBuf(nullptr)
113 , mBufSize(0)
114 , mWriteBuf(nullptr)
115 , mElementsSize(0)
116 , mIsDirty(false)
117 , mAnonymous(false)
118 , mAllocExactSize(false)
119 , mFirstRead(true)
121 LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]", this));
123 memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
126 CacheFileMetadata::~CacheFileMetadata()
128 LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
130 MOZ_ASSERT(!mListener);
132 if (mHashArray) {
133 CacheFileUtils::FreeBuffer(mHashArray);
134 mHashArray = nullptr;
135 mHashArraySize = 0;
138 if (mBuf) {
139 CacheFileUtils::FreeBuffer(mBuf);
140 mBuf = nullptr;
141 mBufSize = 0;
145 void
146 CacheFileMetadata::SetHandle(CacheFileHandle *aHandle)
148 LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle));
150 MOZ_ASSERT(!mHandle);
152 mHandle = aHandle;
155 nsresult
156 CacheFileMetadata::GetKey(nsACString &_retval)
158 _retval = mKey;
159 return NS_OK;
162 nsresult
163 CacheFileMetadata::ReadMetadata(CacheFileMetadataListener *aListener)
165 LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this, aListener));
167 MOZ_ASSERT(!mListener);
168 MOZ_ASSERT(!mHashArray);
169 MOZ_ASSERT(!mBuf);
170 MOZ_ASSERT(!mWriteBuf);
172 nsresult rv;
174 int64_t size = mHandle->FileSize();
175 MOZ_ASSERT(size != -1);
177 if (size == 0) {
178 // this is a new entry
179 LOG(("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
180 "metadata. [this=%p]", this));
182 InitEmptyMetadata();
183 aListener->OnMetadataRead(NS_OK);
184 return NS_OK;
187 if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2*sizeof(uint32_t))) {
188 // there must be at least checksum, header and offset
189 LOG(("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
190 "empty metadata. [this=%p, filesize=%" PRId64 "]", this, size));
192 InitEmptyMetadata();
193 aListener->OnMetadataRead(NS_OK);
194 return NS_OK;
197 // Set offset so that we read at least kMinMetadataRead if the file is big
198 // enough.
199 int64_t offset;
200 if (size < kMinMetadataRead) {
201 offset = 0;
202 } else {
203 offset = size - kMinMetadataRead;
206 // round offset to kAlignSize blocks
207 offset = (offset / kAlignSize) * kAlignSize;
209 mBufSize = size - offset;
210 mBuf = static_cast<char *>(moz_xmalloc(mBufSize));
212 DoMemoryReport(MemoryUsage());
214 LOG(("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
215 "offset=%" PRId64 ", filesize=%" PRId64 " [this=%p]", offset, size, this));
217 mReadStart = mozilla::TimeStamp::Now();
218 mListener = aListener;
219 rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
220 if (NS_FAILED(rv)) {
221 LOG(("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
222 " synchronously, creating empty metadata. [this=%p, rv=0x%08" PRIx32 "]",
223 this, static_cast<uint32_t>(rv)));
225 mListener = nullptr;
226 InitEmptyMetadata();
227 aListener->OnMetadataRead(NS_OK);
228 return NS_OK;
231 return NS_OK;
234 uint32_t
235 CacheFileMetadata::CalcMetadataSize(uint32_t aElementsSize, uint32_t aHashCount)
237 return sizeof(uint32_t) + // hash of the metadata
238 aHashCount * sizeof(CacheHash::Hash16_t) + // array of chunk hashes
239 sizeof(CacheFileMetadataHeader) + // metadata header
240 mKey.Length() + 1 + // key with trailing null
241 aElementsSize + // elements
242 sizeof(uint32_t); // offset
245 nsresult
246 CacheFileMetadata::WriteMetadata(uint32_t aOffset,
247 CacheFileMetadataListener *aListener)
249 LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
250 this, aOffset, aListener));
252 MOZ_ASSERT(!mListener);
253 MOZ_ASSERT(!mWriteBuf);
255 nsresult rv;
257 mIsDirty = false;
259 mWriteBuf = static_cast<char *>(malloc(CalcMetadataSize(mElementsSize,
260 mHashCount)));
261 if (!mWriteBuf) {
262 return NS_ERROR_OUT_OF_MEMORY;
265 char *p = mWriteBuf + sizeof(uint32_t);
266 if (mHashCount) {
267 memcpy(p, mHashArray, mHashCount * sizeof(CacheHash::Hash16_t));
268 p += mHashCount * sizeof(CacheHash::Hash16_t);
270 mMetaHdr.WriteToBuf(p);
271 p += sizeof(CacheFileMetadataHeader);
272 memcpy(p, mKey.get(), mKey.Length());
273 p += mKey.Length();
274 *p = 0;
275 p++;
276 if (mElementsSize) {
277 memcpy(p, mBuf, mElementsSize);
278 p += mElementsSize;
281 CacheHash::Hash32_t hash;
282 hash = CacheHash::Hash(mWriteBuf + sizeof(uint32_t),
283 p - mWriteBuf - sizeof(uint32_t));
284 NetworkEndian::writeUint32(mWriteBuf, hash);
286 NetworkEndian::writeUint32(p, aOffset);
287 p += sizeof(uint32_t);
289 char * writeBuffer = mWriteBuf;
290 if (aListener) {
291 mListener = aListener;
292 } else {
293 // We are not going to pass |this| as a callback so the buffer will be
294 // released by CacheFileIOManager. Just null out mWriteBuf here.
295 mWriteBuf = nullptr;
298 rv = CacheFileIOManager::Write(mHandle, aOffset, writeBuffer, p - writeBuffer,
299 true, true, aListener ? this : nullptr);
300 if (NS_FAILED(rv)) {
301 LOG(("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
302 "failed synchronously. [this=%p, rv=0x%08" PRIx32 "]",
303 this, static_cast<uint32_t>(rv)));
305 mListener = nullptr;
306 if (mWriteBuf) {
307 CacheFileUtils::FreeBuffer(mWriteBuf);
308 mWriteBuf = nullptr;
310 NS_ENSURE_SUCCESS(rv, rv);
313 DoMemoryReport(MemoryUsage());
315 return NS_OK;
318 nsresult
319 CacheFileMetadata::SyncReadMetadata(nsIFile *aFile)
321 LOG(("CacheFileMetadata::SyncReadMetadata() [this=%p]", this));
323 MOZ_ASSERT(!mListener);
324 MOZ_ASSERT(!mHandle);
325 MOZ_ASSERT(!mHashArray);
326 MOZ_ASSERT(!mBuf);
327 MOZ_ASSERT(!mWriteBuf);
328 MOZ_ASSERT(mKey.IsEmpty());
330 nsresult rv;
332 int64_t fileSize;
333 rv = aFile->GetFileSize(&fileSize);
334 if (NS_FAILED(rv)) {
335 // Don't bloat the console
336 return rv;
339 PRFileDesc *fd;
340 rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
341 NS_ENSURE_SUCCESS(rv, rv);
343 int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET);
344 if (offset == -1) {
345 PR_Close(fd);
346 return NS_ERROR_FAILURE;
349 uint32_t metaOffset;
350 int32_t bytesRead = PR_Read(fd, &metaOffset, sizeof(uint32_t));
351 if (bytesRead != sizeof(uint32_t)) {
352 PR_Close(fd);
353 return NS_ERROR_FAILURE;
356 metaOffset = NetworkEndian::readUint32(&metaOffset);
357 if (metaOffset > fileSize) {
358 PR_Close(fd);
359 return NS_ERROR_FAILURE;
362 mBuf = static_cast<char *>(malloc(fileSize - metaOffset));
363 if (!mBuf) {
364 return NS_ERROR_OUT_OF_MEMORY;
366 mBufSize = fileSize - metaOffset;
368 DoMemoryReport(MemoryUsage());
370 offset = PR_Seek64(fd, metaOffset, PR_SEEK_SET);
371 if (offset == -1) {
372 PR_Close(fd);
373 return NS_ERROR_FAILURE;
376 bytesRead = PR_Read(fd, mBuf, mBufSize);
377 PR_Close(fd);
378 if (bytesRead != static_cast<int32_t>(mBufSize)) {
379 return NS_ERROR_FAILURE;
382 rv = ParseMetadata(metaOffset, 0, false);
383 NS_ENSURE_SUCCESS(rv, rv);
385 return NS_OK;
388 const char *
389 CacheFileMetadata::GetElement(const char *aKey)
391 const char *data = mBuf;
392 const char *limit = mBuf + mElementsSize;
394 while (data != limit) {
395 size_t maxLen = limit - data;
396 size_t keyLen = strnlen(data, maxLen);
397 MOZ_RELEASE_ASSERT(keyLen != maxLen, "Metadata elements corrupted. Key "
398 "isn't null terminated!");
399 MOZ_RELEASE_ASSERT(keyLen + 1 != maxLen, "Metadata elements corrupted. "
400 "There is no value for the key!");
402 const char *value = data + keyLen + 1;
403 maxLen = limit - value;
404 size_t valueLen = strnlen(value, maxLen);
405 MOZ_RELEASE_ASSERT(valueLen != maxLen, "Metadata elements corrupted. Value "
406 "isn't null terminated!");
408 if (strcmp(data, aKey) == 0) {
409 LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
410 this, aKey));
411 return value;
414 // point to next pair
415 data += keyLen + valueLen + 2;
417 LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
418 this, aKey));
419 return nullptr;
422 nsresult
423 CacheFileMetadata::SetElement(const char *aKey, const char *aValue)
425 LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]",
426 this, aKey, aValue));
428 MarkDirty();
430 nsresult rv;
432 const uint32_t keySize = strlen(aKey) + 1;
433 char *pos = const_cast<char *>(GetElement(aKey));
435 if (!aValue) {
436 // No value means remove the key/value pair completely, if existing
437 if (pos) {
438 uint32_t oldValueSize = strlen(pos) + 1;
439 uint32_t offset = pos - mBuf;
440 uint32_t remainder = mElementsSize - (offset + oldValueSize);
442 memmove(pos - keySize, pos + oldValueSize, remainder);
443 mElementsSize -= keySize + oldValueSize;
445 return NS_OK;
448 const uint32_t valueSize = strlen(aValue) + 1;
449 uint32_t newSize = mElementsSize + valueSize;
450 if (pos) {
451 const uint32_t oldValueSize = strlen(pos) + 1;
452 const uint32_t offset = pos - mBuf;
453 const uint32_t remainder = mElementsSize - (offset + oldValueSize);
455 // Update the value in place
456 newSize -= oldValueSize;
457 rv = EnsureBuffer(newSize);
458 if (NS_FAILED(rv)) {
459 return rv;
462 // Move the remainder to the right place
463 pos = mBuf + offset;
464 memmove(pos + valueSize, pos + oldValueSize, remainder);
465 } else {
466 // allocate new meta data element
467 newSize += keySize;
468 rv = EnsureBuffer(newSize);
469 if (NS_FAILED(rv)) {
470 return rv;
473 // Add after last element
474 pos = mBuf + mElementsSize;
475 memcpy(pos, aKey, keySize);
476 pos += keySize;
479 // Update value
480 memcpy(pos, aValue, valueSize);
481 mElementsSize = newSize;
483 return NS_OK;
486 nsresult
487 CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor *aVisitor)
489 const char *data = mBuf;
490 const char *limit = mBuf + mElementsSize;
492 while (data < limit) {
493 // Point to the value part
494 const char *value = data + strlen(data) + 1;
495 MOZ_ASSERT(value < limit, "Metadata elements corrupted");
497 aVisitor->OnMetaDataElement(data, value);
499 // Skip value part
500 data = value + strlen(value) + 1;
503 MOZ_ASSERT(data == limit, "Metadata elements corrupted");
505 return NS_OK;
508 CacheHash::Hash16_t
509 CacheFileMetadata::GetHash(uint32_t aIndex)
511 MOZ_ASSERT(aIndex < mHashCount);
512 return NetworkEndian::readUint16(&mHashArray[aIndex]);
515 nsresult
516 CacheFileMetadata::SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash)
518 LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]",
519 this, aIndex, aHash));
521 MarkDirty();
523 MOZ_ASSERT(aIndex <= mHashCount);
525 if (aIndex > mHashCount) {
526 return NS_ERROR_INVALID_ARG;
527 } else if (aIndex == mHashCount) {
528 if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) {
529 // reallocate hash array buffer
530 if (mHashArraySize == 0) {
531 mHashArraySize = kInitialHashArraySize * sizeof(CacheHash::Hash16_t);
532 } else {
533 mHashArraySize *= 2;
535 mHashArray = static_cast<CacheHash::Hash16_t *>(
536 moz_xrealloc(mHashArray, mHashArraySize));
539 mHashCount++;
542 NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
544 DoMemoryReport(MemoryUsage());
546 return NS_OK;
549 nsresult
550 CacheFileMetadata::RemoveHash(uint32_t aIndex)
552 LOG(("CacheFileMetadata::RemoveHash() [this=%p, idx=%d]", this, aIndex));
554 MarkDirty();
556 MOZ_ASSERT((aIndex + 1) == mHashCount, "Can remove only last hash!");
558 if (aIndex + 1 != mHashCount) {
559 return NS_ERROR_INVALID_ARG;
562 mHashCount--;
563 return NS_OK;
566 nsresult
567 CacheFileMetadata::AddFlags(uint32_t aFlags)
569 MarkDirty(false);
570 mMetaHdr.mFlags |= aFlags;
571 return NS_OK;
574 nsresult
575 CacheFileMetadata::RemoveFlags(uint32_t aFlags)
577 MarkDirty(false);
578 mMetaHdr.mFlags &= ~aFlags;
579 return NS_OK;
582 nsresult
583 CacheFileMetadata::GetFlags(uint32_t *_retval)
585 *_retval = mMetaHdr.mFlags;
586 return NS_OK;
589 nsresult
590 CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime)
592 LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
593 this, aExpirationTime));
595 MarkDirty(false);
596 mMetaHdr.mExpirationTime = aExpirationTime;
597 return NS_OK;
600 nsresult
601 CacheFileMetadata::GetExpirationTime(uint32_t *_retval)
603 *_retval = mMetaHdr.mExpirationTime;
604 return NS_OK;
607 nsresult
608 CacheFileMetadata::SetFrecency(uint32_t aFrecency)
610 LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]",
611 this, (double)aFrecency));
613 MarkDirty(false);
614 mMetaHdr.mFrecency = aFrecency;
615 return NS_OK;
618 nsresult
619 CacheFileMetadata::GetFrecency(uint32_t *_retval)
621 *_retval = mMetaHdr.mFrecency;
622 return NS_OK;
625 nsresult
626 CacheFileMetadata::GetLastModified(uint32_t *_retval)
628 *_retval = mMetaHdr.mLastModified;
629 return NS_OK;
632 nsresult
633 CacheFileMetadata::GetLastFetched(uint32_t *_retval)
635 *_retval = mMetaHdr.mLastFetched;
636 return NS_OK;
639 nsresult
640 CacheFileMetadata::GetFetchCount(uint32_t *_retval)
642 *_retval = mMetaHdr.mFetchCount;
643 return NS_OK;
646 nsresult
647 CacheFileMetadata::OnFetched()
649 MarkDirty(false);
651 mMetaHdr.mLastFetched = NOW_SECONDS();
652 ++mMetaHdr.mFetchCount;
653 return NS_OK;
656 void
657 CacheFileMetadata::MarkDirty(bool aUpdateLastModified)
659 mIsDirty = true;
660 if (aUpdateLastModified) {
661 mMetaHdr.mLastModified = NOW_SECONDS();
665 nsresult
666 CacheFileMetadata::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult)
668 MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
669 return NS_ERROR_UNEXPECTED;
672 nsresult
673 CacheFileMetadata::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf,
674 nsresult aResult)
676 LOG(("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, result=0x%08" PRIx32 "]",
677 this, aHandle, static_cast<uint32_t>(aResult)));
679 MOZ_ASSERT(mListener);
680 MOZ_ASSERT(mWriteBuf);
682 CacheFileUtils::FreeBuffer(mWriteBuf);
683 mWriteBuf = nullptr;
685 nsCOMPtr<CacheFileMetadataListener> listener;
687 mListener.swap(listener);
688 listener->OnMetadataWritten(aResult);
690 DoMemoryReport(MemoryUsage());
692 return NS_OK;
695 nsresult
696 CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf,
697 nsresult aResult)
699 LOG(("CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32 "]",
700 this, aHandle, static_cast<uint32_t>(aResult)));
702 MOZ_ASSERT(mListener);
704 nsresult rv;
705 nsCOMPtr<CacheFileMetadataListener> listener;
707 if (NS_FAILED(aResult)) {
708 LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
709 ", creating empty metadata. [this=%p, rv=0x%08" PRIx32 "]",
710 this, static_cast<uint32_t>(aResult)));
712 InitEmptyMetadata();
714 mListener.swap(listener);
715 listener->OnMetadataRead(NS_OK);
716 return NS_OK;
719 if (mFirstRead) {
720 Telemetry::AccumulateTimeDelta(
721 Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_TIME_MS, mReadStart);
722 Telemetry::Accumulate(
723 Telemetry::NETWORK_CACHE_METADATA_FIRST_READ_SIZE, mBufSize);
724 } else {
725 Telemetry::AccumulateTimeDelta(
726 Telemetry::NETWORK_CACHE_METADATA_SECOND_READ_TIME_MS, mReadStart);
729 // check whether we have read all necessary data
730 uint32_t realOffset = NetworkEndian::readUint32(mBuf + mBufSize -
731 sizeof(uint32_t));
733 int64_t size = mHandle->FileSize();
734 MOZ_ASSERT(size != -1);
736 if (realOffset >= size) {
737 LOG(("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating "
738 "empty metadata. [this=%p, realOffset=%u, size=%" PRId64 "]", this,
739 realOffset, size));
741 InitEmptyMetadata();
743 mListener.swap(listener);
744 listener->OnMetadataRead(NS_OK);
745 return NS_OK;
748 uint32_t maxHashCount = size / kChunkSize;
749 uint32_t maxMetadataSize = CalcMetadataSize(kMaxElementsSize, maxHashCount);
750 if (size - realOffset > maxMetadataSize) {
751 LOG(("CacheFileMetadata::OnDataRead() - Invalid realOffset, metadata would "
752 "be too big, creating empty metadata. [this=%p, realOffset=%u, "
753 "maxMetadataSize=%u, size=%" PRId64 "]", this, realOffset, maxMetadataSize,
754 size));
756 InitEmptyMetadata();
758 mListener.swap(listener);
759 listener->OnMetadataRead(NS_OK);
760 return NS_OK;
763 uint32_t usedOffset = size - mBufSize;
765 if (realOffset < usedOffset) {
766 uint32_t missing = usedOffset - realOffset;
767 // we need to read more data
768 char *newBuf = static_cast<char *>(realloc(mBuf, mBufSize + missing));
769 if (!newBuf) {
770 LOG(("CacheFileMetadata::OnDataRead() - Error allocating %d more bytes "
771 "for the missing part of the metadata, creating empty metadata. "
772 "[this=%p]", missing, this));
774 InitEmptyMetadata();
776 mListener.swap(listener);
777 listener->OnMetadataRead(NS_OK);
778 return NS_OK;
781 mBuf = newBuf;
782 memmove(mBuf + missing, mBuf, mBufSize);
783 mBufSize += missing;
785 DoMemoryReport(MemoryUsage());
787 LOG(("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
788 "have full metadata. [this=%p]", missing, this));
790 mFirstRead = false;
791 mReadStart = mozilla::TimeStamp::Now();
792 rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
793 if (NS_FAILED(rv)) {
794 LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() "
795 "failed synchronously, creating empty metadata. [this=%p, "
796 "rv=0x%08" PRIx32 "]", this, static_cast<uint32_t>(rv)));
798 InitEmptyMetadata();
800 mListener.swap(listener);
801 listener->OnMetadataRead(NS_OK);
802 return NS_OK;
805 return NS_OK;
808 Telemetry::Accumulate(Telemetry::NETWORK_CACHE_METADATA_SIZE,
809 size - realOffset);
811 // We have all data according to offset information at the end of the entry.
812 // Try to parse it.
813 rv = ParseMetadata(realOffset, realOffset - usedOffset, true);
814 if (NS_FAILED(rv)) {
815 LOG(("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
816 "empty metadata. [this=%p]", this));
817 InitEmptyMetadata();
818 } else {
819 // Shrink elements buffer.
820 mBuf = static_cast<char *>(moz_xrealloc(mBuf, mElementsSize));
821 mBufSize = mElementsSize;
823 // There is usually no or just one call to SetMetadataElement() when the
824 // metadata is parsed from disk. Avoid allocating power of two sized buffer
825 // which we do in case of newly created metadata.
826 mAllocExactSize = true;
829 mListener.swap(listener);
830 listener->OnMetadataRead(NS_OK);
832 return NS_OK;
835 nsresult
836 CacheFileMetadata::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult)
838 MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
839 return NS_ERROR_UNEXPECTED;
842 nsresult
843 CacheFileMetadata::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult)
845 MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
846 return NS_ERROR_UNEXPECTED;
849 nsresult
850 CacheFileMetadata::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult)
852 MOZ_CRASH("CacheFileMetadata::OnFileRenamed should not be called!");
853 return NS_ERROR_UNEXPECTED;
856 void
857 CacheFileMetadata::InitEmptyMetadata()
859 if (mBuf) {
860 CacheFileUtils::FreeBuffer(mBuf);
861 mBuf = nullptr;
862 mBufSize = 0;
864 mAllocExactSize = false;
865 mOffset = 0;
866 mMetaHdr.mVersion = kCacheEntryVersion;
867 mMetaHdr.mFetchCount = 0;
868 mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
869 mMetaHdr.mKeySize = mKey.Length();
871 // Deliberately not touching the "kCacheEntryIsPinned" flag.
873 DoMemoryReport(MemoryUsage());
875 // We're creating a new entry. If there is any old data truncate it.
876 if (mHandle) {
877 mHandle->SetPinned(Pinned());
878 // We can pronounce the handle as invalid now, because it simply
879 // doesn't have the correct metadata. This will cause IO operations
880 // be bypassed during shutdown (mainly dooming it, when a channel
881 // is canceled by closing the window.)
882 mHandle->SetInvalid();
883 if (mHandle->FileExists() && mHandle->FileSize()) {
884 CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
889 nsresult
890 CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset,
891 bool aHaveKey)
893 LOG(("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
894 "bufOffset=%d, haveKey=%u]", this, aMetaOffset, aBufOffset, aHaveKey));
896 nsresult rv;
898 uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
899 uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
900 uint32_t hashCount = aMetaOffset / kChunkSize;
901 if (aMetaOffset % kChunkSize)
902 hashCount++;
903 uint32_t hashesLen = hashCount * sizeof(CacheHash::Hash16_t);
904 uint32_t hdrOffset = hashesOffset + hashesLen;
905 uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);
907 LOG(("CacheFileMetadata::ParseMetadata() [this=%p]\n metaposOffset=%d\n "
908 "hashesOffset=%d\n hashCount=%d\n hashesLen=%d\n hdfOffset=%d\n "
909 "keyOffset=%d\n", this, metaposOffset, hashesOffset, hashCount,
910 hashesLen,hdrOffset, keyOffset));
912 if (keyOffset > metaposOffset) {
913 LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
914 this));
915 return NS_ERROR_FILE_CORRUPTED;
918 mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
920 if (mMetaHdr.mVersion == 1) {
921 // Backward compatibility before we've added flags to the header
922 keyOffset -= sizeof(uint32_t);
923 } else if (mMetaHdr.mVersion == 2) {
924 // Version 2 just lacks the ability to store alternative data. Nothing to do
925 // here.
926 } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
927 LOG(("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
928 "[version=0x%x, this=%p]", mMetaHdr.mVersion, this));
929 return NS_ERROR_UNEXPECTED;
932 // Update the version stored in the header to make writes
933 // store the header in the current version form.
934 mMetaHdr.mVersion = kCacheEntryVersion;
936 uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
938 if (elementsOffset > metaposOffset) {
939 LOG(("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
940 "[this=%p]", elementsOffset, this));
941 return NS_ERROR_FILE_CORRUPTED;
944 // check that key ends with \0
945 if (mBuf[elementsOffset - 1] != 0) {
946 LOG(("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
947 "[this=%p]", this));
948 return NS_ERROR_FILE_CORRUPTED;
952 if (!aHaveKey) {
953 // get the key form metadata
954 mKey.Assign(mBuf + keyOffset, mMetaHdr.mKeySize);
956 rv = ParseKey(mKey);
957 if (NS_FAILED(rv))
958 return rv;
960 else {
961 if (mMetaHdr.mKeySize != mKey.Length()) {
962 LOG(("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s "
963 "[this=%p]", nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(),
964 this));
965 return NS_ERROR_FILE_CORRUPTED;
968 if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
969 LOG(("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s "
970 "[this=%p]", nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(),
971 this));
972 return NS_ERROR_FILE_CORRUPTED;
976 // check metadata hash (data from hashesOffset to metaposOffset)
977 CacheHash::Hash32_t hashComputed, hashExpected;
978 hashComputed = CacheHash::Hash(mBuf + hashesOffset,
979 metaposOffset - hashesOffset);
980 hashExpected = NetworkEndian::readUint32(mBuf + aBufOffset);
982 if (hashComputed != hashExpected) {
983 LOG(("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
984 "the metadata is %x, hash in file is %x [this=%p]", hashComputed,
985 hashExpected, this));
986 return NS_ERROR_FILE_CORRUPTED;
989 // check elements
990 rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
991 if (NS_FAILED(rv))
992 return rv;
994 if (mHandle) {
995 if (!mHandle->SetPinned(Pinned())) {
996 LOG(("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
997 "pinning state, truncate the file [this=%p, pinned=%d]", this, Pinned()));
998 return NS_ERROR_FILE_CORRUPTED;
1002 mHashArraySize = hashesLen;
1003 mHashCount = hashCount;
1004 if (mHashArraySize) {
1005 mHashArray = static_cast<CacheHash::Hash16_t *>(
1006 moz_xmalloc(mHashArraySize));
1007 memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
1010 MarkDirty();
1012 mElementsSize = metaposOffset - elementsOffset;
1013 memmove(mBuf, mBuf + elementsOffset, mElementsSize);
1014 mOffset = aMetaOffset;
1016 DoMemoryReport(MemoryUsage());
1018 return NS_OK;
1021 nsresult
1022 CacheFileMetadata::CheckElements(const char *aBuf, uint32_t aSize)
1024 if (aSize) {
1025 // Check if the metadata ends with a zero byte.
1026 if (aBuf[aSize - 1] != 0) {
1027 NS_ERROR("Metadata elements are not null terminated");
1028 LOG(("CacheFileMetadata::CheckElements() - Elements are not null "
1029 "terminated. [this=%p]", this));
1030 return NS_ERROR_FILE_CORRUPTED;
1032 // Check that there are an even number of zero bytes
1033 // to match the pattern { key \0 value \0 }
1034 bool odd = false;
1035 for (uint32_t i = 0; i < aSize; i++) {
1036 if (aBuf[i] == 0)
1037 odd = !odd;
1039 if (odd) {
1040 NS_ERROR("Metadata elements are malformed");
1041 LOG(("CacheFileMetadata::CheckElements() - Elements are malformed. "
1042 "[this=%p]", this));
1043 return NS_ERROR_FILE_CORRUPTED;
1046 return NS_OK;
1049 nsresult
1050 CacheFileMetadata::EnsureBuffer(uint32_t aSize)
1052 if (aSize > kMaxElementsSize) {
1053 return NS_ERROR_FAILURE;
1056 if (mBufSize < aSize) {
1057 if (mAllocExactSize) {
1058 // If this is not the only allocation, use power of two for following
1059 // allocations.
1060 mAllocExactSize = false;
1061 } else {
1062 // find smallest power of 2 greater than or equal to aSize
1063 --aSize;
1064 aSize |= aSize >> 1;
1065 aSize |= aSize >> 2;
1066 aSize |= aSize >> 4;
1067 aSize |= aSize >> 8;
1068 aSize |= aSize >> 16;
1069 ++aSize;
1072 if (aSize < kInitialBufSize) {
1073 aSize = kInitialBufSize;
1076 char *newBuf = static_cast<char *>(realloc(mBuf, aSize));
1077 if (!newBuf) {
1078 return NS_ERROR_OUT_OF_MEMORY;
1080 mBufSize = aSize;
1081 mBuf = newBuf;
1083 DoMemoryReport(MemoryUsage());
1086 return NS_OK;
1089 nsresult
1090 CacheFileMetadata::ParseKey(const nsACString &aKey)
1092 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
1093 NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);
1095 mAnonymous = info->IsAnonymous();
1096 mOriginAttributes = *info->OriginAttributesPtr();
1098 return NS_OK;
1101 // Memory reporting
1103 size_t
1104 CacheFileMetadata::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1106 size_t n = 0;
1107 // mHandle reported via CacheFileIOManager.
1108 n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
1109 n += mallocSizeOf(mHashArray);
1110 n += mallocSizeOf(mBuf);
1111 // Ignore mWriteBuf, it's not safe to access it when metadata is being
1112 // written and it's null otherwise.
1113 // mListener is usually the owning CacheFile.
1115 return n;
1118 size_t
1119 CacheFileMetadata::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
1121 return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
1124 } // namespace net
1125 } // namespace mozilla