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