tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

CacheFileMetadata.cpp (30290B)


      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/. */
      4 
      5 #include "CacheLog.h"
      6 #include "CacheFileMetadata.h"
      7 
      8 #include "CacheFileIOManager.h"
      9 #include "nsICacheEntry.h"
     10 #include "CacheHashUtils.h"
     11 #include "CacheFileChunk.h"
     12 #include "CacheFileUtils.h"
     13 #include "nsILoadContextInfo.h"
     14 #include "nsICacheEntry.h"  // for nsICacheEntryMetaDataVisitor
     15 #include "nsIFile.h"
     16 #include "mozilla/ScopeExit.h"
     17 #include "mozilla/DebugOnly.h"
     18 #include "mozilla/IntegerPrintfMacros.h"
     19 #include "mozilla/glean/NetwerkMetrics.h"
     20 #include "prnetdb.h"
     21 
     22 namespace mozilla::net {
     23 
     24 #define kMinMetadataRead 1024  // TODO find optimal value from telemetry
     25 #define kAlignSize 4096
     26 
     27 // Most of the cache entries fit into one chunk due to current chunk size. Make
     28 // sure to tweak this value if kChunkSize is going to change.
     29 #define kInitialHashArraySize 1
     30 
     31 // Initial elements buffer size.
     32 #define kInitialBufSize 64
     33 
     34 // Max size of elements in bytes.
     35 #define kMaxElementsSize (64 * 1024)
     36 
     37 #define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
     38 
     39 NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
     40 
     41 CacheFileMetadata::CacheFileMetadata(
     42    CacheFileHandle* aHandle, const nsACString& aKey,
     43    NotNull<CacheFileUtils::CacheFileLock*> aLock)
     44    : CacheMemoryConsumer(NORMAL),
     45      mHandle(aHandle),
     46      mOffset(-1),
     47      mIsDirty(false),
     48      mAnonymous(false),
     49      mAllocExactSize(false),
     50      mFirstRead(true),
     51      mLock(aLock) {
     52  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
     53       this, aHandle, PromiseFlatCString(aKey).get()));
     54 
     55  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
     56  mMetaHdr.mVersion = kCacheEntryVersion;
     57  mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
     58  mKey = aKey;
     59 
     60  DebugOnly<nsresult> rv{};
     61  rv = ParseKey(aKey);
     62  MOZ_ASSERT(NS_SUCCEEDED(rv));
     63 }
     64 
     65 CacheFileMetadata::CacheFileMetadata(
     66    bool aMemoryOnly, bool aPinned, const nsACString& aKey,
     67    NotNull<CacheFileUtils::CacheFileLock*> aLock)
     68    : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL),
     69      mIsDirty(true),
     70      mAnonymous(false),
     71      mAllocExactSize(false),
     72      mFirstRead(true),
     73      mLock(aLock) {
     74  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]", this,
     75       PromiseFlatCString(aKey).get()));
     76 
     77  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
     78  mMetaHdr.mVersion = kCacheEntryVersion;
     79  if (aPinned) {
     80    AddFlags(kCacheEntryIsPinned);
     81  }
     82  mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
     83  mKey = aKey;
     84  mMetaHdr.mKeySize = mKey.Length();
     85 
     86  DebugOnly<nsresult> rv{};
     87  rv = ParseKey(aKey);
     88  MOZ_ASSERT(NS_SUCCEEDED(rv));
     89 }
     90 
     91 CacheFileMetadata::CacheFileMetadata()
     92    : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */),
     93      mIsDirty(false),
     94      mAnonymous(false),
     95      mAllocExactSize(false),
     96      mFirstRead(true),
     97      mLock(new CacheFileUtils::CacheFileLock()) {
     98  LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]", this));
     99 
    100  memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
    101 }
    102 
    103 CacheFileMetadata::~CacheFileMetadata() {
    104  LOG(("CacheFileMetadata::~CacheFileMetadata() [this=%p]", this));
    105 
    106  MOZ_ASSERT(!mListener);
    107 
    108  if (mHashArray) {
    109    CacheFileUtils::FreeBuffer(mHashArray);
    110    mHashArray = nullptr;
    111    mHashArraySize = 0;
    112  }
    113 
    114  if (mBuf) {
    115    CacheFileUtils::FreeBuffer(mBuf);
    116    mBuf = nullptr;
    117    mBufSize = 0;
    118  }
    119 }
    120 
    121 void CacheFileMetadata::SetHandle(CacheFileHandle* aHandle) {
    122  LOG(("CacheFileMetadata::SetHandle() [this=%p, handle=%p]", this, aHandle));
    123 
    124  MOZ_ASSERT(!mHandle);
    125 
    126  mHandle = aHandle;
    127 }
    128 
    129 void CacheFileMetadata::ReadMetadata(CacheFileMetadataListener* aListener) {
    130  LOG(("CacheFileMetadata::ReadMetadata() [this=%p, listener=%p]", this,
    131       aListener));
    132 
    133  MOZ_ASSERT(!mListener);
    134  MOZ_ASSERT(!mHashArray);
    135  MOZ_ASSERT(!mBuf);
    136  MOZ_ASSERT(!mWriteBuf);
    137 
    138  nsresult rv;
    139 
    140  int64_t size = mHandle->FileSize();
    141  MOZ_ASSERT(size != -1);
    142 
    143  if (size == 0) {
    144    // this is a new entry
    145    LOG(
    146        ("CacheFileMetadata::ReadMetadata() - Filesize == 0, creating empty "
    147         "metadata. [this=%p]",
    148         this));
    149 
    150    InitEmptyMetadata();
    151    aListener->OnMetadataRead(NS_OK);
    152    return;
    153  }
    154 
    155  if (size < int64_t(sizeof(CacheFileMetadataHeader) + 2 * sizeof(uint32_t))) {
    156    // there must be at least checksum, header and offset
    157    LOG(
    158        ("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
    159         "empty metadata. [this=%p, filesize=%" PRId64 "]",
    160         this, size));
    161 
    162    InitEmptyMetadata();
    163    aListener->OnMetadataRead(NS_OK);
    164    return;
    165  }
    166 
    167  // Set offset so that we read at least kMinMetadataRead if the file is big
    168  // enough.
    169  int64_t offset;
    170  if (size < kMinMetadataRead) {
    171    offset = 0;
    172  } else {
    173    offset = size - kMinMetadataRead;
    174  }
    175 
    176  // round offset to kAlignSize blocks
    177  offset = (offset / kAlignSize) * kAlignSize;
    178 
    179  mBufSize = size - offset;
    180  mBuf = static_cast<char*>(moz_xmalloc(mBufSize));
    181 
    182  DoMemoryReport(MemoryUsage());
    183 
    184  LOG(
    185      ("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
    186       "offset=%" PRId64 ", filesize=%" PRId64 " [this=%p]",
    187       offset, size, this));
    188 
    189  mReadStart = mozilla::TimeStamp::Now();
    190  mListener = aListener;
    191  rv = CacheFileIOManager::Read(mHandle, offset, mBuf, mBufSize, this);
    192  if (NS_FAILED(rv)) {
    193    LOG(
    194        ("CacheFileMetadata::ReadMetadata() - CacheFileIOManager::Read() failed"
    195         " synchronously, creating empty metadata. [this=%p, rv=0x%08" PRIx32
    196         "]",
    197         this, static_cast<uint32_t>(rv)));
    198 
    199    mListener = nullptr;
    200    InitEmptyMetadata();
    201    aListener->OnMetadataRead(NS_OK);
    202  }
    203 }
    204 
    205 uint32_t CacheFileMetadata::CalcMetadataSize(uint32_t aElementsSize,
    206                                             uint32_t aHashCount) {
    207  return sizeof(uint32_t) +                          // hash of the metadata
    208         aHashCount * sizeof(CacheHash::Hash16_t) +  // array of chunk hashes
    209         sizeof(CacheFileMetadataHeader) +           // metadata header
    210         mKey.Length() + 1 +                         // key with trailing null
    211         aElementsSize +                             // elements
    212         sizeof(uint32_t);                           // offset
    213 }
    214 
    215 nsresult CacheFileMetadata::WriteMetadata(
    216    uint32_t aOffset, CacheFileMetadataListener* aListener) {
    217  LOG(("CacheFileMetadata::WriteMetadata() [this=%p, offset=%d, listener=%p]",
    218       this, aOffset, aListener));
    219 
    220  MOZ_ASSERT(!mListener);
    221  MOZ_ASSERT(!mWriteBuf);
    222 
    223  nsresult rv;
    224 
    225  mIsDirty = false;
    226 
    227  mWriteBuf =
    228      static_cast<char*>(malloc(CalcMetadataSize(mElementsSize, mHashCount)));
    229  if (!mWriteBuf) {
    230    return NS_ERROR_OUT_OF_MEMORY;
    231  }
    232 
    233  char* p = mWriteBuf + sizeof(uint32_t);
    234  if (mHashCount) {
    235    memcpy(p, mHashArray, mHashCount * sizeof(CacheHash::Hash16_t));
    236    p += mHashCount * sizeof(CacheHash::Hash16_t);
    237  }
    238  mMetaHdr.WriteToBuf(p);
    239  p += sizeof(CacheFileMetadataHeader);
    240  memcpy(p, mKey.get(), mKey.Length());
    241  p += mKey.Length();
    242  *p = 0;
    243  p++;
    244  if (mElementsSize) {
    245    memcpy(p, mBuf, mElementsSize);
    246    p += mElementsSize;
    247  }
    248  LOG(("CacheFileMetadata::WriteMetadata() [this=%p, key=%s, mElementsSize=%d]",
    249       this, mKey.get(), mElementsSize));
    250  CacheHash::Hash32_t hash;
    251  hash = CacheHash::Hash(mWriteBuf + sizeof(uint32_t),
    252                         p - mWriteBuf - sizeof(uint32_t));
    253  NetworkEndian::writeUint32(mWriteBuf, hash);
    254 
    255  NetworkEndian::writeUint32(p, aOffset);
    256  p += sizeof(uint32_t);
    257 
    258  char* writeBuffer = mWriteBuf;
    259  if (aListener) {
    260    mListener = aListener;
    261    rv = CacheFileIOManager::Write(mHandle, aOffset, writeBuffer,
    262                                   p - writeBuffer, true, true, this);
    263  } else {
    264    // We are not going to pass |this| as a callback so the buffer will be
    265    // released by CacheFileIOManager. Just null out mWriteBuf here.
    266    mWriteBuf = nullptr;
    267    rv = CacheFileIOManager::WriteWithoutCallback(mHandle, aOffset, writeBuffer,
    268                                                  p - writeBuffer, true, true);
    269  }
    270 
    271  if (NS_FAILED(rv)) {
    272    LOG(
    273        ("CacheFileMetadata::WriteMetadata() - CacheFileIOManager::Write() "
    274         "failed synchronously. [this=%p, rv=0x%08" PRIx32 "]",
    275         this, static_cast<uint32_t>(rv)));
    276 
    277    mListener = nullptr;
    278    if (mWriteBuf) {
    279      CacheFileUtils::FreeBuffer(mWriteBuf);
    280      mWriteBuf = nullptr;
    281    }
    282    NS_ENSURE_SUCCESS(rv, rv);
    283  }
    284 
    285  DoMemoryReport(MemoryUsage());
    286 
    287  return NS_OK;
    288 }
    289 
    290 nsresult CacheFileMetadata::SyncReadMetadata(nsIFile* aFile) {
    291  LOG(("CacheFileMetadata::SyncReadMetadata() [this=%p]", this));
    292 
    293  MOZ_ASSERT(!mListener);
    294  MOZ_ASSERT(!mHandle);
    295  MOZ_ASSERT(!mHashArray);
    296  MOZ_ASSERT(!mBuf);
    297  MOZ_ASSERT(!mWriteBuf);
    298  MOZ_ASSERT(mKey.IsEmpty());
    299 
    300  nsresult rv;
    301 
    302  int64_t fileSize;
    303  rv = aFile->GetFileSize(&fileSize);
    304  if (NS_FAILED(rv)) {
    305    // Don't bloat the console
    306    return rv;
    307  }
    308 
    309  PRFileDesc* fd;
    310  rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0600, &fd);
    311  NS_ENSURE_SUCCESS(rv, rv);
    312 
    313  int64_t offset = PR_Seek64(fd, fileSize - sizeof(uint32_t), PR_SEEK_SET);
    314  if (offset == -1) {
    315    PR_Close(fd);
    316    return NS_ERROR_FAILURE;
    317  }
    318 
    319  uint32_t metaOffset;
    320  int32_t bytesRead = PR_Read(fd, &metaOffset, sizeof(uint32_t));
    321  if (bytesRead != sizeof(uint32_t)) {
    322    PR_Close(fd);
    323    return NS_ERROR_FAILURE;
    324  }
    325 
    326  metaOffset = NetworkEndian::readUint32(&metaOffset);
    327  if (metaOffset > fileSize) {
    328    PR_Close(fd);
    329    return NS_ERROR_FAILURE;
    330  }
    331 
    332  mBuf = static_cast<char*>(malloc(fileSize - metaOffset));
    333  if (!mBuf) {
    334    return NS_ERROR_OUT_OF_MEMORY;
    335  }
    336  mBufSize = fileSize - metaOffset;
    337 
    338  DoMemoryReport(MemoryUsage());
    339 
    340  offset = PR_Seek64(fd, metaOffset, PR_SEEK_SET);
    341  if (offset == -1) {
    342    PR_Close(fd);
    343    return NS_ERROR_FAILURE;
    344  }
    345 
    346  bytesRead = PR_Read(fd, mBuf, mBufSize);
    347  PR_Close(fd);
    348  if (bytesRead != static_cast<int32_t>(mBufSize)) {
    349    return NS_ERROR_FAILURE;
    350  }
    351 
    352  rv = ParseMetadata(metaOffset, 0, false);
    353  NS_ENSURE_SUCCESS(rv, rv);
    354 
    355  return NS_OK;
    356 }
    357 
    358 void CacheFileMetadata::HandleCorruptMetaData() const {
    359  if (mHandle) {
    360    CacheFileIOManager::DoomFile(mHandle, nullptr);
    361  }
    362 }
    363 
    364 const char* CacheFileMetadata::GetElement(const char* aKey) {
    365  const char* data = mBuf;
    366  const char* limit = mBuf + mElementsSize;
    367 
    368  while (data != limit) {
    369    size_t maxLen = limit - data;
    370    size_t keyLen = strnlen(data, maxLen);
    371 
    372    if (keyLen == maxLen ||      // Key isn't null terminated!
    373        keyLen + 1 == maxLen) {  // There is no value for the key!
    374      HandleCorruptMetaData();
    375      return nullptr;
    376    }
    377 
    378    const char* value = data + keyLen + 1;
    379    maxLen = limit - value;
    380    size_t valueLen = strnlen(value, maxLen);
    381    if (valueLen == maxLen) {  // Value isn't null terminated
    382      HandleCorruptMetaData();
    383      return nullptr;
    384    }
    385 
    386    if (strcmp(data, aKey) == 0) {
    387      LOG(("CacheFileMetadata::GetElement() - Key found [this=%p, key=%s]",
    388           this, aKey));
    389      return value;
    390    }
    391 
    392    // point to next pair
    393    data += keyLen + valueLen + 2;
    394  }
    395  LOG(("CacheFileMetadata::GetElement() - Key not found [this=%p, key=%s]",
    396       this, aKey));
    397  return nullptr;
    398 }
    399 
    400 nsresult CacheFileMetadata::SetElement(const char* aKey, const char* aValue) {
    401  LOG(("CacheFileMetadata::SetElement() [this=%p, key=%s, value=%p]", this,
    402       aKey, aValue));
    403 
    404  mLock->Lock().AssertCurrentThreadOwns();
    405 
    406  MarkDirty();
    407 
    408  nsresult rv;
    409 
    410  const uint32_t keySize = strlen(aKey) + 1;
    411  char* pos = const_cast<char*>(GetElement(aKey));
    412 
    413  if (!aValue) {
    414    // No value means remove the key/value pair completely, if existing
    415    if (pos) {
    416      uint32_t oldValueSize = strlen(pos) + 1;
    417      uint32_t offset = pos - mBuf;
    418      uint32_t remainder = mElementsSize - (offset + oldValueSize);
    419 
    420      memmove(pos - keySize, pos + oldValueSize, remainder);
    421      mElementsSize -= keySize + oldValueSize;
    422    }
    423    return NS_OK;
    424  }
    425 
    426  const uint32_t valueSize = strlen(aValue) + 1;
    427  uint32_t newSize = mElementsSize + valueSize;
    428  if (pos) {
    429    const uint32_t oldValueSize = strlen(pos) + 1;
    430    const uint32_t offset = pos - mBuf;
    431    const uint32_t remainder = mElementsSize - (offset + oldValueSize);
    432 
    433    // Update the value in place
    434    newSize -= oldValueSize;
    435    rv = EnsureBuffer(newSize);
    436    if (NS_FAILED(rv)) {
    437      return rv;
    438    }
    439 
    440    // Move the remainder to the right place
    441    pos = mBuf + offset;
    442    memmove(pos + valueSize, pos + oldValueSize, remainder);
    443  } else {
    444    // allocate new meta data element
    445    newSize += keySize;
    446    rv = EnsureBuffer(newSize);
    447    if (NS_FAILED(rv)) {
    448      return rv;
    449    }
    450 
    451    // Add after last element
    452    pos = mBuf + mElementsSize;
    453    memcpy(pos, aKey, keySize);
    454    pos += keySize;
    455  }
    456 
    457  // Update value
    458  memcpy(pos, aValue, valueSize);
    459  mElementsSize = newSize;
    460 
    461  return NS_OK;
    462 }
    463 
    464 void CacheFileMetadata::Visit(nsICacheEntryMetaDataVisitor* aVisitor) {
    465  const char* data = mBuf;
    466  const char* limit = mBuf + mElementsSize;
    467 
    468  while (data < limit) {
    469    // Point to the value part
    470    const char* value = data + strlen(data) + 1;
    471    MOZ_ASSERT(value < limit, "Metadata elements corrupted");
    472 
    473    aVisitor->OnMetaDataElement(data, value);
    474 
    475    // Skip value part
    476    data = value + strlen(value) + 1;
    477  }
    478 
    479  MOZ_ASSERT(data == limit, "Metadata elements corrupted");
    480 }
    481 
    482 CacheHash::Hash16_t CacheFileMetadata::GetHash(uint32_t aIndex) {
    483  mLock->Lock().AssertCurrentThreadOwns();
    484 
    485  MOZ_ASSERT(aIndex < mHashCount);
    486  return NetworkEndian::readUint16(&mHashArray[aIndex]);
    487 }
    488 
    489 nsresult CacheFileMetadata::SetHash(uint32_t aIndex,
    490                                    CacheHash::Hash16_t aHash) {
    491  LOG(("CacheFileMetadata::SetHash() [this=%p, idx=%d, hash=%x]", this, aIndex,
    492       aHash));
    493 
    494  mLock->Lock().AssertCurrentThreadOwns();
    495 
    496  MarkDirty();
    497 
    498  MOZ_ASSERT(aIndex <= mHashCount);
    499 
    500  if (aIndex > mHashCount) {
    501    return NS_ERROR_INVALID_ARG;
    502  }
    503  if (aIndex == mHashCount) {
    504    if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) {
    505      // reallocate hash array buffer
    506      if (mHashArraySize == 0) {
    507        mHashArraySize = kInitialHashArraySize * sizeof(CacheHash::Hash16_t);
    508      } else {
    509        mHashArraySize *= 2;
    510      }
    511      mHashArray = static_cast<CacheHash::Hash16_t*>(
    512          moz_xrealloc(mHashArray, mHashArraySize));
    513    }
    514 
    515    mHashCount++;
    516  }
    517 
    518  NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
    519 
    520  DoMemoryReport(MemoryUsage());
    521 
    522  return NS_OK;
    523 }
    524 
    525 nsresult CacheFileMetadata::RemoveHash(uint32_t aIndex) {
    526  LOG(("CacheFileMetadata::RemoveHash() [this=%p, idx=%d]", this, aIndex));
    527 
    528  mLock->Lock().AssertCurrentThreadOwns();
    529 
    530  MarkDirty();
    531 
    532  MOZ_ASSERT((aIndex + 1) == mHashCount, "Can remove only last hash!");
    533 
    534  if (aIndex + 1 != mHashCount) {
    535    return NS_ERROR_INVALID_ARG;
    536  }
    537 
    538  mHashCount--;
    539  return NS_OK;
    540 }
    541 
    542 void CacheFileMetadata::AddFlags(uint32_t aFlags) {
    543  MarkDirty(false);
    544  mMetaHdr.mFlags |= aFlags;
    545 }
    546 
    547 void CacheFileMetadata::RemoveFlags(uint32_t aFlags) {
    548  MarkDirty(false);
    549  mMetaHdr.mFlags &= ~aFlags;
    550 }
    551 
    552 void CacheFileMetadata::SetExpirationTime(uint32_t aExpirationTime) {
    553  LOG(("CacheFileMetadata::SetExpirationTime() [this=%p, expirationTime=%d]",
    554       this, aExpirationTime));
    555 
    556  MarkDirty(false);
    557  mMetaHdr.mExpirationTime = aExpirationTime;
    558 }
    559 
    560 void CacheFileMetadata::SetFrecency(uint32_t aFrecency) {
    561  LOG(("CacheFileMetadata::SetFrecency() [this=%p, frecency=%f]", this,
    562       (double)aFrecency));
    563 
    564  MarkDirty(false);
    565  mMetaHdr.mFrecency = aFrecency;
    566 }
    567 
    568 void CacheFileMetadata::OnFetched() {
    569  MarkDirty(false);
    570 
    571  mMetaHdr.mLastFetched = NOW_SECONDS();
    572  ++mMetaHdr.mFetchCount;
    573 }
    574 
    575 void CacheFileMetadata::MarkDirty(bool aUpdateLastModified) {
    576  mIsDirty = true;
    577  if (aUpdateLastModified) {
    578    mMetaHdr.mLastModified = NOW_SECONDS();
    579  }
    580 }
    581 
    582 nsresult CacheFileMetadata::OnFileOpened(CacheFileHandle* aHandle,
    583                                         nsresult aResult) {
    584  MOZ_CRASH("CacheFileMetadata::OnFileOpened should not be called!");
    585  return NS_ERROR_UNEXPECTED;
    586 }
    587 
    588 nsresult CacheFileMetadata::OnDataWritten(CacheFileHandle* aHandle,
    589                                          const char* aBuf, nsresult aResult) {
    590  LOG(
    591      ("CacheFileMetadata::OnDataWritten() [this=%p, handle=%p, "
    592       "result=0x%08" PRIx32 "]",
    593       this, aHandle, static_cast<uint32_t>(aResult)));
    594 
    595  nsCOMPtr<CacheFileMetadataListener> listener;
    596  {
    597    MutexAutoLock lock(mLock->Lock());
    598 
    599    MOZ_ASSERT(mListener);
    600    MOZ_ASSERT(mWriteBuf);
    601 
    602    CacheFileUtils::FreeBuffer(mWriteBuf);
    603    mWriteBuf = nullptr;
    604 
    605    mListener.swap(listener);
    606    DoMemoryReport(MemoryUsage());
    607  }
    608 
    609  listener->OnMetadataWritten(aResult);
    610 
    611  return NS_OK;
    612 }
    613 
    614 nsresult CacheFileMetadata::OnDataRead(CacheFileHandle* aHandle, char* aBuf,
    615                                       nsresult aResult) {
    616  LOG((
    617      "CacheFileMetadata::OnDataRead() [this=%p, handle=%p, result=0x%08" PRIx32
    618      "]",
    619      this, aHandle, static_cast<uint32_t>(aResult)));
    620 
    621  MOZ_ASSERT(mListener);
    622 
    623  nsresult rv;
    624  nsCOMPtr<CacheFileMetadataListener> listener;
    625 
    626  auto notifyListenerOutsideLock = mozilla::MakeScopeExit([&listener] {
    627    if (listener) {
    628      listener->OnMetadataRead(NS_OK);
    629    }
    630  });
    631 
    632  MutexAutoLock lock(mLock->Lock());
    633 
    634  if (NS_FAILED(aResult)) {
    635    LOG(
    636        ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() failed"
    637         ", creating empty metadata. [this=%p, rv=0x%08" PRIx32 "]",
    638         this, static_cast<uint32_t>(aResult)));
    639 
    640    InitEmptyMetadata();
    641 
    642    mListener.swap(listener);
    643    return NS_OK;
    644  }
    645 
    646 #ifndef ANDROID
    647  mozilla::TimeStamp readEnd = mozilla::TimeStamp::Now();
    648  if (mFirstRead) {
    649    mozilla::glean::networking::cache_metadata_first_read_time
    650        .AccumulateRawDuration(readEnd - mReadStart);
    651  } else {
    652    mozilla::glean::networking::cache_metadata_second_read_time
    653        .AccumulateRawDuration(readEnd - mReadStart);
    654  }
    655 #endif
    656 
    657  // check whether we have read all necessary data
    658  uint32_t realOffset =
    659      NetworkEndian::readUint32(mBuf + mBufSize - sizeof(uint32_t));
    660 
    661  int64_t size = mHandle->FileSize();
    662  MOZ_ASSERT(size != -1);
    663 
    664  if (realOffset >= size) {
    665    LOG(
    666        ("CacheFileMetadata::OnDataRead() - Invalid realOffset, creating "
    667         "empty metadata. [this=%p, realOffset=%u, size=%" PRId64 "]",
    668         this, realOffset, size));
    669 
    670    InitEmptyMetadata();
    671 
    672    mListener.swap(listener);
    673    return NS_OK;
    674  }
    675 
    676  uint32_t maxHashCount = size / kChunkSize;
    677  uint32_t maxMetadataSize = CalcMetadataSize(kMaxElementsSize, maxHashCount);
    678  if (size - realOffset > maxMetadataSize) {
    679    LOG(
    680        ("CacheFileMetadata::OnDataRead() - Invalid realOffset, metadata would "
    681         "be too big, creating empty metadata. [this=%p, realOffset=%u, "
    682         "maxMetadataSize=%u, size=%" PRId64 "]",
    683         this, realOffset, maxMetadataSize, size));
    684 
    685    InitEmptyMetadata();
    686 
    687    mListener.swap(listener);
    688    return NS_OK;
    689  }
    690 
    691  uint32_t usedOffset = size - mBufSize;
    692 
    693  if (realOffset < usedOffset) {
    694    uint32_t missing = usedOffset - realOffset;
    695    // we need to read more data
    696    char* newBuf = static_cast<char*>(realloc(mBuf, mBufSize + missing));
    697    if (!newBuf) {
    698      LOG(
    699          ("CacheFileMetadata::OnDataRead() - Error allocating %d more bytes "
    700           "for the missing part of the metadata, creating empty metadata. "
    701           "[this=%p]",
    702           missing, this));
    703 
    704      InitEmptyMetadata();
    705 
    706      mListener.swap(listener);
    707      return NS_OK;
    708    }
    709 
    710    mBuf = newBuf;
    711    memmove(mBuf + missing, mBuf, mBufSize);
    712    mBufSize += missing;
    713 
    714    DoMemoryReport(MemoryUsage());
    715 
    716    LOG(
    717        ("CacheFileMetadata::OnDataRead() - We need to read %d more bytes to "
    718         "have full metadata. [this=%p]",
    719         missing, this));
    720 
    721    mFirstRead = false;
    722    mReadStart = mozilla::TimeStamp::Now();
    723    rv = CacheFileIOManager::Read(mHandle, realOffset, mBuf, missing, this);
    724    if (NS_FAILED(rv)) {
    725      LOG(
    726          ("CacheFileMetadata::OnDataRead() - CacheFileIOManager::Read() "
    727           "failed synchronously, creating empty metadata. [this=%p, "
    728           "rv=0x%08" PRIx32 "]",
    729           this, static_cast<uint32_t>(rv)));
    730 
    731      InitEmptyMetadata();
    732 
    733      mListener.swap(listener);
    734      return NS_OK;
    735    }
    736 
    737    return NS_OK;
    738  }
    739 
    740 #ifndef ANDROID
    741  mozilla::glean::networking::cache_metadata_size.Accumulate(size - realOffset);
    742 #endif
    743 
    744  // We have all data according to offset information at the end of the entry.
    745  // Try to parse it.
    746  rv = ParseMetadata(realOffset, realOffset - usedOffset, true);
    747  if (NS_FAILED(rv)) {
    748    LOG(
    749        ("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
    750         "empty metadata. [this=%p]",
    751         this));
    752    InitEmptyMetadata();
    753  } else {
    754    // Shrink elements buffer.
    755    mBuf = static_cast<char*>(moz_xrealloc(mBuf, mElementsSize));
    756    mBufSize = mElementsSize;
    757 
    758    // There is usually no or just one call to SetMetadataElement() when the
    759    // metadata is parsed from disk. Avoid allocating power of two sized buffer
    760    // which we do in case of newly created metadata.
    761    mAllocExactSize = true;
    762  }
    763 
    764  mListener.swap(listener);
    765 
    766  return NS_OK;
    767 }
    768 
    769 nsresult CacheFileMetadata::OnFileDoomed(CacheFileHandle* aHandle,
    770                                         nsresult aResult) {
    771  MOZ_CRASH("CacheFileMetadata::OnFileDoomed should not be called!");
    772  return NS_ERROR_UNEXPECTED;
    773 }
    774 
    775 nsresult CacheFileMetadata::OnEOFSet(CacheFileHandle* aHandle,
    776                                     nsresult aResult) {
    777  MOZ_CRASH("CacheFileMetadata::OnEOFSet should not be called!");
    778  return NS_ERROR_UNEXPECTED;
    779 }
    780 
    781 nsresult CacheFileMetadata::OnFileRenamed(CacheFileHandle* aHandle,
    782                                          nsresult aResult) {
    783  MOZ_CRASH("CacheFileMetadata::OnFileRenamed should not be called!");
    784  return NS_ERROR_UNEXPECTED;
    785 }
    786 
    787 void CacheFileMetadata::InitEmptyMetadata() {
    788  if (mBuf) {
    789    CacheFileUtils::FreeBuffer(mBuf);
    790    mBuf = nullptr;
    791    mBufSize = 0;
    792  }
    793  mAllocExactSize = false;
    794  mOffset = 0;
    795  mMetaHdr.mVersion = kCacheEntryVersion;
    796  mMetaHdr.mFetchCount = 0;
    797  mMetaHdr.mExpirationTime = nsICacheEntry::NO_EXPIRATION_TIME;
    798  mMetaHdr.mKeySize = mKey.Length();
    799 
    800  // Deliberately not touching the "kCacheEntryIsPinned" flag.
    801 
    802  DoMemoryReport(MemoryUsage());
    803 
    804  // We're creating a new entry. If there is any old data truncate it.
    805  if (mHandle) {
    806    mHandle->SetPinned(Pinned());
    807    // We can pronounce the handle as invalid now, because it simply
    808    // doesn't have the correct metadata.  This will cause IO operations
    809    // be bypassed during shutdown (mainly dooming it, when a channel
    810    // is canceled by closing the window.)
    811    mHandle->SetInvalid();
    812    if (mHandle->FileExists() && mHandle->FileSize()) {
    813      CacheFileIOManager::TruncateSeekSetEOF(mHandle, 0, 0, nullptr);
    814    }
    815  }
    816 }
    817 
    818 nsresult CacheFileMetadata::ParseMetadata(uint32_t aMetaOffset,
    819                                          uint32_t aBufOffset, bool aHaveKey) {
    820  LOG(
    821      ("CacheFileMetadata::ParseMetadata() [this=%p, metaOffset=%d, "
    822       "bufOffset=%d, haveKey=%u]",
    823       this, aMetaOffset, aBufOffset, aHaveKey));
    824 
    825  nsresult rv;
    826 
    827  uint32_t metaposOffset = mBufSize - sizeof(uint32_t);
    828  uint32_t hashesOffset = aBufOffset + sizeof(uint32_t);
    829  uint32_t hashCount = aMetaOffset / kChunkSize;
    830  if (aMetaOffset % kChunkSize) hashCount++;
    831  uint32_t hashesLen = hashCount * sizeof(CacheHash::Hash16_t);
    832  uint32_t hdrOffset = hashesOffset + hashesLen;
    833  uint32_t keyOffset = hdrOffset + sizeof(CacheFileMetadataHeader);
    834 
    835  LOG(
    836      ("CacheFileMetadata::ParseMetadata() [this=%p]\n  metaposOffset=%d\n  "
    837       "hashesOffset=%d\n  hashCount=%d\n  hashesLen=%d\n  hdfOffset=%d\n  "
    838       "keyOffset=%d\n",
    839       this, metaposOffset, hashesOffset, hashCount, hashesLen, hdrOffset,
    840       keyOffset));
    841 
    842  if (keyOffset > metaposOffset) {
    843    LOG(("CacheFileMetadata::ParseMetadata() - Wrong keyOffset! [this=%p]",
    844         this));
    845    return NS_ERROR_FILE_CORRUPTED;
    846  }
    847 
    848  mMetaHdr.ReadFromBuf(mBuf + hdrOffset);
    849 
    850  if (mMetaHdr.mVersion == 1) {
    851    // Backward compatibility before we've added flags to the header
    852    keyOffset -= sizeof(uint32_t);
    853  } else if (mMetaHdr.mVersion == 2) {
    854    // Version 2 just lacks the ability to store alternative data. Nothing to do
    855    // here.
    856  } else if (mMetaHdr.mVersion != kCacheEntryVersion) {
    857    LOG(
    858        ("CacheFileMetadata::ParseMetadata() - Not a version we understand to. "
    859         "[version=0x%x, this=%p]",
    860         mMetaHdr.mVersion, this));
    861    return NS_ERROR_UNEXPECTED;
    862  }
    863 
    864  // Update the version stored in the header to make writes
    865  // store the header in the current version form.
    866  mMetaHdr.mVersion = kCacheEntryVersion;
    867 
    868  uint32_t elementsOffset = mMetaHdr.mKeySize + keyOffset + 1;
    869 
    870  if (elementsOffset > metaposOffset) {
    871    LOG(
    872        ("CacheFileMetadata::ParseMetadata() - Wrong elementsOffset %d "
    873         "[this=%p]",
    874         elementsOffset, this));
    875    return NS_ERROR_FILE_CORRUPTED;
    876  }
    877 
    878  // check that key ends with \0
    879  if (mBuf[elementsOffset - 1] != 0) {
    880    LOG(
    881        ("CacheFileMetadata::ParseMetadata() - Elements not null terminated. "
    882         "[this=%p]",
    883         this));
    884    return NS_ERROR_FILE_CORRUPTED;
    885  }
    886 
    887  if (!aHaveKey) {
    888    // get the key form metadata
    889    mKey.Assign(mBuf + keyOffset, mMetaHdr.mKeySize);
    890 
    891    rv = ParseKey(mKey);
    892    if (NS_FAILED(rv)) return rv;
    893  } else {
    894    if (mMetaHdr.mKeySize != mKey.Length()) {
    895      LOG(
    896          ("CacheFileMetadata::ParseMetadata() - Key collision (1), key=%s "
    897           "[this=%p]",
    898           nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
    899      return NS_ERROR_FILE_CORRUPTED;
    900    }
    901 
    902    if (memcmp(mKey.get(), mBuf + keyOffset, mKey.Length()) != 0) {
    903      LOG(
    904          ("CacheFileMetadata::ParseMetadata() - Key collision (2), key=%s "
    905           "[this=%p]",
    906           nsCString(mBuf + keyOffset, mMetaHdr.mKeySize).get(), this));
    907      return NS_ERROR_FILE_CORRUPTED;
    908    }
    909  }
    910 
    911  // check metadata hash (data from hashesOffset to metaposOffset)
    912  CacheHash::Hash32_t hashComputed, hashExpected;
    913  hashComputed =
    914      CacheHash::Hash(mBuf + hashesOffset, metaposOffset - hashesOffset);
    915  hashExpected = NetworkEndian::readUint32(mBuf + aBufOffset);
    916 
    917  if (hashComputed != hashExpected) {
    918    LOG(
    919        ("CacheFileMetadata::ParseMetadata() - Metadata hash mismatch! Hash of "
    920         "the metadata is %x, hash in file is %x [this=%p]",
    921         hashComputed, hashExpected, this));
    922    return NS_ERROR_FILE_CORRUPTED;
    923  }
    924 
    925  // check elements
    926  rv = CheckElements(mBuf + elementsOffset, metaposOffset - elementsOffset);
    927  if (NS_FAILED(rv)) return rv;
    928 
    929  if (mHandle) {
    930    if (!mHandle->SetPinned(Pinned())) {
    931      LOG(
    932          ("CacheFileMetadata::ParseMetadata() - handle was doomed for this "
    933           "pinning state, truncate the file [this=%p, pinned=%d]",
    934           this, Pinned()));
    935      return NS_ERROR_FILE_CORRUPTED;
    936    }
    937  }
    938 
    939  mHashArraySize = hashesLen;
    940  mHashCount = hashCount;
    941  if (mHashArraySize) {
    942    mHashArray = static_cast<CacheHash::Hash16_t*>(moz_xmalloc(mHashArraySize));
    943    memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
    944  }
    945 
    946  MarkDirty();
    947 
    948  mElementsSize = metaposOffset - elementsOffset;
    949  memmove(mBuf, mBuf + elementsOffset, mElementsSize);
    950  mOffset = aMetaOffset;
    951 
    952  DoMemoryReport(MemoryUsage());
    953 
    954  return NS_OK;
    955 }
    956 
    957 nsresult CacheFileMetadata::CheckElements(const char* aBuf, uint32_t aSize) {
    958  if (aSize) {
    959    // Check if the metadata ends with a zero byte.
    960    if (aBuf[aSize - 1] != 0) {
    961      NS_ERROR("Metadata elements are not null terminated");
    962      LOG(
    963          ("CacheFileMetadata::CheckElements() - Elements are not null "
    964           "terminated. [this=%p]",
    965           this));
    966      return NS_ERROR_FILE_CORRUPTED;
    967    }
    968    // Check that there are an even number of zero bytes
    969    // to match the pattern { key \0 value \0 }
    970    bool odd = false;
    971    for (uint32_t i = 0; i < aSize; i++) {
    972      if (aBuf[i] == 0) odd = !odd;
    973    }
    974    if (odd) {
    975      NS_ERROR("Metadata elements are malformed");
    976      LOG(
    977          ("CacheFileMetadata::CheckElements() - Elements are malformed. "
    978           "[this=%p]",
    979           this));
    980      return NS_ERROR_FILE_CORRUPTED;
    981    }
    982  }
    983  return NS_OK;
    984 }
    985 
    986 nsresult CacheFileMetadata::EnsureBuffer(uint32_t aSize) {
    987  if (aSize > kMaxElementsSize) {
    988    return NS_ERROR_FAILURE;
    989  }
    990 
    991  if (mBufSize < aSize) {
    992    if (mAllocExactSize) {
    993      // If this is not the only allocation, use power of two for following
    994      // allocations.
    995      mAllocExactSize = false;
    996    } else {
    997      // find smallest power of 2 greater than or equal to aSize
    998      --aSize;
    999      aSize |= aSize >> 1;
   1000      aSize |= aSize >> 2;
   1001      aSize |= aSize >> 4;
   1002      aSize |= aSize >> 8;
   1003      aSize |= aSize >> 16;
   1004      ++aSize;
   1005    }
   1006 
   1007    if (aSize < kInitialBufSize) {
   1008      aSize = kInitialBufSize;
   1009    }
   1010 
   1011    char* newBuf = static_cast<char*>(realloc(mBuf, aSize));
   1012    if (!newBuf) {
   1013      return NS_ERROR_OUT_OF_MEMORY;
   1014    }
   1015    mBufSize = aSize;
   1016    mBuf = newBuf;
   1017 
   1018    DoMemoryReport(MemoryUsage());
   1019  }
   1020 
   1021  return NS_OK;
   1022 }
   1023 
   1024 nsresult CacheFileMetadata::ParseKey(const nsACString& aKey) {
   1025  nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
   1026  NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);
   1027 
   1028  mAnonymous = info->IsAnonymous();
   1029  mOriginAttributes = *info->OriginAttributesPtr();
   1030 
   1031  return NS_OK;
   1032 }
   1033 
   1034 // Memory reporting
   1035 
   1036 size_t CacheFileMetadata::SizeOfExcludingThis(
   1037    mozilla::MallocSizeOf mallocSizeOf) const {
   1038  size_t n = 0;
   1039  // mHandle reported via CacheFileIOManager.
   1040  n += mKey.SizeOfExcludingThisIfUnshared(mallocSizeOf);
   1041  n += mallocSizeOf(mHashArray);
   1042  n += mallocSizeOf(mBuf);
   1043  // Ignore mWriteBuf, it's not safe to access it when metadata is being
   1044  // written and it's null otherwise.
   1045  // mListener is usually the owning CacheFile.
   1046 
   1047  return n;
   1048 }
   1049 
   1050 size_t CacheFileMetadata::SizeOfIncludingThis(
   1051    mozilla::MallocSizeOf mallocSizeOf) const {
   1052  return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
   1053 }
   1054 
   1055 }  // namespace mozilla::net