Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/netwerk/cache/nsDiskCacheBlockFile.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
 *
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 "nsDiskCache.h"
9
#include "nsDiskCacheBlockFile.h"
10
#include "mozilla/FileUtils.h"
11
#include "mozilla/IntegerPrintfMacros.h"
12
#include "mozilla/MemoryReporting.h"
13
#include <algorithm>
14
15
using namespace mozilla;
16
17
/******************************************************************************
18
 * nsDiskCacheBlockFile -
19
 *****************************************************************************/
20
21
/******************************************************************************
22
 *  Open
23
 *****************************************************************************/
24
nsresult
25
nsDiskCacheBlockFile::Open(nsIFile * blockFile,
26
                           uint32_t  blockSize,
27
                           uint32_t  bitMapSize,
28
                           nsDiskCache::CorruptCacheInfo *  corruptInfo)
29
0
{
30
0
    NS_ENSURE_ARG_POINTER(corruptInfo);
31
0
    *corruptInfo = nsDiskCache::kUnexpectedError;
32
0
33
0
    if (bitMapSize % 32) {
34
0
        *corruptInfo = nsDiskCache::kInvalidArgPointer;
35
0
        return NS_ERROR_INVALID_ARG;
36
0
    }
37
0
38
0
    mBlockSize = blockSize;
39
0
    mBitMapWords = bitMapSize / 32;
40
0
    uint32_t bitMapBytes = mBitMapWords * 4;
41
0
42
0
    // open the file - restricted to user, the data could be confidential
43
0
    nsresult rv = blockFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 00600, &mFD);
44
0
    if (NS_FAILED(rv)) {
45
0
        *corruptInfo = nsDiskCache::kCouldNotCreateBlockFile;
46
0
        CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open "
47
0
                         "[this=%p] unable to open or create file: %" PRId32,
48
0
                         this, static_cast<uint32_t>(rv)));
49
0
        return rv;  // unable to open or create file
50
0
    }
51
0
52
0
    // allocate bit map buffer
53
0
    mBitMap = new uint32_t[mBitMapWords];
54
0
55
0
    // check if we just creating the file
56
0
    mFileSize = PR_Available(mFD);
57
0
    if (mFileSize < 0) {
58
0
        // XXX an error occurred. We could call PR_GetError(), but how would that help?
59
0
        *corruptInfo = nsDiskCache::kBlockFileSizeError;
60
0
        rv = NS_ERROR_UNEXPECTED;
61
0
        goto error_exit;
62
0
    }
63
0
    if (mFileSize == 0) {
64
0
        // initialize bit map and write it
65
0
        memset(mBitMap, 0, bitMapBytes);
66
0
        if (!Write(0, mBitMap, bitMapBytes)) {
67
0
            *corruptInfo = nsDiskCache::kBlockFileBitMapWriteError;
68
0
            goto error_exit;
69
0
        }
70
0
71
0
    } else if ((uint32_t)mFileSize < bitMapBytes) {
72
0
        *corruptInfo = nsDiskCache::kBlockFileSizeLessThanBitMap;
73
0
        rv = NS_ERROR_UNEXPECTED;  // XXX NS_ERROR_CACHE_INVALID;
74
0
        goto error_exit;
75
0
76
0
    } else {
77
0
        // read the bit map
78
0
        const int32_t bytesRead = PR_Read(mFD, mBitMap, bitMapBytes);
79
0
        if ((bytesRead < 0) || ((uint32_t)bytesRead < bitMapBytes)) {
80
0
            *corruptInfo = nsDiskCache::kBlockFileBitMapReadError;
81
0
            rv = NS_ERROR_UNEXPECTED;
82
0
            goto error_exit;
83
0
        }
84
0
#if defined(IS_LITTLE_ENDIAN)
85
0
        // Swap from network format
86
0
        for (unsigned int i = 0; i < mBitMapWords; ++i)
87
0
            mBitMap[i] = ntohl(mBitMap[i]);
88
0
#endif
89
0
        // validate block file size
90
0
        // Because not whole blocks are written, the size may be a
91
0
        // little bit smaller than used blocks times blocksize,
92
0
        // because the last block will generally not be 'whole'.
93
0
        const uint32_t  estimatedSize = CalcBlockFileSize();
94
0
        if ((uint32_t)mFileSize + blockSize < estimatedSize) {
95
0
            *corruptInfo = nsDiskCache::kBlockFileEstimatedSizeError;
96
0
            rv = NS_ERROR_UNEXPECTED;
97
0
            goto error_exit;
98
0
        }
99
0
    }
100
0
    CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] succeeded",
101
0
                      this));
102
0
    return NS_OK;
103
0
104
0
error_exit:
105
0
    CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Open [this=%p] failed with "
106
0
                     "error %" PRId32, this, static_cast<uint32_t>(rv)));
107
0
    Close(false);
108
0
    return rv;
109
0
}
110
111
112
/******************************************************************************
113
 *  Close
114
 *****************************************************************************/
115
nsresult
116
nsDiskCacheBlockFile::Close(bool flush)
117
0
{
118
0
    nsresult rv = NS_OK;
119
0
120
0
    if (mFD) {
121
0
        if (flush)
122
0
            rv  = FlushBitMap();
123
0
        PRStatus err = PR_Close(mFD);
124
0
        if (NS_SUCCEEDED(rv) && (err != PR_SUCCESS))
125
0
            rv = NS_ERROR_UNEXPECTED;
126
0
        mFD = nullptr;
127
0
    }
128
0
129
0
     if (mBitMap) {
130
0
         delete [] mBitMap;
131
0
         mBitMap = nullptr;
132
0
     }
133
0
134
0
    return rv;
135
0
}
136
137
138
/******************************************************************************
139
 *  AllocateBlocks
140
 *
141
 *  Allocates 1-4 blocks, using a first fit strategy,
142
 *  so that no group of blocks spans a quad block boundary.
143
 *
144
 *  Returns block number of first block allocated or -1 on failure.
145
 *
146
 *****************************************************************************/
147
int32_t
148
nsDiskCacheBlockFile::AllocateBlocks(int32_t numBlocks)
149
0
{
150
0
    const int maxPos = 32 - numBlocks;
151
0
    const uint32_t mask = (0x01 << numBlocks) - 1;
152
0
    for (unsigned int i = 0; i < mBitMapWords; ++i) {
153
0
        uint32_t mapWord = ~mBitMap[i]; // flip bits so free bits are 1
154
0
        if (mapWord) {                  // At least one free bit
155
0
            // Binary search for first free bit in word
156
0
            int bit = 0;
157
0
            if ((mapWord & 0x0FFFF) == 0) { bit |= 16; mapWord >>= 16; }
158
0
            if ((mapWord & 0x000FF) == 0) { bit |= 8;  mapWord >>= 8;  }
159
0
            if ((mapWord & 0x0000F) == 0) { bit |= 4;  mapWord >>= 4;  }
160
0
            if ((mapWord & 0x00003) == 0) { bit |= 2;  mapWord >>= 2;  }
161
0
            if ((mapWord & 0x00001) == 0) { bit |= 1;  mapWord >>= 1;  }
162
0
            // Find first fit for mask
163
0
            for (; bit <= maxPos; ++bit) {
164
0
                // all bits selected by mask are 1, so free
165
0
                if ((mask & mapWord) == mask) {
166
0
                    mBitMap[i] |= mask << bit;
167
0
                    mBitMapDirty = true;
168
0
                    return (int32_t)i * 32 + bit;
169
0
                }
170
0
            }
171
0
        }
172
0
    }
173
0
174
0
    return -1;
175
0
}
176
177
178
/******************************************************************************
179
 *  DeallocateBlocks
180
 *****************************************************************************/
181
nsresult
182
nsDiskCacheBlockFile::DeallocateBlocks( int32_t  startBlock, int32_t  numBlocks)
183
0
{
184
0
    if (!mFD)  return NS_ERROR_NOT_AVAILABLE;
185
0
186
0
    if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) ||
187
0
        (numBlocks < 1)  || (numBlocks > 4))
188
0
       return NS_ERROR_ILLEGAL_VALUE;
189
0
190
0
    const int32_t startWord = startBlock >> 5;      // Divide by 32
191
0
    const uint32_t startBit = startBlock & 31;      // Modulo by 32
192
0
193
0
    // make sure requested deallocation doesn't span a word boundary
194
0
    if (startBit + numBlocks > 32)  return NS_ERROR_UNEXPECTED;
195
0
    uint32_t mask = ((0x01 << numBlocks) - 1) << startBit;
196
0
197
0
    // make sure requested deallocation is currently allocated
198
0
    if ((mBitMap[startWord] & mask) != mask)    return NS_ERROR_ABORT;
199
0
200
0
    mBitMap[startWord] ^= mask;    // flips the bits off;
201
0
    mBitMapDirty = true;
202
0
    // XXX rv = FlushBitMap();  // coherency vs. performance
203
0
    return NS_OK;
204
0
}
205
206
207
/******************************************************************************
208
 *  WriteBlocks
209
 *****************************************************************************/
210
nsresult
211
nsDiskCacheBlockFile::WriteBlocks( void *   buffer,
212
                                   uint32_t size,
213
                                   int32_t  numBlocks,
214
                                   int32_t * startBlock)
215
0
{
216
0
    // presume buffer != nullptr and startBlock != nullptr
217
0
    NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_AVAILABLE);
218
0
219
0
    // allocate some blocks in the cache block file
220
0
    *startBlock = AllocateBlocks(numBlocks);
221
0
    if (*startBlock < 0)
222
0
        return NS_ERROR_NOT_AVAILABLE;
223
0
224
0
    // seek to block position
225
0
    int32_t blockPos = mBitMapWords * 4 + *startBlock * mBlockSize;
226
0
227
0
    // write the blocks
228
0
    return Write(blockPos, buffer, size) ? NS_OK : NS_ERROR_FAILURE;
229
0
}
230
231
232
/******************************************************************************
233
 *  ReadBlocks
234
 *****************************************************************************/
235
nsresult
236
nsDiskCacheBlockFile::ReadBlocks( void *    buffer,
237
                                  int32_t   startBlock,
238
                                  int32_t   numBlocks,
239
                                  int32_t * bytesRead)
240
0
{
241
0
    // presume buffer != nullptr and bytesRead != bytesRead
242
0
243
0
    if (!mFD)  return NS_ERROR_NOT_AVAILABLE;
244
0
    nsresult rv = VerifyAllocation(startBlock, numBlocks);
245
0
    if (NS_FAILED(rv))  return rv;
246
0
247
0
    // seek to block position
248
0
    int32_t blockPos = mBitMapWords * 4 + startBlock * mBlockSize;
249
0
    int32_t filePos = PR_Seek(mFD, blockPos, PR_SEEK_SET);
250
0
    if (filePos != blockPos)  return NS_ERROR_UNEXPECTED;
251
0
252
0
    // read the blocks
253
0
    int32_t bytesToRead = *bytesRead;
254
0
    if ((bytesToRead <= 0) || ((uint32_t)bytesToRead > mBlockSize * numBlocks)) {
255
0
        bytesToRead = mBlockSize * numBlocks;
256
0
    }
257
0
    *bytesRead = PR_Read(mFD, buffer, bytesToRead);
258
0
259
0
    CACHE_LOG_DEBUG(("CACHE: nsDiskCacheBlockFile::Read [this=%p] "
260
0
                     "returned %d / %d bytes", this, *bytesRead, bytesToRead));
261
0
262
0
    return NS_OK;
263
0
}
264
265
266
/******************************************************************************
267
 *  FlushBitMap
268
 *****************************************************************************/
269
nsresult
270
nsDiskCacheBlockFile::FlushBitMap()
271
0
{
272
0
    if (!mBitMapDirty)  return NS_OK;
273
0
274
0
#if defined(IS_LITTLE_ENDIAN)
275
0
    uint32_t *bitmap = new uint32_t[mBitMapWords];
276
0
    // Copy and swap to network format
277
0
    uint32_t *p = bitmap;
278
0
    for (unsigned int i = 0; i < mBitMapWords; ++i, ++p)
279
0
      *p = htonl(mBitMap[i]);
280
#else
281
    uint32_t *bitmap = mBitMap;
282
#endif
283
284
0
    // write bitmap
285
0
    bool written = Write(0, bitmap, mBitMapWords * 4);
286
0
#if defined(IS_LITTLE_ENDIAN)
287
0
    delete [] bitmap;
288
0
#endif
289
0
    if (!written)
290
0
        return NS_ERROR_UNEXPECTED;
291
0
292
0
    PRStatus err = PR_Sync(mFD);
293
0
    if (err != PR_SUCCESS)  return NS_ERROR_UNEXPECTED;
294
0
295
0
    mBitMapDirty = false;
296
0
    return NS_OK;
297
0
}
298
299
300
/******************************************************************************
301
 *  VerifyAllocation
302
 *
303
 *  Return values:
304
 *      NS_OK if all bits are marked allocated
305
 *      NS_ERROR_ILLEGAL_VALUE if parameters don't obey constraints
306
 *      NS_ERROR_FAILURE if some or all the bits are marked unallocated
307
 *
308
 *****************************************************************************/
309
nsresult
310
nsDiskCacheBlockFile::VerifyAllocation( int32_t  startBlock, int32_t  numBlocks)
311
0
{
312
0
    if ((startBlock < 0) || ((uint32_t)startBlock > mBitMapWords * 32 - 1) ||
313
0
        (numBlocks < 1)  || (numBlocks > 4))
314
0
       return NS_ERROR_ILLEGAL_VALUE;
315
0
316
0
    const int32_t startWord = startBlock >> 5;      // Divide by 32
317
0
    const uint32_t startBit = startBlock & 31;      // Modulo by 32
318
0
319
0
    // make sure requested deallocation doesn't span a word boundary
320
0
    if (startBit + numBlocks > 32)  return NS_ERROR_ILLEGAL_VALUE;
321
0
    uint32_t mask = ((0x01 << numBlocks) - 1) << startBit;
322
0
323
0
    // check if all specified blocks are currently allocated
324
0
    if ((mBitMap[startWord] & mask) != mask)    return NS_ERROR_FAILURE;
325
0
326
0
    return NS_OK;
327
0
}
328
329
330
/******************************************************************************
331
 *  CalcBlockFileSize
332
 *
333
 *  Return size of the block file according to the bits set in mBitmap
334
 *
335
 *****************************************************************************/
336
uint32_t
337
nsDiskCacheBlockFile::CalcBlockFileSize()
338
0
{
339
0
    // search for last byte in mBitMap with allocated bits
340
0
    uint32_t  estimatedSize = mBitMapWords * 4;
341
0
    int32_t   i = mBitMapWords;
342
0
    while (--i >= 0) {
343
0
        if (mBitMap[i]) break;
344
0
    }
345
0
346
0
    if (i >= 0) {
347
0
        // binary search to find last allocated bit in byte
348
0
        uint32_t mapWord = mBitMap[i];
349
0
        uint32_t lastBit = 31;
350
0
        if ((mapWord & 0xFFFF0000) == 0) { lastBit ^= 16; mapWord <<= 16; }
351
0
        if ((mapWord & 0xFF000000) == 0) { lastBit ^= 8; mapWord <<= 8; }
352
0
        if ((mapWord & 0xF0000000) == 0) { lastBit ^= 4; mapWord <<= 4; }
353
0
        if ((mapWord & 0xC0000000) == 0) { lastBit ^= 2; mapWord <<= 2; }
354
0
        if ((mapWord & 0x80000000) == 0) { lastBit ^= 1; mapWord <<= 1; }
355
0
        estimatedSize +=  (i * 32 + lastBit + 1) * mBlockSize;
356
0
    }
357
0
358
0
    return estimatedSize;
359
0
}
360
361
/******************************************************************************
362
 *  Write
363
 *
364
 *  Wrapper around PR_Write that grows file in larger chunks to combat fragmentation
365
 *
366
 *****************************************************************************/
367
bool
368
nsDiskCacheBlockFile::Write(int32_t offset, const void *buf, int32_t amount)
369
0
{
370
0
    /* Grow the file to 4mb right away, then double it until the file grows to 20mb.
371
0
       20mb is a magic threshold because OSX stops autodefragging files bigger than that.
372
0
       Beyond 20mb grow in 4mb chunks.
373
0
     */
374
0
    const int32_t upTo = offset + amount;
375
0
    // Use a conservative definition of 20MB
376
0
    const int32_t minPreallocate = 4*1024*1024;
377
0
    const int32_t maxPreallocate = 20*1000*1000;
378
0
    if (mFileSize < upTo) {
379
0
        // maximal file size
380
0
        const int32_t maxFileSize = mBitMapWords * 4 * (mBlockSize * 8 + 1);
381
0
        if (upTo > maxPreallocate) {
382
0
            // grow the file as a multiple of minPreallocate
383
0
            mFileSize = ((upTo + minPreallocate - 1) / minPreallocate) * minPreallocate;
384
0
        } else {
385
0
            // Grow quickly between 1MB to 20MB
386
0
            if (mFileSize)
387
0
                while(mFileSize < upTo)
388
0
                    mFileSize *= 2;
389
0
            mFileSize = clamped(mFileSize, minPreallocate, maxPreallocate);
390
0
        }
391
0
        mFileSize = std::min(mFileSize, maxFileSize);
392
0
#if !defined(XP_MACOSX)
393
0
        mozilla::fallocate(mFD, mFileSize);
394
0
#endif
395
0
    }
396
0
    if (PR_Seek(mFD, offset, PR_SEEK_SET) != offset)
397
0
        return false;
398
0
    return PR_Write(mFD, buf, amount) == amount;
399
0
}
400
401
size_t
402
nsDiskCacheBlockFile::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
403
0
{
404
0
    return aMallocSizeOf(mBitMap) + aMallocSizeOf(mFD);
405
0
}