1 // Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/disk_cache/block_files.h"
6
7 #include "base/file_util.h"
8 #include "base/metrics/histogram.h"
9 #include "base/string_util.h"
10 #include "base/stringprintf.h"
11 #include "base/threading/thread_checker.h"
12 #include "base/time.h"
13 #include "net/disk_cache/cache_util.h"
14 #include "net/disk_cache/file_lock.h"
15 #include "net/disk_cache/trace.h"
16
17 using base::TimeTicks;
18
19 namespace {
20
21 const char* kBlockName = "data_";
22
23 // This array is used to perform a fast lookup of the nibble bit pattern to the
24 // type of entry that can be stored there (number of consecutive blocks).
25 const char s_types[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
26
27 // Returns the type of block (number of consecutive blocks that can be stored)
28 // for a given nibble of the bitmap.
GetMapBlockType(uint8 value)29 inline int GetMapBlockType(uint8 value) {
30 value &= 0xf;
31 return s_types[value];
32 }
33
34 void FixAllocationCounters(disk_cache::BlockFileHeader* header);
35
36 // Creates a new entry on the allocation map, updating the apropriate counters.
37 // target is the type of block to use (number of empty blocks), and size is the
38 // actual number of blocks to use.
CreateMapBlock(int target,int size,disk_cache::BlockFileHeader * header,int * index)39 bool CreateMapBlock(int target, int size, disk_cache::BlockFileHeader* header,
40 int* index) {
41 if (target <= 0 || target > disk_cache::kMaxNumBlocks ||
42 size <= 0 || size > disk_cache::kMaxNumBlocks) {
43 NOTREACHED();
44 return false;
45 }
46
47 TimeTicks start = TimeTicks::Now();
48 // We are going to process the map on 32-block chunks (32 bits), and on every
49 // chunk, iterate through the 8 nibbles where the new block can be located.
50 int current = header->hints[target - 1];
51 for (int i = 0; i < header->max_entries / 32; i++, current++) {
52 if (current == header->max_entries / 32)
53 current = 0;
54 uint32 map_block = header->allocation_map[current];
55
56 for (int j = 0; j < 8; j++, map_block >>= 4) {
57 if (GetMapBlockType(map_block) != target)
58 continue;
59
60 disk_cache::FileLock lock(header);
61 int index_offset = j * 4 + 4 - target;
62 *index = current * 32 + index_offset;
63 DCHECK_EQ(*index / 4, (*index + size - 1) / 4);
64 uint32 to_add = ((1 << size) - 1) << index_offset;
65 header->allocation_map[current] |= to_add;
66
67 header->hints[target - 1] = current;
68 header->empty[target - 1]--;
69 DCHECK(header->empty[target - 1] >= 0);
70 header->num_entries++;
71 if (target != size) {
72 header->empty[target - size - 1]++;
73 }
74 HISTOGRAM_TIMES("DiskCache.CreateBlock", TimeTicks::Now() - start);
75 return true;
76 }
77 }
78
79 // It is possible to have an undetected corruption (for example when the OS
80 // crashes), fix it here.
81 LOG(ERROR) << "Failing CreateMapBlock";
82 FixAllocationCounters(header);
83 return false;
84 }
85
86 // Deletes the block pointed by index from allocation_map, and updates the
87 // relevant counters on the header.
DeleteMapBlock(int index,int size,disk_cache::BlockFileHeader * header)88 void DeleteMapBlock(int index, int size, disk_cache::BlockFileHeader* header) {
89 if (size < 0 || size > disk_cache::kMaxNumBlocks) {
90 NOTREACHED();
91 return;
92 }
93 TimeTicks start = TimeTicks::Now();
94 int byte_index = index / 8;
95 uint8* byte_map = reinterpret_cast<uint8*>(header->allocation_map);
96 uint8 map_block = byte_map[byte_index];
97
98 if (index % 8 >= 4)
99 map_block >>= 4;
100
101 // See what type of block will be availabe after we delete this one.
102 int bits_at_end = 4 - size - index % 4;
103 uint8 end_mask = (0xf << (4 - bits_at_end)) & 0xf;
104 bool update_counters = (map_block & end_mask) == 0;
105 uint8 new_value = map_block & ~(((1 << size) - 1) << (index % 4));
106 int new_type = GetMapBlockType(new_value);
107
108 disk_cache::FileLock lock(header);
109 DCHECK((((1 << size) - 1) << (index % 8)) < 0x100);
110 uint8 to_clear = ((1 << size) - 1) << (index % 8);
111 DCHECK((byte_map[byte_index] & to_clear) == to_clear);
112 byte_map[byte_index] &= ~to_clear;
113
114 if (update_counters) {
115 if (bits_at_end)
116 header->empty[bits_at_end - 1]--;
117 header->empty[new_type - 1]++;
118 DCHECK(header->empty[bits_at_end - 1] >= 0);
119 }
120 header->num_entries--;
121 DCHECK(header->num_entries >= 0);
122 HISTOGRAM_TIMES("DiskCache.DeleteBlock", TimeTicks::Now() - start);
123 }
124
125 #ifndef NDEBUG
126 // Returns true if the specified block is used. Note that this is a simplified
127 // version of DeleteMapBlock().
UsedMapBlock(int index,int size,disk_cache::BlockFileHeader * header)128 bool UsedMapBlock(int index, int size, disk_cache::BlockFileHeader* header) {
129 if (size < 0 || size > disk_cache::kMaxNumBlocks) {
130 NOTREACHED();
131 return false;
132 }
133 int byte_index = index / 8;
134 uint8* byte_map = reinterpret_cast<uint8*>(header->allocation_map);
135 uint8 map_block = byte_map[byte_index];
136
137 if (index % 8 >= 4)
138 map_block >>= 4;
139
140 DCHECK((((1 << size) - 1) << (index % 8)) < 0x100);
141 uint8 to_clear = ((1 << size) - 1) << (index % 8);
142 return ((byte_map[byte_index] & to_clear) == to_clear);
143 }
144 #endif // NDEBUG
145
146 // Restores the "empty counters" and allocation hints.
FixAllocationCounters(disk_cache::BlockFileHeader * header)147 void FixAllocationCounters(disk_cache::BlockFileHeader* header) {
148 for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) {
149 header->hints[i] = 0;
150 header->empty[i] = 0;
151 }
152
153 for (int i = 0; i < header->max_entries / 32; i++) {
154 uint32 map_block = header->allocation_map[i];
155
156 for (int j = 0; j < 8; j++, map_block >>= 4) {
157 int type = GetMapBlockType(map_block);
158 if (type)
159 header->empty[type -1]++;
160 }
161 }
162 }
163
164 // Returns true if the current block file should not be used as-is to store more
165 // records. |block_count| is the number of blocks to allocate.
NeedToGrowBlockFile(const disk_cache::BlockFileHeader * header,int block_count)166 bool NeedToGrowBlockFile(const disk_cache::BlockFileHeader* header,
167 int block_count) {
168 bool have_space = false;
169 int empty_blocks = 0;
170 for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) {
171 empty_blocks += header->empty[i] * (i + 1);
172 if (i >= block_count - 1 && header->empty[i])
173 have_space = true;
174 }
175
176 if (header->next_file && (empty_blocks < disk_cache::kMaxBlocks / 10)) {
177 // This file is almost full but we already created another one, don't use
178 // this file yet so that it is easier to find empty blocks when we start
179 // using this file again.
180 return true;
181 }
182 return !have_space;
183 }
184
185 } // namespace
186
187 namespace disk_cache {
188
BlockFiles(const FilePath & path)189 BlockFiles::BlockFiles(const FilePath& path)
190 : init_(false), zero_buffer_(NULL), path_(path) {
191 }
192
~BlockFiles()193 BlockFiles::~BlockFiles() {
194 if (zero_buffer_)
195 delete[] zero_buffer_;
196 CloseFiles();
197 }
198
Init(bool create_files)199 bool BlockFiles::Init(bool create_files) {
200 DCHECK(!init_);
201 if (init_)
202 return false;
203
204 thread_checker_.reset(new base::ThreadChecker);
205
206 block_files_.resize(kFirstAdditionalBlockFile);
207 for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
208 if (create_files)
209 if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true))
210 return false;
211
212 if (!OpenBlockFile(i))
213 return false;
214
215 // Walk this chain of files removing empty ones.
216 RemoveEmptyFile(static_cast<FileType>(i + 1));
217 }
218
219 init_ = true;
220 return true;
221 }
222
GetFile(Addr address)223 MappedFile* BlockFiles::GetFile(Addr address) {
224 DCHECK(thread_checker_->CalledOnValidThread());
225 DCHECK(block_files_.size() >= 4);
226 DCHECK(address.is_block_file() || !address.is_initialized());
227 if (!address.is_initialized())
228 return NULL;
229
230 int file_index = address.FileNumber();
231 if (static_cast<unsigned int>(file_index) >= block_files_.size() ||
232 !block_files_[file_index]) {
233 // We need to open the file
234 if (!OpenBlockFile(file_index))
235 return NULL;
236 }
237 DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index));
238 return block_files_[file_index];
239 }
240
CreateBlock(FileType block_type,int block_count,Addr * block_address)241 bool BlockFiles::CreateBlock(FileType block_type, int block_count,
242 Addr* block_address) {
243 DCHECK(thread_checker_->CalledOnValidThread());
244 if (block_type < RANKINGS || block_type > BLOCK_4K ||
245 block_count < 1 || block_count > 4)
246 return false;
247 if (!init_)
248 return false;
249
250 MappedFile* file = FileForNewBlock(block_type, block_count);
251 if (!file)
252 return false;
253
254 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
255
256 int target_size = 0;
257 for (int i = block_count; i <= 4; i++) {
258 if (header->empty[i - 1]) {
259 target_size = i;
260 break;
261 }
262 }
263
264 DCHECK(target_size);
265 int index;
266 if (!CreateMapBlock(target_size, block_count, header, &index))
267 return false;
268
269 Addr address(block_type, block_count, header->this_file, index);
270 block_address->set_value(address.value());
271 Trace("CreateBlock 0x%x", address.value());
272 return true;
273 }
274
DeleteBlock(Addr address,bool deep)275 void BlockFiles::DeleteBlock(Addr address, bool deep) {
276 DCHECK(thread_checker_->CalledOnValidThread());
277 if (!address.is_initialized() || address.is_separate_file())
278 return;
279
280 if (!zero_buffer_) {
281 zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4];
282 memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4);
283 }
284 MappedFile* file = GetFile(address);
285 if (!file)
286 return;
287
288 Trace("DeleteBlock 0x%x", address.value());
289
290 size_t size = address.BlockSize() * address.num_blocks();
291 size_t offset = address.start_block() * address.BlockSize() +
292 kBlockHeaderSize;
293 if (deep)
294 file->Write(zero_buffer_, size, offset);
295
296 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
297 DeleteMapBlock(address.start_block(), address.num_blocks(), header);
298
299 if (!header->num_entries) {
300 // This file is now empty. Let's try to delete it.
301 FileType type = Addr::RequiredFileType(header->entry_size);
302 if (Addr::BlockSizeForFileType(RANKINGS) == header->entry_size)
303 type = RANKINGS;
304 RemoveEmptyFile(type);
305 }
306 }
307
CloseFiles()308 void BlockFiles::CloseFiles() {
309 if (init_) {
310 DCHECK(thread_checker_->CalledOnValidThread());
311 }
312 init_ = false;
313 for (unsigned int i = 0; i < block_files_.size(); i++) {
314 if (block_files_[i]) {
315 block_files_[i]->Release();
316 block_files_[i] = NULL;
317 }
318 }
319 block_files_.clear();
320 }
321
ReportStats()322 void BlockFiles::ReportStats() {
323 DCHECK(thread_checker_->CalledOnValidThread());
324 int used_blocks[kFirstAdditionalBlockFile];
325 int load[kFirstAdditionalBlockFile];
326 for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
327 GetFileStats(i, &used_blocks[i], &load[i]);
328 }
329 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks[0]);
330 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks[1]);
331 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks[2]);
332 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks[3]);
333
334 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load[0], 101);
335 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load[1], 101);
336 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load[2], 101);
337 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101);
338 }
339
IsValid(Addr address)340 bool BlockFiles::IsValid(Addr address) {
341 #ifdef NDEBUG
342 return true;
343 #else
344 if (!address.is_initialized() || address.is_separate_file())
345 return false;
346
347 MappedFile* file = GetFile(address);
348 if (!file)
349 return false;
350
351 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
352 bool rv = UsedMapBlock(address.start_block(), address.num_blocks(), header);
353 DCHECK(rv);
354
355 static bool read_contents = false;
356 if (read_contents) {
357 scoped_array<char> buffer;
358 buffer.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]);
359 size_t size = address.BlockSize() * address.num_blocks();
360 size_t offset = address.start_block() * address.BlockSize() +
361 kBlockHeaderSize;
362 bool ok = file->Read(buffer.get(), size, offset);
363 DCHECK(ok);
364 }
365
366 return rv;
367 #endif
368 }
369
CreateBlockFile(int index,FileType file_type,bool force)370 bool BlockFiles::CreateBlockFile(int index, FileType file_type, bool force) {
371 FilePath name = Name(index);
372 int flags =
373 force ? base::PLATFORM_FILE_CREATE_ALWAYS : base::PLATFORM_FILE_CREATE;
374 flags |= base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_WRITE;
375
376 scoped_refptr<File> file(new File(
377 base::CreatePlatformFile(name, flags, NULL, NULL)));
378 if (!file->IsValid())
379 return false;
380
381 BlockFileHeader header;
382 header.entry_size = Addr::BlockSizeForFileType(file_type);
383 header.this_file = static_cast<int16>(index);
384 DCHECK(index <= kint16max && index >= 0);
385
386 return file->Write(&header, sizeof(header), 0);
387 }
388
OpenBlockFile(int index)389 bool BlockFiles::OpenBlockFile(int index) {
390 if (block_files_.size() - 1 < static_cast<unsigned int>(index)) {
391 DCHECK(index > 0);
392 int to_add = index - static_cast<int>(block_files_.size()) + 1;
393 block_files_.resize(block_files_.size() + to_add);
394 }
395
396 FilePath name = Name(index);
397 scoped_refptr<MappedFile> file(new MappedFile());
398
399 if (!file->Init(name, kBlockHeaderSize)) {
400 LOG(ERROR) << "Failed to open " << name.value();
401 return false;
402 }
403
404 size_t file_len = file->GetLength();
405 if (file_len < static_cast<size_t>(kBlockHeaderSize)) {
406 LOG(ERROR) << "File too small " << name.value();
407 return false;
408 }
409
410 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
411 if (kBlockMagic != header->magic || kCurrentVersion != header->version) {
412 LOG(ERROR) << "Invalid file version or magic";
413 return false;
414 }
415
416 if (header->updating) {
417 // Last instance was not properly shutdown.
418 if (!FixBlockFileHeader(file))
419 return false;
420 }
421
422 if (static_cast<int>(file_len) <
423 header->max_entries * header->entry_size + kBlockHeaderSize) {
424 LOG(ERROR) << "File too small " << name.value();
425 return false;
426 }
427
428 if (index == 0) {
429 // Load the links file into memory with a single read.
430 scoped_array<char> buf(new char[file_len]);
431 if (!file->Read(buf.get(), file_len, 0))
432 return false;
433 }
434
435 DCHECK(!block_files_[index]);
436 file.swap(&block_files_[index]);
437 return true;
438 }
439
GrowBlockFile(MappedFile * file,BlockFileHeader * header)440 bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) {
441 if (kMaxBlocks == header->max_entries)
442 return false;
443
444 DCHECK(!header->empty[3]);
445 int new_size = header->max_entries + 1024;
446 if (new_size > kMaxBlocks)
447 new_size = kMaxBlocks;
448
449 int new_size_bytes = new_size * header->entry_size + sizeof(*header);
450
451 FileLock lock(header);
452 if (!file->SetLength(new_size_bytes)) {
453 // Most likely we are trying to truncate the file, so the header is wrong.
454 if (header->updating < 10 && !FixBlockFileHeader(file)) {
455 // If we can't fix the file increase the lock guard so we'll pick it on
456 // the next start and replace it.
457 header->updating = 100;
458 return false;
459 }
460 return (header->max_entries >= new_size);
461 }
462
463 header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries
464 header->max_entries = new_size;
465
466 return true;
467 }
468
FileForNewBlock(FileType block_type,int block_count)469 MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) {
470 COMPILE_ASSERT(RANKINGS == 1, invalid_fily_type);
471 MappedFile* file = block_files_[block_type - 1];
472 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
473
474 TimeTicks start = TimeTicks::Now();
475 while (NeedToGrowBlockFile(header, block_count)) {
476 if (kMaxBlocks == header->max_entries) {
477 file = NextFile(file);
478 if (!file)
479 return NULL;
480 header = reinterpret_cast<BlockFileHeader*>(file->buffer());
481 continue;
482 }
483
484 if (!GrowBlockFile(file, header))
485 return NULL;
486 break;
487 }
488 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start);
489 return file;
490 }
491
NextFile(const MappedFile * file)492 MappedFile* BlockFiles::NextFile(const MappedFile* file) {
493 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
494 int new_file = header->next_file;
495 if (!new_file) {
496 // RANKINGS is not reported as a type for small entries, but we may be
497 // extending the rankings block file.
498 FileType type = Addr::RequiredFileType(header->entry_size);
499 if (header->entry_size == Addr::BlockSizeForFileType(RANKINGS))
500 type = RANKINGS;
501
502 new_file = CreateNextBlockFile(type);
503 if (!new_file)
504 return NULL;
505
506 FileLock lock(header);
507 header->next_file = new_file;
508 }
509
510 // Only the block_file argument is relevant for what we want.
511 Addr address(BLOCK_256, 1, new_file, 0);
512 return GetFile(address);
513 }
514
CreateNextBlockFile(FileType block_type)515 int BlockFiles::CreateNextBlockFile(FileType block_type) {
516 for (int i = kFirstAdditionalBlockFile; i <= kMaxBlockFile; i++) {
517 if (CreateBlockFile(i, block_type, false))
518 return i;
519 }
520 return 0;
521 }
522
523 // We walk the list of files for this particular block type, deleting the ones
524 // that are empty.
RemoveEmptyFile(FileType block_type)525 void BlockFiles::RemoveEmptyFile(FileType block_type) {
526 MappedFile* file = block_files_[block_type - 1];
527 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
528
529 while (header->next_file) {
530 // Only the block_file argument is relevant for what we want.
531 Addr address(BLOCK_256, 1, header->next_file, 0);
532 MappedFile* next_file = GetFile(address);
533 if (!next_file)
534 return;
535
536 BlockFileHeader* next_header =
537 reinterpret_cast<BlockFileHeader*>(next_file->buffer());
538 if (!next_header->num_entries) {
539 DCHECK_EQ(next_header->entry_size, header->entry_size);
540 // Delete next_file and remove it from the chain.
541 int file_index = header->next_file;
542 header->next_file = next_header->next_file;
543 DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index));
544
545 // We get a new handle to the file and release the old one so that the
546 // file gets unmmaped... so we can delete it.
547 FilePath name = Name(file_index);
548 scoped_refptr<File> this_file(new File(false));
549 this_file->Init(name);
550 block_files_[file_index]->Release();
551 block_files_[file_index] = NULL;
552
553 int failure = DeleteCacheFile(name) ? 0 : 1;
554 UMA_HISTOGRAM_COUNTS("DiskCache.DeleteFailed2", failure);
555 if (failure)
556 LOG(ERROR) << "Failed to delete " << name.value() << " from the cache.";
557 continue;
558 }
559
560 header = next_header;
561 file = next_file;
562 }
563 }
564
FixBlockFileHeader(MappedFile * file)565 bool BlockFiles::FixBlockFileHeader(MappedFile* file) {
566 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
567 int file_size = static_cast<int>(file->GetLength());
568 if (file_size < static_cast<int>(sizeof(*header)))
569 return false; // file_size > 2GB is also an error.
570
571 int expected = header->entry_size * header->max_entries + sizeof(*header);
572 if (file_size != expected) {
573 int max_expected = header->entry_size * kMaxBlocks + sizeof(*header);
574 if (file_size < expected || header->empty[3] || file_size > max_expected) {
575 NOTREACHED();
576 LOG(ERROR) << "Unexpected file size";
577 return false;
578 }
579 // We were in the middle of growing the file.
580 int num_entries = (file_size - sizeof(*header)) / header->entry_size;
581 header->max_entries = num_entries;
582 }
583
584 FixAllocationCounters(header);
585 header->updating = 0;
586 return true;
587 }
588
589 // We are interested in the total number of blocks used by this file type, and
590 // the max number of blocks that we can store (reported as the percentage of
591 // used blocks). In order to find out the number of used blocks, we have to
592 // substract the empty blocks from the total blocks for each file in the chain.
GetFileStats(int index,int * used_count,int * load)593 void BlockFiles::GetFileStats(int index, int* used_count, int* load) {
594 int max_blocks = 0;
595 *used_count = 0;
596 *load = 0;
597 for (;;) {
598 if (!block_files_[index] && !OpenBlockFile(index))
599 return;
600
601 BlockFileHeader* header =
602 reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer());
603
604 max_blocks += header->max_entries;
605 int used = header->max_entries;
606 for (int i = 0; i < 4; i++) {
607 used -= header->empty[i] * (i + 1);
608 DCHECK_GE(used, 0);
609 }
610 *used_count += used;
611
612 if (!header->next_file)
613 break;
614 index = header->next_file;
615 }
616 if (max_blocks)
617 *load = *used_count * 100 / max_blocks;
618 }
619
Name(int index)620 FilePath BlockFiles::Name(int index) {
621 // The file format allows for 256 files.
622 DCHECK(index < 256 || index >= 0);
623 std::string tmp = base::StringPrintf("%s%d", kBlockName, index);
624 return path_.AppendASCII(tmp);
625 }
626
627 } // namespace disk_cache
628