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