Coverage Report

Created: 2018-09-25 14:53

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