Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/cache/nsDiskCacheMap.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* vim:set ts=4 sw=4 sts=4 cin et: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "nsCache.h"
8
#include "nsDiskCacheMap.h"
9
#include "nsDiskCacheBinding.h"
10
#include "nsDiskCacheEntry.h"
11
#include "nsDiskCacheDevice.h"
12
#include "nsCacheService.h"
13
14
#include <string.h>
15
#include "nsPrintfCString.h"
16
17
#include "nsISerializable.h"
18
#include "nsSerializationHelper.h"
19
20
#include "mozilla/MemoryReporting.h"
21
#include "mozilla/Sprintf.h"
22
#include "mozilla/Telemetry.h"
23
#include <algorithm>
24
25
using namespace mozilla;
26
27
/******************************************************************************
28
 *  nsDiskCacheMap
29
 *****************************************************************************/
30
31
/**
32
 *  File operations
33
 */
34
35
nsresult
36
nsDiskCacheMap::Open(nsIFile *  cacheDirectory,
37
                     nsDiskCache::CorruptCacheInfo *  corruptInfo)
38
0
{
39
0
    NS_ENSURE_ARG_POINTER(corruptInfo);
40
0
41
0
    // Assume we have an unexpected error until we find otherwise.
42
0
    *corruptInfo = nsDiskCache::kUnexpectedError;
43
0
    NS_ENSURE_ARG_POINTER(cacheDirectory);
44
0
    if (mMapFD)  return NS_ERROR_ALREADY_INITIALIZED;
45
0
46
0
    mCacheDirectory = cacheDirectory;   // save a reference for ourselves
47
0
48
0
    // create nsIFile for _CACHE_MAP_
49
0
    nsresult rv;
50
0
    nsCOMPtr<nsIFile> file;
51
0
    rv = cacheDirectory->Clone(getter_AddRefs(file));
52
0
    rv = file->AppendNative(NS_LITERAL_CSTRING("_CACHE_MAP_"));
53
0
    NS_ENSURE_SUCCESS(rv, rv);
54
0
55
0
    // open the file - restricted to user, the data could be confidential
56
0
    rv = file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mMapFD);
57
0
    if (NS_FAILED(rv)) {
58
0
        *corruptInfo = nsDiskCache::kOpenCacheMapError;
59
0
        NS_WARNING("Could not open cache map file");
60
0
        return NS_ERROR_FILE_CORRUPTED;
61
0
    }
62
0
63
0
    bool cacheFilesExist = CacheFilesExist();
64
0
    rv = NS_ERROR_FILE_CORRUPTED;  // presume the worst
65
0
    uint32_t mapSize = PR_Available(mMapFD);
66
0
67
0
    if (NS_FAILED(InitCacheClean(cacheDirectory,
68
0
                                 corruptInfo))) {
69
0
        // corruptInfo is set in the call to InitCacheClean
70
0
        goto error_exit;
71
0
    }
72
0
73
0
    // check size of map file
74
0
    if (mapSize == 0) {  // creating a new _CACHE_MAP_
75
0
76
0
        // block files shouldn't exist if we're creating the _CACHE_MAP_
77
0
        if (cacheFilesExist) {
78
0
            *corruptInfo = nsDiskCache::kBlockFilesShouldNotExist;
79
0
            goto error_exit;
80
0
        }
81
0
82
0
        if (NS_FAILED(CreateCacheSubDirectories())) {
83
0
            *corruptInfo = nsDiskCache::kCreateCacheSubdirectories;
84
0
            goto error_exit;
85
0
        }
86
0
87
0
        // create the file - initialize in memory
88
0
        memset(&mHeader, 0, sizeof(nsDiskCacheHeader));
89
0
        mHeader.mVersion = nsDiskCache::kCurrentVersion;
90
0
        mHeader.mRecordCount = kMinRecordCount;
91
0
        mRecordArray = (nsDiskCacheRecord*)
92
0
            calloc(mHeader.mRecordCount, sizeof(nsDiskCacheRecord));
93
0
        if (!mRecordArray) {
94
0
            *corruptInfo = nsDiskCache::kOutOfMemory;
95
0
            rv = NS_ERROR_OUT_OF_MEMORY;
96
0
            goto error_exit;
97
0
        }
98
0
    } else if (mapSize >= sizeof(nsDiskCacheHeader)) {  // read existing _CACHE_MAP_
99
0
100
0
        // if _CACHE_MAP_ exists, so should the block files
101
0
        if (!cacheFilesExist) {
102
0
            *corruptInfo = nsDiskCache::kBlockFilesShouldExist;
103
0
            goto error_exit;
104
0
        }
105
0
106
0
        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::Open [this=%p] reading map", this));
107
0
108
0
        // read the header
109
0
        uint32_t bytesRead = PR_Read(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
110
0
        if (sizeof(nsDiskCacheHeader) != bytesRead) {
111
0
            *corruptInfo = nsDiskCache::kHeaderSizeNotRead;
112
0
            goto error_exit;
113
0
        }
114
0
        mHeader.Unswap();
115
0
116
0
        if (mHeader.mIsDirty) {
117
0
            *corruptInfo = nsDiskCache::kHeaderIsDirty;
118
0
            goto error_exit;
119
0
        }
120
0
121
0
        if (mHeader.mVersion != nsDiskCache::kCurrentVersion) {
122
0
            *corruptInfo = nsDiskCache::kVersionMismatch;
123
0
            goto error_exit;
124
0
        }
125
0
126
0
        uint32_t recordArraySize =
127
0
                mHeader.mRecordCount * sizeof(nsDiskCacheRecord);
128
0
        if (mapSize < recordArraySize + sizeof(nsDiskCacheHeader)) {
129
0
            *corruptInfo = nsDiskCache::kRecordsIncomplete;
130
0
            goto error_exit;
131
0
        }
132
0
133
0
        // Get the space for the records
134
0
        mRecordArray = (nsDiskCacheRecord*) malloc(recordArraySize);
135
0
        if (!mRecordArray) {
136
0
            *corruptInfo = nsDiskCache::kOutOfMemory;
137
0
            rv = NS_ERROR_OUT_OF_MEMORY;
138
0
            goto error_exit;
139
0
        }
140
0
141
0
        // Read the records
142
0
        bytesRead = PR_Read(mMapFD, mRecordArray, recordArraySize);
143
0
        if (bytesRead < recordArraySize) {
144
0
            *corruptInfo = nsDiskCache::kNotEnoughToRead;
145
0
            goto error_exit;
146
0
        }
147
0
148
0
        // Unswap each record
149
0
        int32_t total = 0;
150
0
        for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
151
0
            if (mRecordArray[i].HashNumber()) {
152
0
#if defined(IS_LITTLE_ENDIAN)
153
0
                mRecordArray[i].Unswap();
154
0
#endif
155
0
                total ++;
156
0
            }
157
0
        }
158
0
159
0
        // verify entry count
160
0
        if (total != mHeader.mEntryCount) {
161
0
            *corruptInfo = nsDiskCache::kEntryCountIncorrect;
162
0
            goto error_exit;
163
0
        }
164
0
165
0
    } else {
166
0
        *corruptInfo = nsDiskCache::kHeaderIncomplete;
167
0
        goto error_exit;
168
0
    }
169
0
170
0
    rv = OpenBlockFiles(corruptInfo);
171
0
    if (NS_FAILED(rv)) {
172
0
        // corruptInfo is set in the call to OpenBlockFiles
173
0
        goto error_exit;
174
0
    }
175
0
176
0
    // set dirty bit and flush header
177
0
    mHeader.mIsDirty    = true;
178
0
    rv = FlushHeader();
179
0
    if (NS_FAILED(rv)) {
180
0
        *corruptInfo = nsDiskCache::kFlushHeaderError;
181
0
        goto error_exit;
182
0
    }
183
0
184
0
    Telemetry::Accumulate(Telemetry::HTTP_DISK_CACHE_OVERHEAD,
185
0
                          (uint32_t)SizeOfExcludingThis(moz_malloc_size_of));
186
0
187
0
    *corruptInfo = nsDiskCache::kNotCorrupt;
188
0
    return NS_OK;
189
0
190
0
error_exit:
191
0
    (void) Close(false);
192
0
193
0
    return rv;
194
0
}
195
196
197
nsresult
198
nsDiskCacheMap::Close(bool flush)
199
0
{
200
0
    nsCacheService::AssertOwnsLock();
201
0
    nsresult  rv = NS_OK;
202
0
203
0
    // Cancel any pending cache validation event, the FlushRecords call below
204
0
    // will validate the cache.
205
0
    if (mCleanCacheTimer) {
206
0
        mCleanCacheTimer->Cancel();
207
0
    }
208
0
209
0
    // If cache map file and its block files are still open, close them
210
0
    if (mMapFD) {
211
0
        // close block files
212
0
        rv = CloseBlockFiles(flush);
213
0
        if (NS_SUCCEEDED(rv) && flush && mRecordArray) {
214
0
            // write the map records
215
0
            rv = FlushRecords(false);   // don't bother swapping buckets back
216
0
            if (NS_SUCCEEDED(rv)) {
217
0
                // clear dirty bit
218
0
                mHeader.mIsDirty = false;
219
0
                rv = FlushHeader();
220
0
            }
221
0
        }
222
0
        if ((PR_Close(mMapFD) != PR_SUCCESS) && (NS_SUCCEEDED(rv)))
223
0
            rv = NS_ERROR_UNEXPECTED;
224
0
225
0
        mMapFD = nullptr;
226
0
    }
227
0
228
0
    if (mCleanFD) {
229
0
        PR_Close(mCleanFD);
230
0
        mCleanFD = nullptr;
231
0
    }
232
0
233
0
    free(mRecordArray);
234
0
    mRecordArray = nullptr;
235
0
    free(mBuffer);
236
0
    mBuffer = nullptr;
237
0
    mBufferSize = 0;
238
0
    return rv;
239
0
}
240
241
242
nsresult
243
nsDiskCacheMap::Trim()
244
0
{
245
0
    nsresult rv, rv2 = NS_OK;
246
0
    for (auto& block : mBlockFile) {
247
0
        rv = block.Trim();
248
0
        if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
249
0
    }
250
0
    // Try to shrink the records array
251
0
    rv = ShrinkRecords();
252
0
    if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
253
0
    return rv2;
254
0
}
255
256
257
nsresult
258
nsDiskCacheMap::FlushHeader()
259
0
{
260
0
    if (!mMapFD)  return NS_ERROR_NOT_AVAILABLE;
261
0
262
0
    // seek to beginning of cache map
263
0
    int32_t filePos = PR_Seek(mMapFD, 0, PR_SEEK_SET);
264
0
    if (filePos != 0)  return NS_ERROR_UNEXPECTED;
265
0
266
0
    // write the header
267
0
    mHeader.Swap();
268
0
    int32_t bytesWritten = PR_Write(mMapFD, &mHeader, sizeof(nsDiskCacheHeader));
269
0
    mHeader.Unswap();
270
0
    if (sizeof(nsDiskCacheHeader) != bytesWritten) {
271
0
        return NS_ERROR_UNEXPECTED;
272
0
    }
273
0
274
0
    PRStatus err = PR_Sync(mMapFD);
275
0
    if (err != PR_SUCCESS) return NS_ERROR_UNEXPECTED;
276
0
277
0
    // If we have a clean header then revalidate the cache clean file
278
0
    if (!mHeader.mIsDirty) {
279
0
        RevalidateCache();
280
0
    }
281
0
282
0
    return NS_OK;
283
0
}
284
285
286
nsresult
287
nsDiskCacheMap::FlushRecords(bool unswap)
288
0
{
289
0
    if (!mMapFD)  return NS_ERROR_NOT_AVAILABLE;
290
0
291
0
    // seek to beginning of buckets
292
0
    int32_t filePos = PR_Seek(mMapFD, sizeof(nsDiskCacheHeader), PR_SEEK_SET);
293
0
    if (filePos != sizeof(nsDiskCacheHeader))
294
0
        return NS_ERROR_UNEXPECTED;
295
0
296
0
#if defined(IS_LITTLE_ENDIAN)
297
0
    // Swap each record
298
0
    for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
299
0
        if (mRecordArray[i].HashNumber())
300
0
            mRecordArray[i].Swap();
301
0
    }
302
0
#endif
303
0
304
0
    int32_t recordArraySize = sizeof(nsDiskCacheRecord) * mHeader.mRecordCount;
305
0
306
0
    int32_t bytesWritten = PR_Write(mMapFD, mRecordArray, recordArraySize);
307
0
    if (bytesWritten != recordArraySize)
308
0
        return NS_ERROR_UNEXPECTED;
309
0
310
0
#if defined(IS_LITTLE_ENDIAN)
311
0
    if (unswap) {
312
0
        // Unswap each record
313
0
        for (int32_t i = 0; i < mHeader.mRecordCount; ++i) {
314
0
            if (mRecordArray[i].HashNumber())
315
0
                mRecordArray[i].Unswap();
316
0
        }
317
0
    }
318
0
#endif
319
0
320
0
    return NS_OK;
321
0
}
322
323
324
/**
325
 *  Record operations
326
 */
327
328
uint32_t
329
nsDiskCacheMap::GetBucketRank(uint32_t bucketIndex, uint32_t targetRank)
330
0
{
331
0
    nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
332
0
    uint32_t            rank = 0;
333
0
334
0
    for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
335
0
        if ((rank < records[i].EvictionRank()) &&
336
0
            ((targetRank == 0) || (records[i].EvictionRank() < targetRank)))
337
0
                rank = records[i].EvictionRank();
338
0
    }
339
0
    return rank;
340
0
}
341
342
nsresult
343
nsDiskCacheMap::GrowRecords()
344
0
{
345
0
    if (mHeader.mRecordCount >= mMaxRecordCount)
346
0
        return NS_OK;
347
0
    CACHE_LOG_DEBUG(("CACHE: GrowRecords\n"));
348
0
349
0
    // Resize the record array
350
0
    int32_t newCount = mHeader.mRecordCount << 1;
351
0
    if (newCount > mMaxRecordCount)
352
0
        newCount = mMaxRecordCount;
353
0
    nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
354
0
            realloc(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
355
0
    if (!newArray)
356
0
        return NS_ERROR_OUT_OF_MEMORY;
357
0
358
0
    // Space out the buckets
359
0
    uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
360
0
    uint32_t newRecordsPerBucket = newCount / kBuckets;
361
0
    // Work from back to space out each bucket to the new array
362
0
    for (int bucketIndex = kBuckets - 1; bucketIndex >= 0; --bucketIndex) {
363
0
        // Move bucket
364
0
        nsDiskCacheRecord *newRecords = newArray + bucketIndex * newRecordsPerBucket;
365
0
        const uint32_t count = mHeader.mBucketUsage[bucketIndex];
366
0
        memmove(newRecords,
367
0
                newArray + bucketIndex * oldRecordsPerBucket,
368
0
                count * sizeof(nsDiskCacheRecord));
369
0
        // clear unused records
370
0
        memset(newRecords + count, 0,
371
0
               (newRecordsPerBucket - count) * sizeof(nsDiskCacheRecord));
372
0
    }
373
0
374
0
    // Set as the new record array
375
0
    mRecordArray = newArray;
376
0
    mHeader.mRecordCount = newCount;
377
0
378
0
    InvalidateCache();
379
0
380
0
    return NS_OK;
381
0
}
382
383
nsresult
384
nsDiskCacheMap::ShrinkRecords()
385
0
{
386
0
    if (mHeader.mRecordCount <= kMinRecordCount)
387
0
        return NS_OK;
388
0
    CACHE_LOG_DEBUG(("CACHE: ShrinkRecords\n"));
389
0
390
0
    // Verify if we can shrink the record array: all buckets must be less than
391
0
    // 1/2 filled
392
0
    uint32_t maxUsage = 0, bucketIndex;
393
0
    for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
394
0
        if (maxUsage < mHeader.mBucketUsage[bucketIndex])
395
0
            maxUsage = mHeader.mBucketUsage[bucketIndex];
396
0
    }
397
0
    // Determine new bucket size, halve size until maxUsage
398
0
    uint32_t oldRecordsPerBucket = GetRecordsPerBucket();
399
0
    uint32_t newRecordsPerBucket = oldRecordsPerBucket;
400
0
    while (maxUsage < (newRecordsPerBucket >> 1))
401
0
        newRecordsPerBucket >>= 1;
402
0
    if (newRecordsPerBucket < (kMinRecordCount / kBuckets))
403
0
        newRecordsPerBucket = (kMinRecordCount / kBuckets);
404
0
    NS_ASSERTION(newRecordsPerBucket <= oldRecordsPerBucket,
405
0
                 "ShrinkRecords() can't grow records!");
406
0
    if (newRecordsPerBucket == oldRecordsPerBucket)
407
0
        return NS_OK;
408
0
    // Move the buckets close to each other
409
0
    for (bucketIndex = 1; bucketIndex < kBuckets; ++bucketIndex) {
410
0
        // Move bucket
411
0
        memmove(mRecordArray + bucketIndex * newRecordsPerBucket,
412
0
                mRecordArray + bucketIndex * oldRecordsPerBucket,
413
0
                newRecordsPerBucket * sizeof(nsDiskCacheRecord));
414
0
    }
415
0
416
0
    // Shrink the record array memory block itself
417
0
    uint32_t newCount = newRecordsPerBucket * kBuckets;
418
0
    nsDiskCacheRecord* newArray = (nsDiskCacheRecord *)
419
0
            realloc(mRecordArray, newCount * sizeof(nsDiskCacheRecord));
420
0
    if (!newArray)
421
0
        return NS_ERROR_OUT_OF_MEMORY;
422
0
423
0
    // Set as the new record array
424
0
    mRecordArray = newArray;
425
0
    mHeader.mRecordCount = newCount;
426
0
427
0
    InvalidateCache();
428
0
429
0
    return NS_OK;
430
0
}
431
432
nsresult
433
nsDiskCacheMap::AddRecord( nsDiskCacheRecord *  mapRecord,
434
                           nsDiskCacheRecord *  oldRecord)
435
0
{
436
0
    CACHE_LOG_DEBUG(("CACHE: AddRecord [%x]\n", mapRecord->HashNumber()));
437
0
438
0
    const uint32_t      hashNumber = mapRecord->HashNumber();
439
0
    const uint32_t      bucketIndex = GetBucketIndex(hashNumber);
440
0
    const uint32_t      count = mHeader.mBucketUsage[bucketIndex];
441
0
442
0
    oldRecord->SetHashNumber(0);  // signify no record
443
0
444
0
    if (count == GetRecordsPerBucket()) {
445
0
        // Ignore failure to grow the record space, we will then reuse old records
446
0
        GrowRecords();
447
0
    }
448
0
449
0
    nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
450
0
    if (count < GetRecordsPerBucket()) {
451
0
        // stick the new record at the end
452
0
        records[count] = *mapRecord;
453
0
        mHeader.mEntryCount++;
454
0
        mHeader.mBucketUsage[bucketIndex]++;
455
0
        if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
456
0
            mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
457
0
        InvalidateCache();
458
0
    } else {
459
0
        // Find the record with the highest eviction rank
460
0
        nsDiskCacheRecord * mostEvictable = &records[0];
461
0
        for (int i = count-1; i > 0; i--) {
462
0
            if (records[i].EvictionRank() > mostEvictable->EvictionRank())
463
0
                mostEvictable = &records[i];
464
0
        }
465
0
        *oldRecord     = *mostEvictable;    // i == GetRecordsPerBucket(), so
466
0
                                            // evict the mostEvictable
467
0
        *mostEvictable = *mapRecord;        // replace it with the new record
468
0
        // check if we need to update mostEvictable entry in header
469
0
        if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
470
0
            mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
471
0
        if (oldRecord->EvictionRank() >= mHeader.mEvictionRank[bucketIndex])
472
0
            mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
473
0
        InvalidateCache();
474
0
    }
475
0
476
0
    NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
477
0
                 "eviction rank out of sync");
478
0
    return NS_OK;
479
0
}
480
481
482
nsresult
483
nsDiskCacheMap::UpdateRecord( nsDiskCacheRecord *  mapRecord)
484
0
{
485
0
    CACHE_LOG_DEBUG(("CACHE: UpdateRecord [%x]\n", mapRecord->HashNumber()));
486
0
487
0
    const uint32_t      hashNumber = mapRecord->HashNumber();
488
0
    const uint32_t      bucketIndex = GetBucketIndex(hashNumber);
489
0
    nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
490
0
491
0
    for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
492
0
        if (records[i].HashNumber() == hashNumber) {
493
0
            const uint32_t oldRank = records[i].EvictionRank();
494
0
495
0
            // stick the new record here
496
0
            records[i] = *mapRecord;
497
0
498
0
            // update eviction rank in header if necessary
499
0
            if (mHeader.mEvictionRank[bucketIndex] < mapRecord->EvictionRank())
500
0
                mHeader.mEvictionRank[bucketIndex] = mapRecord->EvictionRank();
501
0
            else if (mHeader.mEvictionRank[bucketIndex] == oldRank)
502
0
                mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
503
0
504
0
            InvalidateCache();
505
0
506
0
NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] == GetBucketRank(bucketIndex, 0),
507
0
             "eviction rank out of sync");
508
0
            return NS_OK;
509
0
        }
510
0
    }
511
0
    MOZ_ASSERT_UNREACHABLE("record not found");
512
0
    return NS_ERROR_UNEXPECTED;
513
0
}
514
515
516
nsresult
517
nsDiskCacheMap::FindRecord( uint32_t  hashNumber, nsDiskCacheRecord *  result)
518
0
{
519
0
    const uint32_t      bucketIndex = GetBucketIndex(hashNumber);
520
0
    nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
521
0
522
0
    for (int i = mHeader.mBucketUsage[bucketIndex]-1; i >= 0; i--) {
523
0
        if (records[i].HashNumber() == hashNumber) {
524
0
            *result = records[i];    // copy the record
525
0
            NS_ASSERTION(result->ValidRecord(), "bad cache map record");
526
0
            return NS_OK;
527
0
        }
528
0
    }
529
0
    return NS_ERROR_CACHE_KEY_NOT_FOUND;
530
0
}
531
532
533
nsresult
534
nsDiskCacheMap::DeleteRecord( nsDiskCacheRecord *  mapRecord)
535
0
{
536
0
    CACHE_LOG_DEBUG(("CACHE: DeleteRecord [%x]\n", mapRecord->HashNumber()));
537
0
538
0
    const uint32_t      hashNumber = mapRecord->HashNumber();
539
0
    const uint32_t      bucketIndex = GetBucketIndex(hashNumber);
540
0
    nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
541
0
    uint32_t            last = mHeader.mBucketUsage[bucketIndex]-1;
542
0
543
0
    for (int i = last; i >= 0; i--) {
544
0
        if (records[i].HashNumber() == hashNumber) {
545
0
            // found it, now delete it.
546
0
            uint32_t  evictionRank = records[i].EvictionRank();
547
0
            NS_ASSERTION(evictionRank == mapRecord->EvictionRank(),
548
0
                         "evictionRank out of sync");
549
0
            // if not the last record, shift last record into opening
550
0
            records[i] = records[last];
551
0
            records[last].SetHashNumber(0); // clear last record
552
0
            mHeader.mBucketUsage[bucketIndex] = last;
553
0
            mHeader.mEntryCount--;
554
0
555
0
            // update eviction rank
556
0
            uint32_t  bucketIndex = GetBucketIndex(mapRecord->HashNumber());
557
0
            if (mHeader.mEvictionRank[bucketIndex] <= evictionRank) {
558
0
                mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
559
0
            }
560
0
561
0
            InvalidateCache();
562
0
563
0
            NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
564
0
                         GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
565
0
            return NS_OK;
566
0
        }
567
0
    }
568
0
    return NS_ERROR_UNEXPECTED;
569
0
}
570
571
572
int32_t
573
nsDiskCacheMap::VisitEachRecord(uint32_t                    bucketIndex,
574
                                nsDiskCacheRecordVisitor *  visitor,
575
                                uint32_t                    evictionRank)
576
0
{
577
0
    int32_t             rv = kVisitNextRecord;
578
0
    uint32_t            count = mHeader.mBucketUsage[bucketIndex];
579
0
    nsDiskCacheRecord * records = GetFirstRecordInBucket(bucketIndex);
580
0
581
0
    // call visitor for each entry (matching any eviction rank)
582
0
    for (int i = count-1; i >= 0; i--) {
583
0
        if (evictionRank > records[i].EvictionRank()) continue;
584
0
585
0
        rv = visitor->VisitRecord(&records[i]);
586
0
        if (rv == kStopVisitingRecords)
587
0
            break;    // Stop visiting records
588
0
589
0
        if (rv == kDeleteRecordAndContinue) {
590
0
            --count;
591
0
            records[i] = records[count];
592
0
            records[count].SetHashNumber(0);
593
0
            InvalidateCache();
594
0
        }
595
0
    }
596
0
597
0
    if (mHeader.mBucketUsage[bucketIndex] - count != 0) {
598
0
        mHeader.mEntryCount -= mHeader.mBucketUsage[bucketIndex] - count;
599
0
        mHeader.mBucketUsage[bucketIndex] = count;
600
0
        // recalc eviction rank
601
0
        mHeader.mEvictionRank[bucketIndex] = GetBucketRank(bucketIndex, 0);
602
0
    }
603
0
    NS_ASSERTION(mHeader.mEvictionRank[bucketIndex] ==
604
0
                 GetBucketRank(bucketIndex, 0), "eviction rank out of sync");
605
0
606
0
    return rv;
607
0
}
608
609
610
/**
611
 *  VisitRecords
612
 *
613
 *  Visit every record in cache map in the most convenient order
614
 */
615
nsresult
616
nsDiskCacheMap::VisitRecords( nsDiskCacheRecordVisitor *  visitor)
617
0
{
618
0
    for (int bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex) {
619
0
        if (VisitEachRecord(bucketIndex, visitor, 0) == kStopVisitingRecords)
620
0
            break;
621
0
    }
622
0
    return NS_OK;
623
0
}
624
625
626
/**
627
 *  EvictRecords
628
 *
629
 *  Just like VisitRecords, but visits the records in order of their eviction rank
630
 */
631
nsresult
632
nsDiskCacheMap::EvictRecords( nsDiskCacheRecordVisitor * visitor)
633
0
{
634
0
    uint32_t  tempRank[kBuckets];
635
0
    int       bucketIndex = 0;
636
0
637
0
    // copy eviction rank array
638
0
    for (bucketIndex = 0; bucketIndex < kBuckets; ++bucketIndex)
639
0
        tempRank[bucketIndex] = mHeader.mEvictionRank[bucketIndex];
640
0
641
0
    // Maximum number of iterations determined by number of records
642
0
    // as a safety limiter for the loop. Use a copy of mHeader.mEntryCount since
643
0
    // the value could decrease if some entry is evicted.
644
0
    int32_t entryCount = mHeader.mEntryCount;
645
0
    for (int n = 0; n < entryCount; ++n) {
646
0
647
0
        // find bucket with highest eviction rank
648
0
        uint32_t    rank  = 0;
649
0
        for (int i = 0; i < kBuckets; ++i) {
650
0
            if (rank < tempRank[i]) {
651
0
                rank = tempRank[i];
652
0
                bucketIndex = i;
653
0
            }
654
0
        }
655
0
656
0
        if (rank == 0) break;  // we've examined all the records
657
0
658
0
        // visit records in bucket with eviction ranks >= target eviction rank
659
0
        if (VisitEachRecord(bucketIndex, visitor, rank) == kStopVisitingRecords)
660
0
            break;
661
0
662
0
        // find greatest rank less than 'rank'
663
0
        tempRank[bucketIndex] = GetBucketRank(bucketIndex, rank);
664
0
    }
665
0
    return NS_OK;
666
0
}
667
668
669
670
nsresult
671
nsDiskCacheMap::OpenBlockFiles(nsDiskCache::CorruptCacheInfo *  corruptInfo)
672
0
{
673
0
    NS_ENSURE_ARG_POINTER(corruptInfo);
674
0
675
0
    // create nsIFile for block file
676
0
    nsCOMPtr<nsIFile> blockFile;
677
0
    nsresult rv = NS_OK;
678
0
    *corruptInfo = nsDiskCache::kUnexpectedError;
679
0
680
0
    for (int i = 0; i < kNumBlockFiles; ++i) {
681
0
        rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
682
0
        if (NS_FAILED(rv)) {
683
0
            *corruptInfo = nsDiskCache::kCouldNotGetBlockFileForIndex;
684
0
            break;
685
0
        }
686
0
687
0
        uint32_t blockSize = GetBlockSizeForIndex(i+1); // +1 to match file selectors 1,2,3
688
0
        uint32_t bitMapSize = GetBitMapSizeForIndex(i+1);
689
0
        rv = mBlockFile[i].Open(blockFile, blockSize, bitMapSize, corruptInfo);
690
0
        if (NS_FAILED(rv)) {
691
0
            // corruptInfo was set inside the call to mBlockFile[i].Open
692
0
            break;
693
0
        }
694
0
    }
695
0
    // close all files in case of any error
696
0
    if (NS_FAILED(rv))
697
0
        (void)CloseBlockFiles(false); // we already have an error to report
698
0
699
0
    return rv;
700
0
}
701
702
703
nsresult
704
nsDiskCacheMap::CloseBlockFiles(bool flush)
705
0
{
706
0
    nsresult rv, rv2 = NS_OK;
707
0
    for (auto& block : mBlockFile) {
708
0
        rv = block.Close(flush);
709
0
        if (NS_FAILED(rv))  rv2 = rv;   // if one or more errors, report at least one
710
0
    }
711
0
    return rv2;
712
0
}
713
714
715
bool
716
nsDiskCacheMap::CacheFilesExist()
717
0
{
718
0
    nsCOMPtr<nsIFile> blockFile;
719
0
    nsresult rv;
720
0
721
0
    for (int i = 0; i < kNumBlockFiles; ++i) {
722
0
        bool exists;
723
0
        rv = GetBlockFileForIndex(i, getter_AddRefs(blockFile));
724
0
        if (NS_FAILED(rv))  return false;
725
0
726
0
        rv = blockFile->Exists(&exists);
727
0
        if (NS_FAILED(rv) || !exists)  return false;
728
0
    }
729
0
730
0
    return true;
731
0
}
732
733
734
nsresult
735
nsDiskCacheMap::CreateCacheSubDirectories()
736
0
{
737
0
    if (!mCacheDirectory)
738
0
        return NS_ERROR_UNEXPECTED;
739
0
740
0
    for (int32_t index = 0 ; index < 16 ; index++) {
741
0
        nsCOMPtr<nsIFile> file;
742
0
        nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
743
0
        if (NS_FAILED(rv))
744
0
            return rv;
745
0
746
0
        rv = file->AppendNative(nsPrintfCString("%X", index));
747
0
        if (NS_FAILED(rv))
748
0
            return rv;
749
0
750
0
        rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
751
0
        if (NS_FAILED(rv))
752
0
            return rv;
753
0
    }
754
0
755
0
    return NS_OK;
756
0
}
757
758
759
nsDiskCacheEntry *
760
nsDiskCacheMap::ReadDiskCacheEntry(nsDiskCacheRecord * record)
761
0
{
762
0
    CACHE_LOG_DEBUG(("CACHE: ReadDiskCacheEntry [%x]\n", record->HashNumber()));
763
0
764
0
    nsresult            rv         = NS_ERROR_UNEXPECTED;
765
0
    nsDiskCacheEntry *  diskEntry  = nullptr;
766
0
    uint32_t            metaFile   = record->MetaFile();
767
0
    int32_t             bytesRead  = 0;
768
0
769
0
    if (!record->MetaLocationInitialized())  return nullptr;
770
0
771
0
    if (metaFile == 0) {  // entry/metadata stored in separate file
772
0
        // open and read the file
773
0
        nsCOMPtr<nsIFile> file;
774
0
        rv = GetLocalFileForDiskCacheRecord(record,
775
0
                                            nsDiskCache::kMetaData,
776
0
                                            false,
777
0
                                            getter_AddRefs(file));
778
0
        NS_ENSURE_SUCCESS(rv, nullptr);
779
0
780
0
        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheMap::ReadDiskCacheEntry"
781
0
                         "[this=%p] reading disk cache entry", this));
782
0
783
0
        PRFileDesc * fd = nullptr;
784
0
785
0
        // open the file - restricted to user, the data could be confidential
786
0
        rv = file->OpenNSPRFileDesc(PR_RDONLY, 00600, &fd);
787
0
        NS_ENSURE_SUCCESS(rv, nullptr);
788
0
789
0
        int32_t fileSize = PR_Available(fd);
790
0
        if (fileSize < 0) {
791
0
            // an error occurred. We could call PR_GetError(), but how would that help?
792
0
            rv = NS_ERROR_UNEXPECTED;
793
0
        } else {
794
0
            rv = EnsureBuffer(fileSize);
795
0
            if (NS_SUCCEEDED(rv)) {
796
0
                bytesRead = PR_Read(fd, mBuffer, fileSize);
797
0
                if (bytesRead < fileSize) {
798
0
                    rv = NS_ERROR_UNEXPECTED;
799
0
                }
800
0
            }
801
0
        }
802
0
        PR_Close(fd);
803
0
        NS_ENSURE_SUCCESS(rv, nullptr);
804
0
805
0
    } else if (metaFile < (kNumBlockFiles + 1)) {
806
0
        // entry/metadata stored in cache block file
807
0
808
0
        // allocate buffer
809
0
        uint32_t blockCount = record->MetaBlockCount();
810
0
        bytesRead = blockCount * GetBlockSizeForIndex(metaFile);
811
0
812
0
        rv = EnsureBuffer(bytesRead);
813
0
        NS_ENSURE_SUCCESS(rv, nullptr);
814
0
815
0
        // read diskEntry, note when the blocks are at the end of file,
816
0
        // bytesRead may be less than blockSize*blockCount.
817
0
        // But the bytesRead should at least agree with the real disk entry size.
818
0
        rv = mBlockFile[metaFile - 1].ReadBlocks(mBuffer,
819
0
                                                 record->MetaStartBlock(),
820
0
                                                 blockCount,
821
0
                                                 &bytesRead);
822
0
        NS_ENSURE_SUCCESS(rv, nullptr);
823
0
    }
824
0
    diskEntry = (nsDiskCacheEntry *)mBuffer;
825
0
    diskEntry->Unswap();    // disk to memory
826
0
    // Check if calculated size agrees with bytesRead
827
0
    if (bytesRead < 0 || (uint32_t)bytesRead < diskEntry->Size())
828
0
        return nullptr;
829
0
830
0
    // Return the buffer containing the diskEntry structure
831
0
    return diskEntry;
832
0
}
833
834
835
/**
836
 *  CreateDiskCacheEntry(nsCacheEntry * entry)
837
 *
838
 *  Prepare an nsCacheEntry for writing to disk
839
 */
840
nsDiskCacheEntry *
841
nsDiskCacheMap::CreateDiskCacheEntry(nsDiskCacheBinding *  binding,
842
                                     uint32_t * aSize)
843
0
{
844
0
    nsCacheEntry * entry = binding->mCacheEntry;
845
0
    if (!entry)  return nullptr;
846
0
847
0
    // Store security info, if it is serializable
848
0
    nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
849
0
    nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
850
0
    if (infoObj && !serializable) return nullptr;
851
0
    if (serializable) {
852
0
        nsCString info;
853
0
        nsresult rv = NS_SerializeToString(serializable, info);
854
0
        if (NS_FAILED(rv)) return nullptr;
855
0
        rv = entry->SetMetaDataElement("security-info", info.get());
856
0
        if (NS_FAILED(rv)) return nullptr;
857
0
    }
858
0
859
0
    uint32_t  keySize  = entry->Key()->Length() + 1;
860
0
    uint32_t  metaSize = entry->MetaDataSize();
861
0
    uint32_t  size     = sizeof(nsDiskCacheEntry) + keySize + metaSize;
862
0
863
0
    if (aSize) *aSize = size;
864
0
865
0
    nsresult rv = EnsureBuffer(size);
866
0
    if (NS_FAILED(rv)) return nullptr;
867
0
868
0
    nsDiskCacheEntry *diskEntry = (nsDiskCacheEntry *)mBuffer;
869
0
    diskEntry->mHeaderVersion   = nsDiskCache::kCurrentVersion;
870
0
    diskEntry->mMetaLocation    = binding->mRecord.MetaLocation();
871
0
    diskEntry->mFetchCount      = entry->FetchCount();
872
0
    diskEntry->mLastFetched     = entry->LastFetched();
873
0
    diskEntry->mLastModified    = entry->LastModified();
874
0
    diskEntry->mExpirationTime  = entry->ExpirationTime();
875
0
    diskEntry->mDataSize        = entry->DataSize();
876
0
    diskEntry->mKeySize         = keySize;
877
0
    diskEntry->mMetaDataSize    = metaSize;
878
0
879
0
    memcpy(diskEntry->Key(), entry->Key()->get(), keySize);
880
0
881
0
    rv = entry->FlattenMetaData(diskEntry->MetaData(), metaSize);
882
0
    if (NS_FAILED(rv)) return nullptr;
883
0
884
0
    return diskEntry;
885
0
}
886
887
888
nsresult
889
nsDiskCacheMap::WriteDiskCacheEntry(nsDiskCacheBinding *  binding)
890
0
{
891
0
    CACHE_LOG_DEBUG(("CACHE: WriteDiskCacheEntry [%x]\n",
892
0
        binding->mRecord.HashNumber()));
893
0
894
0
    nsresult            rv        = NS_OK;
895
0
    uint32_t            size;
896
0
    nsDiskCacheEntry *  diskEntry =  CreateDiskCacheEntry(binding, &size);
897
0
    if (!diskEntry)  return NS_ERROR_UNEXPECTED;
898
0
899
0
    uint32_t  fileIndex = CalculateFileIndex(size);
900
0
901
0
    // Deallocate old storage if necessary
902
0
    if (binding->mRecord.MetaLocationInitialized()) {
903
0
        // we have existing storage
904
0
905
0
        if ((binding->mRecord.MetaFile() == 0) &&
906
0
            (fileIndex == 0)) {  // keeping the separate file
907
0
            // just decrement total
908
0
            DecrementTotalSize(binding->mRecord.MetaFileSize());
909
0
            NS_ASSERTION(binding->mRecord.MetaFileGeneration() == binding->mGeneration,
910
0
                         "generations out of sync");
911
0
        } else {
912
0
            rv = DeleteStorage(&binding->mRecord, nsDiskCache::kMetaData);
913
0
            NS_ENSURE_SUCCESS(rv, rv);
914
0
        }
915
0
    }
916
0
917
0
    binding->mRecord.SetEvictionRank(ULONG_MAX - SecondsFromPRTime(PR_Now()));
918
0
    // write entry data to disk cache block file
919
0
    diskEntry->Swap();
920
0
921
0
    if (fileIndex != 0) {
922
0
        while (true) {
923
0
            uint32_t  blockSize = GetBlockSizeForIndex(fileIndex);
924
0
            uint32_t  blocks    = ((size - 1) / blockSize) + 1;
925
0
926
0
            int32_t startBlock;
927
0
            rv = mBlockFile[fileIndex - 1].WriteBlocks(diskEntry, size, blocks,
928
0
                                                       &startBlock);
929
0
            if (NS_SUCCEEDED(rv)) {
930
0
                // update binding and cache map record
931
0
                binding->mRecord.SetMetaBlocks(fileIndex, startBlock, blocks);
932
0
933
0
                rv = UpdateRecord(&binding->mRecord);
934
0
                NS_ENSURE_SUCCESS(rv, rv);
935
0
936
0
                // XXX we should probably write out bucket ourselves
937
0
938
0
                IncrementTotalSize(blocks, blockSize);
939
0
                break;
940
0
            }
941
0
942
0
            if (fileIndex == kNumBlockFiles) {
943
0
                fileIndex = 0; // write data to separate file
944
0
                break;
945
0
            }
946
0
947
0
            // try next block file
948
0
            fileIndex++;
949
0
        }
950
0
    }
951
0
952
0
    if (fileIndex == 0) {
953
0
        // Write entry data to separate file
954
0
        uint32_t metaFileSizeK = ((size + 0x03FF) >> 10); // round up to nearest 1k
955
0
        if (metaFileSizeK > kMaxDataSizeK)
956
0
            metaFileSizeK = kMaxDataSizeK;
957
0
958
0
        binding->mRecord.SetMetaFileGeneration(binding->mGeneration);
959
0
        binding->mRecord.SetMetaFileSize(metaFileSizeK);
960
0
        rv = UpdateRecord(&binding->mRecord);
961
0
        NS_ENSURE_SUCCESS(rv, rv);
962
0
963
0
        nsCOMPtr<nsIFile> localFile;
964
0
        rv = GetLocalFileForDiskCacheRecord(&binding->mRecord,
965
0
                                            nsDiskCache::kMetaData,
966
0
                                            true,
967
0
                                            getter_AddRefs(localFile));
968
0
        NS_ENSURE_SUCCESS(rv, rv);
969
0
970
0
        // open the file
971
0
        PRFileDesc * fd;
972
0
        // open the file - restricted to user, the data could be confidential
973
0
        rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE, 00600, &fd);
974
0
        NS_ENSURE_SUCCESS(rv, rv);
975
0
976
0
        // write the file
977
0
        int32_t bytesWritten = PR_Write(fd, diskEntry, size);
978
0
979
0
        PRStatus err = PR_Close(fd);
980
0
        if ((bytesWritten != (int32_t)size) || (err != PR_SUCCESS)) {
981
0
            return NS_ERROR_UNEXPECTED;
982
0
        }
983
0
984
0
        IncrementTotalSize(metaFileSizeK);
985
0
    }
986
0
987
0
    return rv;
988
0
}
989
990
991
nsresult
992
nsDiskCacheMap::ReadDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
993
0
{
994
0
    CACHE_LOG_DEBUG(("CACHE: ReadDataCacheBlocks [%x size=%u]\n",
995
0
        binding->mRecord.HashNumber(), size));
996
0
997
0
    uint32_t  fileIndex = binding->mRecord.DataFile();
998
0
    int32_t   readSize = size;
999
0
1000
0
    nsresult rv = mBlockFile[fileIndex - 1].ReadBlocks(buffer,
1001
0
                                                       binding->mRecord.DataStartBlock(),
1002
0
                                                       binding->mRecord.DataBlockCount(),
1003
0
                                                       &readSize);
1004
0
    NS_ENSURE_SUCCESS(rv, rv);
1005
0
    if (readSize < (int32_t)size) {
1006
0
        rv = NS_ERROR_UNEXPECTED;
1007
0
    }
1008
0
    return rv;
1009
0
}
1010
1011
1012
nsresult
1013
nsDiskCacheMap::WriteDataCacheBlocks(nsDiskCacheBinding * binding, char * buffer, uint32_t size)
1014
0
{
1015
0
    CACHE_LOG_DEBUG(("CACHE: WriteDataCacheBlocks [%x size=%u]\n",
1016
0
        binding->mRecord.HashNumber(), size));
1017
0
1018
0
    nsresult  rv = NS_OK;
1019
0
1020
0
    // determine block file & number of blocks
1021
0
    uint32_t  fileIndex  = CalculateFileIndex(size);
1022
0
    uint32_t  blockCount = 0;
1023
0
    int32_t   startBlock = 0;
1024
0
1025
0
    if (size > 0) {
1026
0
        // if fileIndex is 0, bad things happen below, which makes gcc 4.7
1027
0
        // complain, but it's not supposed to happen. See bug 854105.
1028
0
        MOZ_ASSERT(fileIndex);
1029
0
        while (fileIndex) {
1030
0
            uint32_t  blockSize  = GetBlockSizeForIndex(fileIndex);
1031
0
            blockCount = ((size - 1) / blockSize) + 1;
1032
0
1033
0
            rv = mBlockFile[fileIndex - 1].WriteBlocks(buffer, size, blockCount,
1034
0
                                                       &startBlock);
1035
0
            if (NS_SUCCEEDED(rv)) {
1036
0
                IncrementTotalSize(blockCount, blockSize);
1037
0
                break;
1038
0
            }
1039
0
1040
0
            if (fileIndex == kNumBlockFiles)
1041
0
                return rv;
1042
0
1043
0
            fileIndex++;
1044
0
        }
1045
0
    }
1046
0
1047
0
    // update binding and cache map record
1048
0
    binding->mRecord.SetDataBlocks(fileIndex, startBlock, blockCount);
1049
0
    if (!binding->mDoomed) {
1050
0
        rv = UpdateRecord(&binding->mRecord);
1051
0
    }
1052
0
    return rv;
1053
0
}
1054
1055
1056
nsresult
1057
nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record)
1058
0
{
1059
0
    nsresult  rv1 = DeleteStorage(record, nsDiskCache::kData);
1060
0
    nsresult  rv2 = DeleteStorage(record, nsDiskCache::kMetaData);
1061
0
    return NS_FAILED(rv1) ? rv1 : rv2;
1062
0
}
1063
1064
1065
nsresult
1066
nsDiskCacheMap::DeleteStorage(nsDiskCacheRecord * record, bool metaData)
1067
0
{
1068
0
    CACHE_LOG_DEBUG(("CACHE: DeleteStorage [%x %u]\n", record->HashNumber(),
1069
0
        metaData));
1070
0
1071
0
    nsresult    rv = NS_ERROR_UNEXPECTED;
1072
0
    uint32_t    fileIndex = metaData ? record->MetaFile() : record->DataFile();
1073
0
    nsCOMPtr<nsIFile> file;
1074
0
1075
0
    if (fileIndex == 0) {
1076
0
        // delete the file
1077
0
        uint32_t  sizeK = metaData ? record->MetaFileSize() : record->DataFileSize();
1078
0
        // XXX if sizeK == USHRT_MAX, stat file for actual size
1079
0
1080
0
        rv = GetFileForDiskCacheRecord(record, metaData, false, getter_AddRefs(file));
1081
0
        if (NS_SUCCEEDED(rv)) {
1082
0
            rv = file->Remove(false);    // false == non-recursive
1083
0
        }
1084
0
        DecrementTotalSize(sizeK);
1085
0
1086
0
    } else if (fileIndex < (kNumBlockFiles + 1)) {
1087
0
        // deallocate blocks
1088
0
        uint32_t  startBlock = metaData ? record->MetaStartBlock() : record->DataStartBlock();
1089
0
        uint32_t  blockCount = metaData ? record->MetaBlockCount() : record->DataBlockCount();
1090
0
1091
0
        rv = mBlockFile[fileIndex - 1].DeallocateBlocks(startBlock, blockCount);
1092
0
        DecrementTotalSize(blockCount, GetBlockSizeForIndex(fileIndex));
1093
0
    }
1094
0
    if (metaData)  record->ClearMetaLocation();
1095
0
    else           record->ClearDataLocation();
1096
0
1097
0
    return rv;
1098
0
}
1099
1100
1101
nsresult
1102
nsDiskCacheMap::GetFileForDiskCacheRecord(nsDiskCacheRecord * record,
1103
                                          bool                meta,
1104
                                          bool                createPath,
1105
                                          nsIFile **          result)
1106
0
{
1107
0
    if (!mCacheDirectory)  return NS_ERROR_NOT_AVAILABLE;
1108
0
1109
0
    nsCOMPtr<nsIFile> file;
1110
0
    nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1111
0
    if (NS_FAILED(rv))  return rv;
1112
0
1113
0
    uint32_t hash = record->HashNumber();
1114
0
1115
0
    // The file is stored under subdirectories according to the hash number:
1116
0
    // 0x01234567 -> 0/12/
1117
0
    rv = file->AppendNative(nsPrintfCString("%X", hash >> 28));
1118
0
    if (NS_FAILED(rv))  return rv;
1119
0
    rv = file->AppendNative(nsPrintfCString("%02X", (hash >> 20) & 0xFF));
1120
0
    if (NS_FAILED(rv))  return rv;
1121
0
1122
0
    bool exists;
1123
0
    if (createPath && (NS_FAILED(file->Exists(&exists)) || !exists)) {
1124
0
        rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
1125
0
        if (NS_FAILED(rv))  return rv;
1126
0
    }
1127
0
1128
0
    int16_t generation = record->Generation();
1129
0
    char name[32];
1130
0
    // Cut the beginning of the hash that was used in the path
1131
0
    ::SprintfLiteral(name, "%05X%c%02X", hash & 0xFFFFF, (meta ? 'm' : 'd'),
1132
0
                     generation);
1133
0
    rv = file->AppendNative(nsDependentCString(name));
1134
0
    if (NS_FAILED(rv))  return rv;
1135
0
1136
0
    NS_IF_ADDREF(*result = file);
1137
0
    return rv;
1138
0
}
1139
1140
1141
nsresult
1142
nsDiskCacheMap::GetLocalFileForDiskCacheRecord(nsDiskCacheRecord * record,
1143
                                               bool                meta,
1144
                                               bool                createPath,
1145
                                               nsIFile **          result)
1146
0
{
1147
0
    nsCOMPtr<nsIFile> file;
1148
0
    nsresult rv = GetFileForDiskCacheRecord(record,
1149
0
                                            meta,
1150
0
                                            createPath,
1151
0
                                            getter_AddRefs(file));
1152
0
    if (NS_FAILED(rv))  return rv;
1153
0
1154
0
    NS_IF_ADDREF(*result = file);
1155
0
    return rv;
1156
0
}
1157
1158
1159
nsresult
1160
nsDiskCacheMap::GetBlockFileForIndex(uint32_t index, nsIFile ** result)
1161
0
{
1162
0
    if (!mCacheDirectory)  return NS_ERROR_NOT_AVAILABLE;
1163
0
1164
0
    nsCOMPtr<nsIFile> file;
1165
0
    nsresult rv = mCacheDirectory->Clone(getter_AddRefs(file));
1166
0
    if (NS_FAILED(rv))  return rv;
1167
0
1168
0
    char name[32];
1169
0
    ::SprintfLiteral(name, "_CACHE_%03d_", index + 1);
1170
0
    rv = file->AppendNative(nsDependentCString(name));
1171
0
    if (NS_FAILED(rv))  return rv;
1172
0
1173
0
    NS_IF_ADDREF(*result = file);
1174
0
1175
0
    return rv;
1176
0
}
1177
1178
1179
uint32_t
1180
nsDiskCacheMap::CalculateFileIndex(uint32_t size)
1181
0
{
1182
0
    // We prefer to use block file with larger block if the wasted space would
1183
0
    // be the same. E.g. store entry with size of 3073 bytes in 1 4K-block
1184
0
    // instead of in 4 1K-blocks.
1185
0
1186
0
    if (size <= 3 * BLOCK_SIZE_FOR_INDEX(1))  return 1;
1187
0
    if (size <= 3 * BLOCK_SIZE_FOR_INDEX(2))  return 2;
1188
0
    if (size <= 4 * BLOCK_SIZE_FOR_INDEX(3))  return 3;
1189
0
    return 0;
1190
0
}
1191
1192
nsresult
1193
nsDiskCacheMap::EnsureBuffer(uint32_t bufSize)
1194
0
{
1195
0
    if (mBufferSize < bufSize) {
1196
0
        char* buf = (char*) realloc(mBuffer, bufSize);
1197
0
        if (!buf) {
1198
0
            mBufferSize = 0;
1199
0
            return NS_ERROR_OUT_OF_MEMORY;
1200
0
        }
1201
0
        mBuffer = buf;
1202
0
        mBufferSize = bufSize;
1203
0
    }
1204
0
    return NS_OK;
1205
0
}
1206
1207
void
1208
nsDiskCacheMap::NotifyCapacityChange(uint32_t capacity)
1209
0
{
1210
0
  // Heuristic 1. average cache entry size is probably around 1KB
1211
0
  // Heuristic 2. we don't want more than 32MB reserved to store the record
1212
0
  //              map in memory.
1213
0
  const int32_t RECORD_COUNT_LIMIT = 32 * 1024 * 1024 / sizeof(nsDiskCacheRecord);
1214
0
  int32_t maxRecordCount = std::min(int32_t(capacity), RECORD_COUNT_LIMIT);
1215
0
  if (mMaxRecordCount < maxRecordCount) {
1216
0
    // We can only grow
1217
0
    mMaxRecordCount = maxRecordCount;
1218
0
  }
1219
0
}
1220
1221
size_t
1222
nsDiskCacheMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
1223
0
{
1224
0
  size_t usage = aMallocSizeOf(mRecordArray);
1225
0
1226
0
  usage += aMallocSizeOf(mBuffer);
1227
0
  usage += aMallocSizeOf(mMapFD);
1228
0
  usage += aMallocSizeOf(mCleanFD);
1229
0
  usage += aMallocSizeOf(mCacheDirectory);
1230
0
  usage += aMallocSizeOf(mCleanCacheTimer);
1231
0
1232
0
  for (auto& block : mBlockFile) {
1233
0
    usage += block.SizeOfExcludingThis(aMallocSizeOf);
1234
0
  }
1235
0
1236
0
  return usage;
1237
0
}
1238
1239
nsresult
1240
nsDiskCacheMap::InitCacheClean(nsIFile *  cacheDirectory,
1241
                               nsDiskCache::CorruptCacheInfo *  corruptInfo)
1242
0
{
1243
0
    // The _CACHE_CLEAN_ file will be used in the future to determine
1244
0
    // if the cache is clean or not.
1245
0
    bool cacheCleanFileExists = false;
1246
0
    nsCOMPtr<nsIFile> cacheCleanFile;
1247
0
    nsresult rv = cacheDirectory->GetParent(getter_AddRefs(cacheCleanFile));
1248
0
    if (NS_SUCCEEDED(rv)) {
1249
0
        rv = cacheCleanFile->AppendNative(
1250
0
                 NS_LITERAL_CSTRING("_CACHE_CLEAN_"));
1251
0
        if (NS_SUCCEEDED(rv)) {
1252
0
            // Check if the file already exists, if it does, we will later read the
1253
0
            // value and report it to telemetry.
1254
0
            cacheCleanFile->Exists(&cacheCleanFileExists);
1255
0
        }
1256
0
    }
1257
0
    if (NS_FAILED(rv)) {
1258
0
        NS_WARNING("Could not build cache clean file path");
1259
0
        *corruptInfo = nsDiskCache::kCacheCleanFilePathError;
1260
0
        return rv;
1261
0
    }
1262
0
1263
0
    // Make sure the _CACHE_CLEAN_ file exists
1264
0
    rv = cacheCleanFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
1265
0
                                          00600, &mCleanFD);
1266
0
    if (NS_FAILED(rv)) {
1267
0
        NS_WARNING("Could not open cache clean file");
1268
0
        *corruptInfo = nsDiskCache::kCacheCleanOpenFileError;
1269
0
        return rv;
1270
0
    }
1271
0
1272
0
    if (cacheCleanFileExists) {
1273
0
        char clean = '0';
1274
0
        int32_t bytesRead = PR_Read(mCleanFD, &clean, 1);
1275
0
        if (bytesRead != 1) {
1276
0
            NS_WARNING("Could not read _CACHE_CLEAN_ file contents");
1277
0
        }
1278
0
    }
1279
0
1280
0
    // Create a timer that will be used to validate the cache
1281
0
    // as long as an activity threshold was met
1282
0
    mCleanCacheTimer = NS_NewTimer(nsCacheService::GlobalInstance()->mCacheIOThread);
1283
0
    rv = mCleanCacheTimer ? ResetCacheTimer() : NS_ERROR_OUT_OF_MEMORY;
1284
0
1285
0
    if (NS_FAILED(rv)) {
1286
0
        NS_WARNING("Could not create cache clean timer");
1287
0
        mCleanCacheTimer = nullptr;
1288
0
        *corruptInfo = nsDiskCache::kCacheCleanTimerError;
1289
0
        return rv;
1290
0
    }
1291
0
1292
0
    return NS_OK;
1293
0
}
1294
1295
nsresult
1296
nsDiskCacheMap::WriteCacheClean(bool clean)
1297
0
{
1298
0
    nsCacheService::AssertOwnsLock();
1299
0
    if (!mCleanFD) {
1300
0
        NS_WARNING("Cache clean file is not open!");
1301
0
        return NS_ERROR_FAILURE;
1302
0
    }
1303
0
1304
0
    CACHE_LOG_DEBUG(("CACHE: WriteCacheClean: %d\n", clean? 1 : 0));
1305
0
    // I'm using a simple '1' or '0' to denote cache clean
1306
0
    // since it can be edited easily by any text editor for testing.
1307
0
    char data = clean? '1' : '0';
1308
0
    int32_t filePos = PR_Seek(mCleanFD, 0, PR_SEEK_SET);
1309
0
    if (filePos != 0) {
1310
0
        NS_WARNING("Could not seek in cache clean file!");
1311
0
        return NS_ERROR_FAILURE;
1312
0
    }
1313
0
    int32_t bytesWritten = PR_Write(mCleanFD, &data, 1);
1314
0
    if (bytesWritten != 1) {
1315
0
        NS_WARNING("Could not write cache clean file!");
1316
0
        return NS_ERROR_FAILURE;
1317
0
    }
1318
0
    PRStatus err = PR_Sync(mCleanFD);
1319
0
    if (err != PR_SUCCESS) {
1320
0
        NS_WARNING("Could not flush cache clean file!");
1321
0
    }
1322
0
1323
0
    return NS_OK;
1324
0
}
1325
1326
nsresult
1327
nsDiskCacheMap::InvalidateCache()
1328
0
{
1329
0
    nsCacheService::AssertOwnsLock();
1330
0
    CACHE_LOG_DEBUG(("CACHE: InvalidateCache\n"));
1331
0
    nsresult rv;
1332
0
1333
0
    if (!mIsDirtyCacheFlushed) {
1334
0
        rv = WriteCacheClean(false);
1335
0
        if (NS_FAILED(rv)) {
1336
0
          return rv;
1337
0
        }
1338
0
1339
0
        mIsDirtyCacheFlushed = true;
1340
0
    }
1341
0
1342
0
    rv = ResetCacheTimer();
1343
0
    NS_ENSURE_SUCCESS(rv, rv);
1344
0
1345
0
    return NS_OK;
1346
0
}
1347
1348
nsresult
1349
nsDiskCacheMap::ResetCacheTimer(int32_t timeout)
1350
0
{
1351
0
    mCleanCacheTimer->Cancel();
1352
0
    nsresult rv = mCleanCacheTimer->InitWithNamedFuncCallback(
1353
0
      RevalidateTimerCallback,
1354
0
      nullptr,
1355
0
      timeout,
1356
0
      nsITimer::TYPE_ONE_SHOT,
1357
0
      "nsDiskCacheMap::ResetCacheTimer");
1358
0
    NS_ENSURE_SUCCESS(rv, rv);
1359
0
    mLastInvalidateTime = PR_IntervalNow();
1360
0
1361
0
    return rv;
1362
0
}
1363
1364
void
1365
nsDiskCacheMap::RevalidateTimerCallback(nsITimer *aTimer, void *arg)
1366
0
{
1367
0
    nsCacheServiceAutoLock lock;
1368
0
    if (!nsCacheService::gService->mDiskDevice ||
1369
0
        !nsCacheService::gService->mDiskDevice->Initialized()) {
1370
0
        return;
1371
0
    }
1372
0
1373
0
    nsDiskCacheMap *diskCacheMap =
1374
0
        &nsCacheService::gService->mDiskDevice->mCacheMap;
1375
0
1376
0
    // If we have less than kRevalidateCacheTimeout since the last timer was
1377
0
    // issued then another thread called InvalidateCache.  This won't catch
1378
0
    // all cases where we wanted to cancel the timer, but under the lock it
1379
0
    // is always OK to revalidate as long as IsCacheInSafeState() returns
1380
0
    // true.  We just want to avoid revalidating when we can to reduce IO
1381
0
    // and this check will do that.
1382
0
    uint32_t delta =
1383
0
        PR_IntervalToMilliseconds(PR_IntervalNow() -
1384
0
                                  diskCacheMap->mLastInvalidateTime) +
1385
0
        kRevalidateCacheTimeoutTolerance;
1386
0
    if (delta < kRevalidateCacheTimeout) {
1387
0
        diskCacheMap->ResetCacheTimer();
1388
0
        return;
1389
0
    }
1390
0
1391
0
    nsresult rv = diskCacheMap->RevalidateCache();
1392
0
    if (NS_FAILED(rv)) {
1393
0
        diskCacheMap->ResetCacheTimer(kRevalidateCacheErrorTimeout);
1394
0
    }
1395
0
}
1396
1397
bool
1398
nsDiskCacheMap::IsCacheInSafeState()
1399
0
{
1400
0
    return nsCacheService::GlobalInstance()->IsDoomListEmpty();
1401
0
}
1402
1403
nsresult
1404
nsDiskCacheMap::RevalidateCache()
1405
0
{
1406
0
    CACHE_LOG_DEBUG(("CACHE: RevalidateCache\n"));
1407
0
    nsresult rv;
1408
0
1409
0
    if (!IsCacheInSafeState()) {
1410
0
        CACHE_LOG_DEBUG(("CACHE: Revalidation should not performed because "
1411
0
                         "cache not in a safe state\n"));
1412
0
        // Normally we would return an error here, but there is a bug where
1413
0
        // the doom list sometimes gets an entry 'stuck' and doens't clear it
1414
0
        // until browser shutdown.  So we allow revalidation for the time being
1415
0
        // to get proper telemetry data of how much the cache corruption plan
1416
0
        // would help.
1417
0
    }
1418
0
1419
0
    // If telemetry data shows it is worth it, we'll be flushing headers and
1420
0
    // records before flushing the clean cache file.
1421
0
1422
0
    // Write out the _CACHE_CLEAN_ file with '1'
1423
0
    rv = WriteCacheClean(true);
1424
0
    if (NS_FAILED(rv)) {
1425
0
        return rv;
1426
0
    }
1427
0
1428
0
    mIsDirtyCacheFlushed = false;
1429
0
1430
0
    return NS_OK;
1431
0
}