michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CacheLog.h" michael@0: #include "CacheFileMetadata.h" michael@0: michael@0: #include "CacheFileIOManager.h" michael@0: #include "nsICacheEntry.h" michael@0: #include "CacheHashUtils.h" michael@0: #include "CacheFileChunk.h" michael@0: #include "CacheFileUtils.h" michael@0: #include "nsILoadContextInfo.h" michael@0: #include "../cache/nsCacheUtils.h" michael@0: #include "nsIFile.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "prnetdb.h" michael@0: michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: #define kMinMetadataRead 1024 // TODO find optimal value from telemetry michael@0: #define kAlignSize 4096 michael@0: michael@0: #define kCacheEntryVersion 1 michael@0: michael@0: NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener) michael@0: michael@0: CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey) michael@0: : CacheMemoryConsumer(NORMAL) michael@0: , mHandle(aHandle) michael@0: , mHashArray(nullptr) michael@0: , mHashArraySize(0) michael@0: , mHashCount(0) michael@0: , mOffset(-1) michael@0: , mBuf(nullptr) michael@0: , mBufSize(0) michael@0: , mWriteBuf(nullptr) michael@0: , mElementsSize(0) michael@0: , mIsDirty(false) michael@0: , mAnonymous(false) michael@0: , mInBrowser(false) michael@0: , mAppId(nsILoadContextInfo::NO_APP_ID) michael@0: { michael@0: LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]", michael@0: this, aHandle, PromiseFlatCString(aKey).get())); michael@0: michael@0: MOZ_COUNT_CTOR(CacheFileMetadata); michael@0: memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader)); michael@0: mMetaHdr.mVersion = kCacheEntryVersion; michael@0: mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME; michael@0: mKey = aKey; michael@0: michael@0: DebugOnly rv; michael@0: rv = ParseKey(aKey); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: michael@0: CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey) michael@0: : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL) michael@0: , mHandle(nullptr) michael@0: , mHashArray(nullptr) michael@0: , mHashArraySize(0) michael@0: , mHashCount(0) michael@0: , mOffset(0) michael@0: , mBuf(nullptr) michael@0: , mBufSize(0) michael@0: , mWriteBuf(nullptr) michael@0: , mElementsSize(0) michael@0: , mIsDirty(true) michael@0: , mAnonymous(false) michael@0: , mInBrowser(false) michael@0: , mAppId(nsILoadContextInfo::NO_APP_ID) michael@0: { michael@0: LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]", michael@0: this, PromiseFlatCString(aKey).get())); michael@0: michael@0: MOZ_COUNT_CTOR(CacheFileMetadata); michael@0: memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader)); michael@0: mMetaHdr.mVersion = kCacheEntryVersion; michael@0: mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME; michael@0: mMetaHdr.mFetchCount = 1; michael@0: mKey = aKey; michael@0: mMetaHdr.mKeySize = mKey.Length(); michael@0: michael@0: DebugOnly rv; michael@0: rv = ParseKey(aKey); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: michael@0: CacheFileMetadata::CacheFileMetadata() michael@0: : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */) michael@0: , mHandle(nullptr) michael@0: , mHashArray(nullptr) michael@0: , mHashArraySize(0) michael@0: , mHashCount(0) michael@0: , mOffset(0) michael@0: , mBuf(nullptr) michael@0: , mBufSize(0) michael@0: , mWriteBuf(nullptr) michael@0: , mElementsSize(0) michael@0: , mIsDirty(false) michael@0: , mAnonymous(false) michael@0: , mInBrowser(false) michael@0: , mAppId(nsILoadContextInfo::NO_APP_ID) michael@0: { michael@0: LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]", this)); michael@0: michael@0: MOZ_COUNT_CTOR(CacheFileMetadata); michael@0: memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader)); michael@0: } michael@0: michael@0: CacheFileMetadata::~CacheFileMetadata() michael@0: { michael@0: LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this)); michael@0: michael@0: MOZ_COUNT_DTOR(CacheFileMetadata); michael@0: MOZ_ASSERT(!mListener); michael@0: michael@0: if (mHashArray) { michael@0: free(mHashArray); michael@0: mHashArray = nullptr; michael@0: mHashArraySize = 0; michael@0: } michael@0: michael@0: if (mBuf) { michael@0: free(mBuf); michael@0: mBuf = nullptr; michael@0: mBufSize = 0; michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheFileMetadata::SetHandle(CacheFileHandle *aHandle) michael@0: { michael@0: LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle)); michael@0: michael@0: MOZ_ASSERT(!mHandle); michael@0: michael@0: mHandle = aHandle; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::GetKey(nsACString &_retval) michael@0: { michael@0: _retval = mKey; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::ReadMetadata(CacheFileMetadataListener *aListener) michael@0: { michael@0: LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this, aListener)); michael@0: michael@0: MOZ_ASSERT(!mListener); michael@0: MOZ_ASSERT(!mHashArray); michael@0: MOZ_ASSERT(!mBuf); michael@0: MOZ_ASSERT(!mWriteBuf); michael@0: michael@0: nsresult rv; michael@0: michael@0: int64_t size = mHandle->FileSize(); michael@0: MOZ_ASSERT(size != -1); michael@0: michael@0: if (size == 0) { michael@0: // this is a new entry michael@0: LOG(("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty " michael@0: "metadata. [this=%p]", this)); michael@0: michael@0: InitEmptyMetadata(); michael@0: aListener->OnMetadataRead(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2*sizeof(uint32_t))) { michael@0: // there must be at least checksum, header and offset michael@0: LOG(("CacheFileMetadata::ReadMetadata() - File is corrupted, creating " michael@0: "empty metadata. [this=%p, filesize=%lld]", this, size)); michael@0: michael@0: InitEmptyMetadata(); michael@0: aListener->OnMetadataRead(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // round offset to 4k blocks michael@0: int64_t offset = (size / kAlignSize) * kAlignSize; michael@0: michael@0: if (size - offset < kMinMetadataRead && offset > kAlignSize) michael@0: offset -= kAlignSize; michael@0: michael@0: mBufSize = size - offset; michael@0: mBuf = static_cast(moz_xmalloc(mBufSize)); michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: michael@0: LOG(("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying " michael@0: "offset=%lld, filesize=%lld [this=%p]", offset, size, this)); michael@0: michael@0: mListener = aListener; michael@0: rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, true, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed" michael@0: " synchronously, creating empty metadata. [this=%p, rv=0x%08x]", michael@0: this, rv)); michael@0: michael@0: mListener = nullptr; michael@0: InitEmptyMetadata(); michael@0: aListener->OnMetadataRead(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::WriteMetadata(uint32_t aOffset, michael@0: CacheFileMetadataListener *aListener) michael@0: { michael@0: LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]", michael@0: this, aOffset, aListener)); michael@0: michael@0: MOZ_ASSERT(!mListener); michael@0: MOZ_ASSERT(!mWriteBuf); michael@0: michael@0: nsresult rv; michael@0: michael@0: mIsDirty = false; michael@0: michael@0: mWriteBuf = static_cast(moz_xmalloc(sizeof(uint32_t) + michael@0: mHashCount * sizeof(CacheHash::Hash16_t) + michael@0: sizeof(CacheFileMetadataHeader) + mKey.Length() + 1 + michael@0: mElementsSize + sizeof(uint32_t))); michael@0: michael@0: char *p = mWriteBuf + sizeof(uint32_t); michael@0: memcpy(p, mHashArray, mHashCount * sizeof(CacheHash::Hash16_t)); michael@0: p += mHashCount * sizeof(CacheHash::Hash16_t); michael@0: mMetaHdr.WriteToBuf(p); michael@0: p += sizeof(CacheFileMetadataHeader); michael@0: memcpy(p, mKey.get(), mKey.Length()); michael@0: p += mKey.Length(); michael@0: *p = 0; michael@0: p++; michael@0: memcpy(p, mBuf, mElementsSize); michael@0: p += mElementsSize; michael@0: michael@0: CacheHash::Hash32_t hash; michael@0: hash = CacheHash::Hash(mWriteBuf + sizeof(uint32_t), michael@0: p - mWriteBuf - sizeof(uint32_t)); michael@0: NetworkEndian::writeUint32(mWriteBuf, hash); michael@0: michael@0: NetworkEndian::writeUint32(p, aOffset); michael@0: p += sizeof(uint32_t); michael@0: michael@0: char * writeBuffer; michael@0: if (aListener) { michael@0: mListener = aListener; michael@0: writeBuffer = mWriteBuf; michael@0: } else { michael@0: // We are not going to pass |this| as callback to CacheFileIOManager::Write michael@0: // so we must allocate a new buffer that will be released automatically when michael@0: // write is finished. This is actually better than to let michael@0: // CacheFileMetadata::OnDataWritten do the job, since when dispatching the michael@0: // result from some reason fails during shutdown, we would unnecessarily leak michael@0: // both this object and the buffer. michael@0: writeBuffer = static_cast(moz_xmalloc(p - mWriteBuf)); michael@0: memcpy(mWriteBuf, writeBuffer, p - mWriteBuf); michael@0: } michael@0: michael@0: rv = CacheFileIOManager::Write(mHandle, aOffset, writeBuffer, p - mWriteBuf, michael@0: true, aListener ? this : nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() " michael@0: "failed synchronously. [this=%p, rv=0x%08x]", this, rv)); michael@0: michael@0: mListener = nullptr; michael@0: if (writeBuffer != mWriteBuf) { michael@0: free(writeBuffer); michael@0: } michael@0: free(mWriteBuf); michael@0: mWriteBuf = nullptr; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::SyncReadMetadata(nsIFile *aFile) michael@0: { michael@0: LOG(("CacheFileMetadata::SyncReadMetadata() [this=%p]", this)); michael@0: michael@0: MOZ_ASSERT(!mListener); michael@0: MOZ_ASSERT(!mHandle); michael@0: MOZ_ASSERT(!mHashArray); michael@0: MOZ_ASSERT(!mBuf); michael@0: MOZ_ASSERT(!mWriteBuf); michael@0: MOZ_ASSERT(mKey.IsEmpty()); michael@0: michael@0: nsresult rv; michael@0: michael@0: int64_t fileSize; michael@0: rv = aFile->GetFileSize(&fileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: PRFileDesc *fd; michael@0: rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET); michael@0: if (offset == -1) { michael@0: PR_Close(fd); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint32_t metaOffset; michael@0: int32_t bytesRead = PR_Read(fd, &metaOffset, sizeof(uint32_t)); michael@0: if (bytesRead != sizeof(uint32_t)) { michael@0: PR_Close(fd); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: metaOffset = NetworkEndian::readUint32(&metaOffset); michael@0: if (metaOffset > fileSize) { michael@0: PR_Close(fd); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mBufSize = fileSize - metaOffset; michael@0: mBuf = static_cast(moz_xmalloc(mBufSize)); michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: michael@0: offset = PR_Seek64(fd, metaOffset, PR_SEEK_SET); michael@0: if (offset == -1) { michael@0: PR_Close(fd); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: bytesRead = PR_Read(fd, mBuf, mBufSize); michael@0: PR_Close(fd); michael@0: if (bytesRead != static_cast(mBufSize)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: rv = ParseMetadata(metaOffset, 0, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: const char * michael@0: CacheFileMetadata::GetElement(const char *aKey) michael@0: { michael@0: const char *data = mBuf; michael@0: const char *limit = mBuf + mElementsSize; michael@0: michael@0: while (data < limit) { michael@0: // Point to the value part michael@0: const char *value = data + strlen(data) + 1; michael@0: MOZ_ASSERT(value < limit, "Metadata elements corrupted"); michael@0: if (strcmp(data, aKey) == 0) { michael@0: LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]", michael@0: this, aKey)); michael@0: return value; michael@0: } michael@0: michael@0: // Skip value part michael@0: data = value + strlen(value) + 1; michael@0: } michael@0: MOZ_ASSERT(data == limit, "Metadata elements corrupted"); michael@0: LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]", michael@0: this, aKey)); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::SetElement(const char *aKey, const char *aValue) michael@0: { michael@0: LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]", michael@0: this, aKey, aValue)); michael@0: michael@0: MarkDirty(); michael@0: michael@0: const uint32_t keySize = strlen(aKey) + 1; michael@0: char *pos = const_cast(GetElement(aKey)); michael@0: michael@0: if (!aValue) { michael@0: // No value means remove the key/value pair completely, if existing michael@0: if (pos) { michael@0: uint32_t oldValueSize = strlen(pos) + 1; michael@0: uint32_t offset = pos - mBuf; michael@0: uint32_t remainder = mElementsSize - (offset + oldValueSize); michael@0: michael@0: memmove(pos - keySize, pos + oldValueSize, remainder); michael@0: mElementsSize -= keySize + oldValueSize; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: const uint32_t valueSize = strlen(aValue) + 1; michael@0: uint32_t newSize = mElementsSize + valueSize; michael@0: if (pos) { michael@0: const uint32_t oldValueSize = strlen(pos) + 1; michael@0: const uint32_t offset = pos - mBuf; michael@0: const uint32_t remainder = mElementsSize - (offset + oldValueSize); michael@0: michael@0: // Update the value in place michael@0: newSize -= oldValueSize; michael@0: EnsureBuffer(newSize); michael@0: michael@0: // Move the remainder to the right place michael@0: pos = mBuf + offset; michael@0: memmove(pos + valueSize, pos + oldValueSize, remainder); michael@0: } else { michael@0: // allocate new meta data element michael@0: newSize += keySize; michael@0: EnsureBuffer(newSize); michael@0: michael@0: // Add after last element michael@0: pos = mBuf + mElementsSize; michael@0: memcpy(pos, aKey, keySize); michael@0: pos += keySize; michael@0: } michael@0: michael@0: // Update value michael@0: memcpy(pos, aValue, valueSize); michael@0: mElementsSize = newSize; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: CacheHash::Hash16_t michael@0: CacheFileMetadata::GetHash(uint32_t aIndex) michael@0: { michael@0: MOZ_ASSERT(aIndex < mHashCount); michael@0: return NetworkEndian::readUint16(&mHashArray[aIndex]); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::SetHash(uint32_t aIndex, CacheHash::Hash16_t aHash) michael@0: { michael@0: LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]", michael@0: this, aIndex, aHash)); michael@0: michael@0: MarkDirty(); michael@0: michael@0: MOZ_ASSERT(aIndex <= mHashCount); michael@0: michael@0: if (aIndex > mHashCount) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } else if (aIndex == mHashCount) { michael@0: if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) { michael@0: // reallocate hash array buffer michael@0: if (mHashArraySize == 0) michael@0: mHashArraySize = 32 * sizeof(CacheHash::Hash16_t); michael@0: else michael@0: mHashArraySize *= 2; michael@0: mHashArray = static_cast( michael@0: moz_xrealloc(mHashArray, mHashArraySize)); michael@0: } michael@0: michael@0: mHashCount++; michael@0: } michael@0: michael@0: NetworkEndian::writeUint16(&mHashArray[aIndex], aHash); michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime) michael@0: { michael@0: LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]", michael@0: this, aExpirationTime)); michael@0: michael@0: MarkDirty(); michael@0: mMetaHdr.mExpirationTime = aExpirationTime; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::GetExpirationTime(uint32_t *_retval) michael@0: { michael@0: *_retval = mMetaHdr.mExpirationTime; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::SetLastModified(uint32_t aLastModified) michael@0: { michael@0: LOG(("CacheFileMetadata::SetLastModified() [this=%p, lastModified=%d]", michael@0: this, aLastModified)); michael@0: michael@0: MarkDirty(); michael@0: mMetaHdr.mLastModified = aLastModified; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::GetLastModified(uint32_t *_retval) michael@0: { michael@0: *_retval = mMetaHdr.mLastModified; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::SetFrecency(uint32_t aFrecency) michael@0: { michael@0: LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]", michael@0: this, (double)aFrecency)); michael@0: michael@0: MarkDirty(); michael@0: mMetaHdr.mFrecency = aFrecency; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::GetFrecency(uint32_t *_retval) michael@0: { michael@0: *_retval = mMetaHdr.mFrecency; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::GetLastFetched(uint32_t *_retval) michael@0: { michael@0: *_retval = mMetaHdr.mLastFetched; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::GetFetchCount(uint32_t *_retval) michael@0: { michael@0: *_retval = mMetaHdr.mFetchCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::OnFileOpened(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::OnDataWritten(CacheFileHandle *aHandle, const char *aBuf, michael@0: nsresult aResult) michael@0: { michael@0: LOG(("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, result=0x%08x]", michael@0: this, aHandle, aResult)); michael@0: michael@0: MOZ_ASSERT(mListener); michael@0: MOZ_ASSERT(mWriteBuf); michael@0: michael@0: free(mWriteBuf); michael@0: mWriteBuf = nullptr; michael@0: michael@0: nsCOMPtr listener; michael@0: michael@0: mListener.swap(listener); michael@0: listener->OnMetadataWritten(aResult); michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::OnDataRead(CacheFileHandle *aHandle, char *aBuf, michael@0: nsresult aResult) michael@0: { michael@0: LOG(("CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08x]", michael@0: this, aHandle, aResult)); michael@0: michael@0: MOZ_ASSERT(mListener); michael@0: michael@0: nsresult rv, retval; michael@0: nsCOMPtr listener; michael@0: michael@0: if (NS_FAILED(aResult)) { michael@0: LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed" michael@0: ", creating empty metadata. [this=%p, rv=0x%08x]", this, aResult)); michael@0: michael@0: InitEmptyMetadata(); michael@0: retval = NS_OK; michael@0: michael@0: mListener.swap(listener); michael@0: listener->OnMetadataRead(retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // check whether we have read all necessary data michael@0: uint32_t realOffset = NetworkEndian::readUint32(mBuf + mBufSize - michael@0: sizeof(uint32_t)); michael@0: michael@0: int64_t size = mHandle->FileSize(); michael@0: MOZ_ASSERT(size != -1); michael@0: michael@0: if (realOffset >= size) { michael@0: LOG(("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating " michael@0: "empty metadata. [this=%p, realOffset=%d, size=%lld]", this, michael@0: realOffset, size)); michael@0: michael@0: InitEmptyMetadata(); michael@0: retval = NS_OK; michael@0: michael@0: mListener.swap(listener); michael@0: listener->OnMetadataRead(retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t usedOffset = size - mBufSize; michael@0: michael@0: if (realOffset < usedOffset) { michael@0: uint32_t missing = usedOffset - realOffset; michael@0: // we need to read more data michael@0: mBuf = static_cast(moz_xrealloc(mBuf, mBufSize + missing)); michael@0: memmove(mBuf + missing, mBuf, mBufSize); michael@0: mBufSize += missing; michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: michael@0: LOG(("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to " michael@0: "have full metadata. [this=%p]", missing, this)); michael@0: michael@0: rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, true, this); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() " michael@0: "failed synchronously, creating empty metadata. [this=%p, " michael@0: "rv=0x%08x]", this, rv)); michael@0: michael@0: InitEmptyMetadata(); michael@0: retval = NS_OK; michael@0: michael@0: mListener.swap(listener); michael@0: listener->OnMetadataRead(retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We have all data according to offset information at the end of the entry. michael@0: // Try to parse it. michael@0: rv = ParseMetadata(realOffset, realOffset - usedOffset, true); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating " michael@0: "empty metadata. [this=%p]", this)); michael@0: InitEmptyMetadata(); michael@0: retval = NS_OK; michael@0: } michael@0: else { michael@0: retval = NS_OK; michael@0: } michael@0: michael@0: mListener.swap(listener); michael@0: listener->OnMetadataRead(retval); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::OnFileDoomed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::OnEOFSet(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::OnFileRenamed(CacheFileHandle *aHandle, nsresult aResult) michael@0: { michael@0: MOZ_CRASH("CacheFileMetadata::OnFileRenamed should not be called!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: void michael@0: CacheFileMetadata::InitEmptyMetadata() michael@0: { michael@0: if (mBuf) { michael@0: free(mBuf); michael@0: mBuf = nullptr; michael@0: mBufSize = 0; michael@0: } michael@0: mOffset = 0; michael@0: mMetaHdr.mVersion = kCacheEntryVersion; michael@0: mMetaHdr.mFetchCount = 1; michael@0: mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME; michael@0: mMetaHdr.mKeySize = mKey.Length(); michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset, michael@0: bool aHaveKey) michael@0: { michael@0: LOG(("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, " michael@0: "bufOffset=%d, haveKey=%u]", this, aMetaOffset, aBufOffset, aHaveKey)); michael@0: michael@0: nsresult rv; michael@0: michael@0: uint32_t metaposOffset = mBufSize - sizeof(uint32_t); michael@0: uint32_t hashesOffset = aBufOffset + sizeof(uint32_t); michael@0: uint32_t hashCount = aMetaOffset / kChunkSize; michael@0: if (aMetaOffset % kChunkSize) michael@0: hashCount++; michael@0: uint32_t hashesLen = hashCount * sizeof(CacheHash::Hash16_t); michael@0: uint32_t hdrOffset = hashesOffset + hashesLen; michael@0: uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader); michael@0: michael@0: LOG(("CacheFileMetadata::ParseMetadata() [this=%p]\n metaposOffset=%d\n " michael@0: "hashesOffset=%d\n hashCount=%d\n hashesLen=%d\n hdfOffset=%d\n " michael@0: "keyOffset=%d\n", this, metaposOffset, hashesOffset, hashCount, michael@0: hashesLen,hdrOffset, keyOffset)); michael@0: michael@0: if (keyOffset > metaposOffset) { michael@0: LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]", michael@0: this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: mMetaHdr.ReadFromBuf(mBuf + hdrOffset); michael@0: michael@0: if (mMetaHdr.mVersion != kCacheEntryVersion) { michael@0: LOG(("CacheFileMetadata::ParseMetadata() - Not a version we understand to. " michael@0: "[version=0x%x, this=%p]", mMetaHdr.mVersion, this)); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1; michael@0: michael@0: if (elementsOffset > metaposOffset) { michael@0: LOG(("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d " michael@0: "[this=%p]", elementsOffset, this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: // check that key ends with \0 michael@0: if (mBuf[elementsOffset - 1] != 0) { michael@0: LOG(("CacheFileMetadata::ParseMetadata() - Elements not null terminated. " michael@0: "[this=%p]", this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: michael@0: if (!aHaveKey) { michael@0: // get the key form metadata michael@0: mKey.Assign(mBuf + keyOffset, mMetaHdr.mKeySize); michael@0: michael@0: rv = ParseKey(mKey); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: else { michael@0: if (mMetaHdr.mKeySize != mKey.Length()) { michael@0: LOG(("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s " michael@0: "[this=%p]", nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), michael@0: this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) { michael@0: LOG(("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s " michael@0: "[this=%p]", nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), michael@0: this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: } michael@0: michael@0: // check metadata hash (data from hashesOffset to metaposOffset) michael@0: CacheHash::Hash32_t hashComputed, hashExpected; michael@0: hashComputed = CacheHash::Hash(mBuf + hashesOffset, michael@0: metaposOffset - hashesOffset); michael@0: hashExpected = NetworkEndian::readUint32(mBuf + aBufOffset); michael@0: michael@0: if (hashComputed != hashExpected) { michael@0: LOG(("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of " michael@0: "the metadata is %x, hash in file is %x [this=%p]", hashComputed, michael@0: hashExpected, this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: // check elements michael@0: rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mHashArraySize = hashesLen; michael@0: mHashCount = hashCount; michael@0: if (mHashArraySize) { michael@0: mHashArray = static_cast( michael@0: moz_xmalloc(mHashArraySize)); michael@0: memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize); michael@0: } michael@0: michael@0: michael@0: mMetaHdr.mFetchCount++; michael@0: MarkDirty(); michael@0: michael@0: mElementsSize = metaposOffset - elementsOffset; michael@0: memmove(mBuf, mBuf + elementsOffset, mElementsSize); michael@0: mOffset = aMetaOffset; michael@0: michael@0: // TODO: shrink memory if buffer is too big michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::CheckElements(const char *aBuf, uint32_t aSize) michael@0: { michael@0: if (aSize) { michael@0: // Check if the metadata ends with a zero byte. michael@0: if (aBuf[aSize - 1] != 0) { michael@0: NS_ERROR("Metadata elements are not null terminated"); michael@0: LOG(("CacheFileMetadata::CheckElements() - Elements are not null " michael@0: "terminated. [this=%p]", this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: // Check that there are an even number of zero bytes michael@0: // to match the pattern { key \0 value \0 } michael@0: bool odd = false; michael@0: for (uint32_t i = 0; i < aSize; i++) { michael@0: if (aBuf[i] == 0) michael@0: odd = !odd; michael@0: } michael@0: if (odd) { michael@0: NS_ERROR("Metadata elements are malformed"); michael@0: LOG(("CacheFileMetadata::CheckElements() - Elements are malformed. " michael@0: "[this=%p]", this)); michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheFileMetadata::EnsureBuffer(uint32_t aSize) michael@0: { michael@0: if (mBufSize < aSize) { michael@0: mBufSize = aSize; michael@0: mBuf = static_cast(moz_xrealloc(mBuf, mBufSize)); michael@0: } michael@0: michael@0: DoMemoryReport(MemoryUsage()); michael@0: } michael@0: michael@0: nsresult michael@0: CacheFileMetadata::ParseKey(const nsACString &aKey) michael@0: { michael@0: nsCOMPtr info = CacheFileUtils::ParseKey(aKey); michael@0: NS_ENSURE_TRUE(info, NS_ERROR_FAILURE); michael@0: michael@0: mAnonymous = info->IsAnonymous(); michael@0: mAppId = info->AppId(); michael@0: mInBrowser = info->IsInBrowserElement(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Memory reporting michael@0: michael@0: size_t michael@0: CacheFileMetadata::SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t n = 0; michael@0: // mHandle reported via CacheFileIOManager. michael@0: n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf); michael@0: n += mallocSizeOf(mHashArray); michael@0: n += mallocSizeOf(mBuf); michael@0: n += mallocSizeOf(mWriteBuf); michael@0: // mListener is usually the owning CacheFile. michael@0: michael@0: return n; michael@0: } michael@0: michael@0: size_t michael@0: CacheFileMetadata::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla