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