Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  CacheFileMetadata.cpp   Sprache: C

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "CacheLog.h"
#include "CacheFileMetadata.h"

#include "CacheFileIOManager.h"
#include "nsICacheEntry.h"
#include "CacheHashUtils.h"
#include "CacheFileChunk.h"
#include "CacheFileUtils.h"
#include "nsILoadContextInfo.h"
#include "nsICacheEntry.h"  // for nsICacheEntryMetaDataVisitor
#include "nsIFile.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Telemetry.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/glean/NetwerkMetrics.h"
#include "prnetdb.h"

namespace mozilla::net {

#define kMinMetadataRead 1024  // TODO find optimal value from telemetry
#define kAlignSize 4096

// Most of the cache entries fit into one chunk due to current chunk size. Make
// sure to tweak this value if kChunkSize is going to change.
#define kInitialHashArraySize 1

// Initial elements buffer size.
#define kInitialBufSize 64

// Max size of elements in bytes.
#define kMaxElementsSize (64 * 1024)

#define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))

NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)

CacheFileMetadata::CacheFileMetadata(
    CacheFileHandle* aHandle, const nsACString& aKey,
    NotNull<CacheFileUtils::CacheFileLock*> aLock)
    : CacheMemoryConsumer(NORMAL),
      mHandle(aHandle),
      mOffset(-1),
      mIsDirty(false),
      mAnonymous(false),
      mAllocExactSize(false),
      mFirstRead(true),
      mLock(aLock) {
  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
       this, aHandle, PromiseFlatCString(aKey).get()));

  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
  mMetaHdr.mVersion = kCacheEntryVersion;
  mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
  mKey = aKey;

  DebugOnly<nsresult> rv{};
  rv = ParseKey(aKey);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

CacheFileMetadata::CacheFileMetadata(
    bool aMemoryOnly, bool aPinned, const nsACString& aKey,
    NotNull<CacheFileUtils::CacheFileLock*> aLock)
    : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL),
      mIsDirty(true),
      mAnonymous(false),
      mAllocExactSize(false),
      mFirstRead(true),
      mLock(aLock) {
  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]"this,
       PromiseFlatCString(aKey).get()));

  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
  mMetaHdr.mVersion = kCacheEntryVersion;
  if (aPinned) {
    AddFlags(kCacheEntryIsPinned);
  }
  mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
  mKey = aKey;
  mMetaHdr.mKeySize = mKey.Length();

  DebugOnly<nsresult> rv{};
  rv = ParseKey(aKey);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
}

CacheFileMetadata::CacheFileMetadata()
    : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */),
      mIsDirty(false),
      mAnonymous(false),
      mAllocExactSize(false),
      mFirstRead(true),
      mLock(new CacheFileUtils::CacheFileLock()) {
  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]"this));

  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
}

CacheFileMetadata::~CacheFileMetadata() {
  LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]"this));

  MOZ_ASSERT(!mListener);

  if (mHashArray) {
    CacheFileUtils::FreeBuffer(mHashArray);
    mHashArray = nullptr;
    mHashArraySize = 0;
  }

  if (mBuf) {
    CacheFileUtils::FreeBuffer(mBuf);
    mBuf = nullptr;
    mBufSize = 0;
  }
}

void CacheFileMetadata::SetHandle(CacheFileHandle* aHandle) {
  LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]"this, aHandle));

  MOZ_ASSERT(!mHandle);

  mHandle = aHandle;
}

void CacheFileMetadata::ReadMetadata(CacheFileMetadataListener* aListener) {
  LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]"this,
       aListener));

  MOZ_ASSERT(!mListener);
  MOZ_ASSERT(!mHashArray);
  MOZ_ASSERT(!mBuf);
  MOZ_ASSERT(!mWriteBuf);

  nsresult rv;

  int64_t size = mHandle->FileSize();
  MOZ_ASSERT(size != -1);

  if (size == 0) {
    // this is a new entry
    LOG(
        ("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
         "metadata. [this=%p]",
         this));

    InitEmptyMetadata();
    aListener->OnMetadataRead(NS_OK);
    return;
  }

  if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2 * sizeof(uint32_t))) {
    // there must be at least checksum, header and offset
    LOG(
        ("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
         "empty metadata. [this=%p, filesize=%" PRId64 "]",
         this, size));

    InitEmptyMetadata();
    aListener->OnMetadataRead(NS_OK);
    return;
  }

  // Set offset so that we read at least kMinMetadataRead if the file is big
  // enough.
  int64_t offset;
  if (size < kMinMetadataRead) {
    offset = 0;
  } else {
    offset = size - kMinMetadataRead;
  }

  // round offset to kAlignSize blocks
  offset = (offset / kAlignSize) * kAlignSize;

  mBufSize = size - offset;
  mBuf = static_cast<char*>(moz_xmalloc(mBufSize));

  DoMemoryReport(MemoryUsage());

  LOG(
      ("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
       "offset=%" PRId64 ", filesize=%" PRId64 " [this=%p]",
       offset, size, this));

  mReadStart = mozilla::TimeStamp::Now();
  mListener = aListener;
  rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
  if (NS_FAILED(rv)) {
    LOG(
        ("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
         " synchronously, creating empty metadata. [this=%p, rv=0x%08" PRIx32
         "]",
         thisstatic_cast<uint32_t>(rv)));

    mListener = nullptr;
    InitEmptyMetadata();
    aListener->OnMetadataRead(NS_OK);
  }
}

uint32_t CacheFileMetadata::CalcMetadataSize(uint32_t aElementsSize,
                                             uint32_t aHashCount) {
  return sizeof(uint32_t) +                          // hash of the metadata
         aHashCount * sizeof(CacheHash::Hash16_t) +  // array of chunk hashes
         sizeof(CacheFileMetadataHeader) +           // metadata header
         mKey.Length() + 1 +                         // key with trailing null
         aElementsSize +                             // elements
         sizeof(uint32_t);                           // offset
}

nsresult CacheFileMetadata::WriteMetadata(
    uint32_t aOffset, CacheFileMetadataListener* aListener) {
  LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
       this, aOffset, aListener));

  MOZ_ASSERT(!mListener);
  MOZ_ASSERT(!mWriteBuf);

  nsresult rv;

  mIsDirty = false;

  mWriteBuf =
      static_cast<char*>(malloc(CalcMetadataSize(mElementsSize, mHashCount)));
  if (!mWriteBuf) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  char* p = mWriteBuf + sizeof(uint32_t);
  if (mHashCount) {
    memcpy(p, mHashArray, mHashCount * sizeof(CacheHash::Hash16_t));
    p += mHashCount * sizeof(CacheHash::Hash16_t);
  }
  mMetaHdr.WriteToBuf(p);
  p += sizeof(CacheFileMetadataHeader);
  memcpy(p, mKey.get(), mKey.Length());
  p += mKey.Length();
  *p = 0;
  p++;
  if (mElementsSize) {
    memcpy(p, mBuf, mElementsSize);
    p += mElementsSize;
  }

  CacheHash::Hash32_t hash;
  hash = CacheHash::Hash(mWriteBuf + sizeof(uint32_t),
                         p - mWriteBuf - sizeof(uint32_t));
  NetworkEndian::writeUint32(mWriteBuf, hash);

  NetworkEndian::writeUint32(p, aOffset);
  p += sizeof(uint32_t);

  char* writeBuffer = mWriteBuf;
  if (aListener) {
    mListener = aListener;
    rv = CacheFileIOManager::Write(mHandle, aOffset, writeBuffer,
                                   p - writeBuffer, truetruethis);
  } else {
    // We are not going to pass |this| as a callback so the buffer will be
    // released by CacheFileIOManager. Just null out mWriteBuf here.
    mWriteBuf = nullptr;
    rv = CacheFileIOManager::WriteWithoutCallback(mHandle, aOffset, writeBuffer,
                                                  p - writeBuffer, truetrue);
  }

  if (NS_FAILED(rv)) {
    LOG(
        ("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
         "failed synchronously. [this=%p, rv=0x%08" PRIx32 "]",
         thisstatic_cast<uint32_t>(rv)));

    mListener = nullptr;
    if (mWriteBuf) {
      CacheFileUtils::FreeBuffer(mWriteBuf);
      mWriteBuf = nullptr;
    }
    NS_ENSURE_SUCCESS(rv, rv);
  }

  DoMemoryReport(MemoryUsage());

  return NS_OK;
}

nsresult CacheFileMetadata::SyncReadMetadata(nsIFile* aFile) {
  LOG(("CacheFileMetadata::SyncReadMetadata() [this=%p]"this));

  MOZ_ASSERT(!mListener);
  MOZ_ASSERT(!mHandle);
  MOZ_ASSERT(!mHashArray);
  MOZ_ASSERT(!mBuf);
  MOZ_ASSERT(!mWriteBuf);
  MOZ_ASSERT(mKey.IsEmpty());

  nsresult rv;

  int64_t fileSize;
  rv = aFile->GetFileSize(&fileSize);
  if (NS_FAILED(rv)) {
    // Don't bloat the console
    return rv;
  }

  PRFileDesc* fd;
  rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
  NS_ENSURE_SUCCESS(rv, rv);

  int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET);
  if (offset == -1) {
    PR_Close(fd);
    return NS_ERROR_FAILURE;
  }

  uint32_t metaOffset;
  int32_t bytesRead = PR_Read(fd, &metaOffset, sizeof(uint32_t));
  if (bytesRead != sizeof(uint32_t)) {
    PR_Close(fd);
    return NS_ERROR_FAILURE;
  }

  metaOffset = NetworkEndian::readUint32(&metaOffset);
  if (metaOffset > fileSize) {
    PR_Close(fd);
    return NS_ERROR_FAILURE;
  }

  mBuf = static_cast<char*>(malloc(fileSize - metaOffset));
  if (!mBuf) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  mBufSize = fileSize - metaOffset;

  DoMemoryReport(MemoryUsage());

  offset = PR_Seek64(fd, metaOffset, PR_SEEK_SET);
  if (offset == -1) {
    PR_Close(fd);
    return NS_ERROR_FAILURE;
  }

  bytesRead = PR_Read(fd, mBuf, mBufSize);
  PR_Close(fd);
  if (bytesRead != static_cast<int32_t>(mBufSize)) {
    return NS_ERROR_FAILURE;
  }

  rv = ParseMetadata(metaOffset, 0, false);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

const char* CacheFileMetadata::GetElement(const char* aKey) {
  const char* data = mBuf;
  const char* limit = mBuf + mElementsSize;

  while (data != limit) {
    size_t maxLen = limit - data;
    size_t keyLen = strnlen(data, maxLen);
    MOZ_RELEASE_ASSERT(keyLen != maxLen,
                       "Metadata elements corrupted. Key "
                       "isn't null terminated!");
    MOZ_RELEASE_ASSERT(keyLen + 1 != maxLen,
                       "Metadata elements corrupted. "
                       "There is no value for the key!");

    const char* value = data + keyLen + 1;
    maxLen = limit - value;
    size_t valueLen = strnlen(value, maxLen);
    MOZ_RELEASE_ASSERT(valueLen != maxLen,
                       "Metadata elements corrupted. Value "
                       "isn't null terminated!");

    if (strcmp(data, aKey) == 0) {
      LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
           this, aKey));
      return value;
    }

    // point to next pair
    data += keyLen + valueLen + 2;
  }
  LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
       this, aKey));
  return nullptr;
}

nsresult CacheFileMetadata::SetElement(const char* aKey, const char* aValue) {
  LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]"this,
       aKey, aValue));

  mLock->Lock().AssertCurrentThreadOwns();

  MarkDirty();

  nsresult rv;

  const uint32_t keySize = strlen(aKey) + 1;
  char* pos = const_cast<char*>(GetElement(aKey));

  if (!aValue) {
    // No value means remove the key/value pair completely, if existing
    if (pos) {
      uint32_t oldValueSize = strlen(pos) + 1;
      uint32_t offset = pos - mBuf;
      uint32_t remainder = mElementsSize - (offset + oldValueSize);

      memmove(pos - keySize, pos + oldValueSize, remainder);
      mElementsSize -= keySize + oldValueSize;
    }
    return NS_OK;
  }

  const uint32_t valueSize = strlen(aValue) + 1;
  uint32_t newSize = mElementsSize + valueSize;
  if (pos) {
    const uint32_t oldValueSize = strlen(pos) + 1;
    const uint32_t offset = pos - mBuf;
    const uint32_t remainder = mElementsSize - (offset + oldValueSize);

    // Update the value in place
    newSize -= oldValueSize;
    rv = EnsureBuffer(newSize);
    if (NS_FAILED(rv)) {
      return rv;
    }

    // Move the remainder to the right place
    pos = mBuf + offset;
    memmove(pos + valueSize, pos + oldValueSize, remainder);
  } else {
    // allocate new meta data element
    newSize += keySize;
    rv = EnsureBuffer(newSize);
    if (NS_FAILED(rv)) {
      return rv;
    }

    // Add after last element
    pos = mBuf + mElementsSize;
    memcpy(pos, aKey, keySize);
    pos += keySize;
  }

  // Update value
  memcpy(pos, aValue, valueSize);
  mElementsSize = newSize;

  return NS_OK;
}

void CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor* aVisitor) {
  const char* data = mBuf;
  const char* limit = mBuf + mElementsSize;

  while (data < limit) {
    // Point to the value part
    const char* value = data + strlen(data) + 1;
    MOZ_ASSERT(value < limit, "Metadata elements corrupted");

    aVisitor->OnMetaDataElement(data, value);

    // Skip value part
    data = value + strlen(value) + 1;
  }

  MOZ_ASSERT(data == limit, "Metadata elements corrupted");
}

CacheHash::Hash16_t CacheFileMetadata::GetHash(uint32_t aIndex) {
  mLock->Lock().AssertCurrentThreadOwns();

  MOZ_ASSERT(aIndex < mHashCount);
  return NetworkEndian::readUint16(&mHashArray[aIndex]);
}

nsresult CacheFileMetadata::SetHash(uint32_t aIndex,
                                    CacheHash::Hash16_t aHash) {
  LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]"this, aIndex,
       aHash));

  mLock->Lock().AssertCurrentThreadOwns();

  MarkDirty();

  MOZ_ASSERT(aIndex <= mHashCount);

  if (aIndex > mHashCount) {
    return NS_ERROR_INVALID_ARG;
  }
  if (aIndex == mHashCount) {
    if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) {
      // reallocate hash array buffer
      if (mHashArraySize == 0) {
        mHashArraySize = kInitialHashArraySize * sizeof(CacheHash::Hash16_t);
      } else {
        mHashArraySize *= 2;
      }
      mHashArray = static_cast<CacheHash::Hash16_t*>(
          moz_xrealloc(mHashArray, mHashArraySize));
    }

    mHashCount++;
  }

  NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);

  DoMemoryReport(MemoryUsage());

  return NS_OK;
}

nsresult CacheFileMetadata::RemoveHash(uint32_t aIndex) {
  LOG(("CacheFileMetadata::RemoveHash() [this=%p, idx=%d]"this, aIndex));

  mLock->Lock().AssertCurrentThreadOwns();

  MarkDirty();

  MOZ_ASSERT((aIndex + 1) == mHashCount, "Can remove only last hash!");

  if (aIndex + 1 != mHashCount) {
    return NS_ERROR_INVALID_ARG;
  }

  mHashCount--;
  return NS_OK;
}

void CacheFileMetadata::AddFlags(uint32_t aFlags) {
  MarkDirty(false);
  mMetaHdr.mFlags |= aFlags;
}

void CacheFileMetadata::RemoveFlags(uint32_t aFlags) {
  MarkDirty(false);
  mMetaHdr.mFlags &= ~aFlags;
}

void CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime) {
  LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
       this, aExpirationTime));

  MarkDirty(false);
  mMetaHdr.mExpirationTime = aExpirationTime;
}

void CacheFileMetadata::SetFrecency(uint32_t aFrecency) {
  LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]"this,
       (double)aFrecency));

  MarkDirty(false);
  mMetaHdr.mFrecency = aFrecency;
}

void CacheFileMetadata::OnFetched() {
  MarkDirty(false);

  mMetaHdr.mLastFetched = NOW_SECONDS();
  ++mMetaHdr.mFetchCount;
}

void CacheFileMetadata::MarkDirty(bool aUpdateLastModified) {
  mIsDirty = true;
  if (aUpdateLastModified) {
    mMetaHdr.mLastModified = NOW_SECONDS();
  }
}

nsresult CacheFileMetadata::OnFileOpened(CacheFileHandle* aHandle,
                                         nsresult aResult) {
  MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
  return NS_ERROR_UNEXPECTED;
}

nsresult CacheFileMetadata::OnDataWritten(CacheFileHandle* aHandle,
                                          const char* aBuf, nsresult aResult) {
  LOG(
      ("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, "
       "result=0x%08" PRIx32 "]",
       this, aHandle, static_cast<uint32_t>(aResult)));

  nsCOMPtr<CacheFileMetadataListener> listener;
  {
    MutexAutoLock lock(mLock->Lock());

    MOZ_ASSERT(mListener);
    MOZ_ASSERT(mWriteBuf);

    CacheFileUtils::FreeBuffer(mWriteBuf);
    mWriteBuf = nullptr;

    mListener.swap(listener);
    DoMemoryReport(MemoryUsage());
  }

  listener->OnMetadataWritten(aResult);

  return NS_OK;
}

nsresult CacheFileMetadata::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
                                       nsresult aResult) {
  LOG((
      "CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32
      "]",
      this, aHandle, static_cast<uint32_t>(aResult)));

  MOZ_ASSERT(mListener);

  nsresult rv;
  nsCOMPtr<CacheFileMetadataListener> listener;

  auto notifyListenerOutsideLock = mozilla::MakeScopeExit([&listener] {
    if (listener) {
      listener->OnMetadataRead(NS_OK);
    }
  });

  MutexAutoLock lock(mLock->Lock());

  if (NS_FAILED(aResult)) {
    LOG(
        ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
         ", creating empty metadata. [this=%p, rv=0x%08" PRIx32 "]",
         thisstatic_cast<uint32_t>(aResult)));

    InitEmptyMetadata();

    mListener.swap(listener);
    return NS_OK;
  }

#ifndef ANDROID
  mozilla::TimeStamp readEnd = mozilla::TimeStamp::Now();
  if (mFirstRead) {
    mozilla::glean::networking::cache_metadata_first_read_time
        .AccumulateRawDuration(readEnd - mReadStart);
  } else {
    mozilla::glean::networking::cache_metadata_second_read_time
        .AccumulateRawDuration(readEnd - mReadStart);
  }
#endif

  // check whether we have read all necessary data
  uint32_t realOffset =
      NetworkEndian::readUint32(mBuf + mBufSize - sizeof(uint32_t));

  int64_t size = mHandle->FileSize();
  MOZ_ASSERT(size != -1);

  if (realOffset >= size) {
    LOG(
        ("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating "
         "empty metadata. [this=%p, realOffset=%u, size=%" PRId64 "]",
         this, realOffset, size));

    InitEmptyMetadata();

    mListener.swap(listener);
    return NS_OK;
  }

  uint32_t maxHashCount = size / kChunkSize;
  uint32_t maxMetadataSize = CalcMetadataSize(kMaxElementsSize, maxHashCount);
  if (size - realOffset > maxMetadataSize) {
    LOG(
        ("CacheFileMetadata::OnDataRead() - Invalid realOffset, metadata would "
         "be too big, creating empty metadata. [this=%p, realOffset=%u, "
         "maxMetadataSize=%u, size=%" PRId64 "]",
         this, realOffset, maxMetadataSize, size));

    InitEmptyMetadata();

    mListener.swap(listener);
    return NS_OK;
  }

  uint32_t usedOffset = size - mBufSize;

  if (realOffset < usedOffset) {
    uint32_t missing = usedOffset - realOffset;
    // we need to read more data
    char* newBuf = static_cast<char*>(realloc(mBuf, mBufSize + missing));
    if (!newBuf) {
      LOG(
          ("CacheFileMetadata::OnDataRead() - Error allocating %d more bytes "
           "for the missing part of the metadata, creating empty metadata. "
           "[this=%p]",
           missing, this));

      InitEmptyMetadata();

      mListener.swap(listener);
      return NS_OK;
    }

    mBuf = newBuf;
    memmove(mBuf + missing, mBuf, mBufSize);
    mBufSize += missing;

    DoMemoryReport(MemoryUsage());

    LOG(
        ("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
         "have full metadata. [this=%p]",
         missing, this));

    mFirstRead = false;
    mReadStart = mozilla::TimeStamp::Now();
    rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
    if (NS_FAILED(rv)) {
      LOG(
          ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() "
           "failed synchronously, creating empty metadata. [this=%p, "
           "rv=0x%08" PRIx32 "]",
           thisstatic_cast<uint32_t>(rv)));

      InitEmptyMetadata();

      mListener.swap(listener);
      return NS_OK;
    }

    return NS_OK;
  }

#ifndef ANDROID
  mozilla::glean::networking::cache_metadata_size.Accumulate(size - realOffset);
#endif

  // We have all data according to offset information at the end of the entry.
  // Try to parse it.
  rv = ParseMetadata(realOffset, realOffset - usedOffset, true);
  if (NS_FAILED(rv)) {
    LOG(
        ("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
         "empty metadata. [this=%p]",
         this));
    InitEmptyMetadata();
  } else {
    // Shrink elements buffer.
    mBuf = static_cast<char*>(moz_xrealloc(mBuf, mElementsSize));
    mBufSize = mElementsSize;

    // There is usually no or just one call to SetMetadataElement() when the
    // metadata is parsed from disk. Avoid allocating power of two sized buffer
    // which we do in case of newly created metadata.
    mAllocExactSize = true;
  }

  mListener.swap(listener);

  return NS_OK;
}

nsresult CacheFileMetadata::OnFileDoomed(CacheFileHandle* aHandle,
                                         nsresult aResult) {
  MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
  return NS_ERROR_UNEXPECTED;
}

nsresult CacheFileMetadata::OnEOFSet(CacheFileHandle* aHandle,
                                     nsresult aResult) {
  MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
  return NS_ERROR_UNEXPECTED;
}

nsresult CacheFileMetadata::OnFileRenamed(CacheFileHandle* aHandle,
                                          nsresult aResult) {
  MOZ_CRASH("CacheFileMetadata::OnFileRenamed should not be called!");
  return NS_ERROR_UNEXPECTED;
}

void CacheFileMetadata::InitEmptyMetadata() {
  if (mBuf) {
    CacheFileUtils::FreeBuffer(mBuf);
    mBuf = nullptr;
    mBufSize = 0;
  }
  mAllocExactSize = false;
  mOffset = 0;
  mMetaHdr.mVersion = kCacheEntryVersion;
  mMetaHdr.mFetchCount = 0;
  mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
  mMetaHdr.mKeySize = mKey.Length();

  // Deliberately not touching the "kCacheEntryIsPinned" flag.

  DoMemoryReport(MemoryUsage());

  // We're creating a new entry. If there is any old data truncate it.
  if (mHandle) {
    mHandle->SetPinned(Pinned());
    // We can pronounce the handle as invalid now, because it simply
    // doesn't have the correct metadata.  This will cause IO operations
    // be bypassed during shutdown (mainly dooming it, when a channel
    // is canceled by closing the window.)
    mHandle->SetInvalid();
    if (mHandle->FileExists() && mHandle->FileSize()) {
      CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
    }
  }
}

nsresult CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset,
                                          uint32_t aBufOffset, bool aHaveKey) {
  LOG(
      ("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
       "bufOffset=%d, haveKey=%u]",
       this, aMetaOffset, aBufOffset, aHaveKey));

  nsresult rv;

  uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
  uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
  uint32_t hashCount = aMetaOffset / kChunkSize;
  if (aMetaOffset % kChunkSize) hashCount++;
  uint32_t hashesLen = hashCount * sizeof(CacheHash::Hash16_t);
  uint32_t hdrOffset = hashesOffset + hashesLen;
  uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);

  LOG(
      ("CacheFileMetadata::ParseMetadata() [this=%p]\n metaposOffset=%d\n "
       "hashesOffset=%d\n hashCount=%d\n hashesLen=%d\n hdfOffset=%d\n "
       "keyOffset=%d\n",
       this, metaposOffset, hashesOffset, hashCount, hashesLen, hdrOffset,
       keyOffset));

  if (keyOffset > metaposOffset) {
    LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
         this));
    return NS_ERROR_FILE_CORRUPTED;
  }

  mMetaHdr.ReadFromBuf(mBuf + hdrOffset);

  if (mMetaHdr.mVersion == 1) {
    // Backward compatibility before we've added flags to the header
    keyOffset -= sizeof(uint32_t);
  } else if (mMetaHdr.mVersion == 2) {
    // Version 2 just lacks the ability to store alternative data. Nothing to do
    // here.
  } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
    LOG(
        ("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
         "[version=0x%x, this=%p]",
         mMetaHdr.mVersion, this));
    return NS_ERROR_UNEXPECTED;
  }

  // Update the version stored in the header to make writes
  // store the header in the current version form.
  mMetaHdr.mVersion = kCacheEntryVersion;

  uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;

  if (elementsOffset > metaposOffset) {
    LOG(
        ("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
         "[this=%p]",
         elementsOffset, this));
    return NS_ERROR_FILE_CORRUPTED;
  }

  // check that key ends with \0
  if (mBuf[elementsOffset - 1] != 0) {
    LOG(
        ("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
         "[this=%p]",
         this));
    return NS_ERROR_FILE_CORRUPTED;
  }

  if (!aHaveKey) {
    // get the key form metadata
    mKey.Assign(mBuf + keyOffset, mMetaHdr.mKeySize);

    rv = ParseKey(mKey);
    if (NS_FAILED(rv)) return rv;
  } else {
    if (mMetaHdr.mKeySize != mKey.Length()) {
      LOG(
          ("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s "
           "[this=%p]",
           nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
      return NS_ERROR_FILE_CORRUPTED;
    }

    if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
      LOG(
          ("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s "
           "[this=%p]",
           nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
      return NS_ERROR_FILE_CORRUPTED;
    }
  }

  // check metadata hash (data from hashesOffset to metaposOffset)
  CacheHash::Hash32_t hashComputed, hashExpected;
  hashComputed =
      CacheHash::Hash(mBuf + hashesOffset, metaposOffset - hashesOffset);
  hashExpected = NetworkEndian::readUint32(mBuf + aBufOffset);

  if (hashComputed != hashExpected) {
    LOG(
        ("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
         "the metadata is %x, hash in file is %x [this=%p]",
         hashComputed, hashExpected, this));
    return NS_ERROR_FILE_CORRUPTED;
  }

  // check elements
  rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
  if (NS_FAILED(rv)) return rv;

  if (mHandle) {
    if (!mHandle->SetPinned(Pinned())) {
      LOG(
          ("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
           "pinning state, truncate the file [this=%p, pinned=%d]",
           this, Pinned()));
      return NS_ERROR_FILE_CORRUPTED;
    }
  }

  mHashArraySize = hashesLen;
  mHashCount = hashCount;
  if (mHashArraySize) {
    mHashArray = static_cast<CacheHash::Hash16_t*>(moz_xmalloc(mHashArraySize));
    memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
  }

  MarkDirty();

  mElementsSize = metaposOffset - elementsOffset;
  memmove(mBuf, mBuf + elementsOffset, mElementsSize);
  mOffset = aMetaOffset;

  DoMemoryReport(MemoryUsage());

  return NS_OK;
}

nsresult CacheFileMetadata::CheckElements(const char* aBuf, uint32_t aSize) {
  if (aSize) {
    // Check if the metadata ends with a zero byte.
    if (aBuf[aSize - 1] != 0) {
      NS_ERROR("Metadata elements are not null terminated");
      LOG(
          ("CacheFileMetadata::CheckElements() - Elements are not null "
           "terminated. [this=%p]",
           this));
      return NS_ERROR_FILE_CORRUPTED;
    }
    // Check that there are an even number of zero bytes
    // to match the pattern { key \0 value \0 }
    bool odd = false;
    for (uint32_t i = 0; i < aSize; i++) {
      if (aBuf[i] == 0) odd = !odd;
    }
    if (odd) {
      NS_ERROR("Metadata elements are malformed");
      LOG(
          ("CacheFileMetadata::CheckElements() - Elements are malformed. "
           "[this=%p]",
           this));
      return NS_ERROR_FILE_CORRUPTED;
    }
  }
  return NS_OK;
}

nsresult CacheFileMetadata::EnsureBuffer(uint32_t aSize) {
  if (aSize > kMaxElementsSize) {
    return NS_ERROR_FAILURE;
  }

  if (mBufSize < aSize) {
    if (mAllocExactSize) {
      // If this is not the only allocation, use power of two for following
      // allocations.
      mAllocExactSize = false;
    } else {
      // find smallest power of 2 greater than or equal to aSize
      --aSize;
      aSize |= aSize >> 1;
      aSize |= aSize >> 2;
      aSize |= aSize >> 4;
      aSize |= aSize >> 8;
      aSize |= aSize >> 16;
      ++aSize;
    }

    if (aSize < kInitialBufSize) {
      aSize = kInitialBufSize;
    }

    char* newBuf = static_cast<char*>(realloc(mBuf, aSize));
    if (!newBuf) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
    mBufSize = aSize;
    mBuf = newBuf;

    DoMemoryReport(MemoryUsage());
  }

  return NS_OK;
}

nsresult CacheFileMetadata::ParseKey(const nsACString& aKey) {
  nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
  NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);

  mAnonymous = info->IsAnonymous();
  mOriginAttributes = *info->OriginAttributesPtr();

  return NS_OK;
}

// Memory reporting

size_t CacheFileMetadata::SizeOfExcludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  size_t n = 0;
  // mHandle reported via CacheFileIOManager.
  n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
  n += mallocSizeOf(mHashArray);
  n += mallocSizeOf(mBuf);
  // Ignore mWriteBuf, it's not safe to access it when metadata is being
  // written and it's null otherwise.
  // mListener is usually the owning CacheFile.

  return n;
}

size_t CacheFileMetadata::SizeOfIncludingThis(
    mozilla::MallocSizeOf mallocSizeOf) const {
  return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
}

}  // namespace mozilla::net

99%


¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge