OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/disk_cache/block_files.h" | 5 #include "net/disk_cache/block_files.h" |
6 | 6 |
7 #include "base/atomicops.h" | 7 #include "base/atomicops.h" |
8 #include "base/files/file_path.h" | 8 #include "base/file_util.h" |
9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
10 #include "base/string_util.h" | 10 #include "base/string_util.h" |
11 #include "base/stringprintf.h" | 11 #include "base/stringprintf.h" |
12 #include "base/threading/thread_checker.h" | 12 #include "base/threading/thread_checker.h" |
13 #include "base/time.h" | 13 #include "base/time.h" |
14 #include "net/disk_cache/cache_util.h" | 14 #include "net/disk_cache/cache_util.h" |
15 #include "net/disk_cache/file_lock.h" | 15 #include "net/disk_cache/file_lock.h" |
16 #include "net/disk_cache/trace.h" | 16 #include "net/disk_cache/trace.h" |
17 | 17 |
18 using base::TimeTicks; | 18 using base::TimeTicks; |
19 | 19 |
20 namespace { | |
21 | |
22 const char* kBlockName = "data_"; | |
23 | |
24 // This array is used to perform a fast lookup of the nibble bit pattern to the | |
25 // type of entry that can be stored there (number of consecutive blocks). | |
26 const char s_types[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}; | |
27 | |
28 // Returns the type of block (number of consecutive blocks that can be stored) | |
29 // for a given nibble of the bitmap. | |
30 inline int GetMapBlockType(uint8 value) { | |
31 value &= 0xf; | |
32 return s_types[value]; | |
33 } | |
34 | |
35 void FixAllocationCounters(disk_cache::BlockFileHeader* header); | |
36 | |
37 // Creates a new entry on the allocation map, updating the apropriate counters. | |
38 // target is the type of block to use (number of empty blocks), and size is the | |
39 // actual number of blocks to use. | |
40 bool CreateMapBlock(int target, int size, disk_cache::BlockFileHeader* header, | |
41 int* index) { | |
42 if (target <= 0 || target > disk_cache::kMaxNumBlocks || | |
43 size <= 0 || size > disk_cache::kMaxNumBlocks) { | |
44 NOTREACHED(); | |
45 return false; | |
46 } | |
47 | |
48 TimeTicks start = TimeTicks::Now(); | |
49 // We are going to process the map on 32-block chunks (32 bits), and on every | |
50 // chunk, iterate through the 8 nibbles where the new block can be located. | |
51 int current = header->hints[target - 1]; | |
52 for (int i = 0; i < header->max_entries / 32; i++, current++) { | |
53 if (current == header->max_entries / 32) | |
54 current = 0; | |
55 uint32 map_block = header->allocation_map[current]; | |
56 | |
57 for (int j = 0; j < 8; j++, map_block >>= 4) { | |
58 if (GetMapBlockType(map_block) != target) | |
59 continue; | |
60 | |
61 disk_cache::FileLock lock(header); | |
62 int index_offset = j * 4 + 4 - target; | |
63 *index = current * 32 + index_offset; | |
64 DCHECK_EQ(*index / 4, (*index + size - 1) / 4); | |
65 uint32 to_add = ((1 << size) - 1) << index_offset; | |
66 header->num_entries++; | |
67 | |
68 // Note that there is no race in the normal sense here, but if we enforce | |
69 // the order of memory accesses between num_entries and allocation_map, we | |
70 // can assert that even if we crash here, num_entries will never be less | |
71 // than the actual number of used blocks. | |
72 base::subtle::MemoryBarrier(); | |
73 header->allocation_map[current] |= to_add; | |
74 | |
75 header->hints[target - 1] = current; | |
76 header->empty[target - 1]--; | |
77 DCHECK_GE(header->empty[target - 1], 0); | |
78 if (target != size) { | |
79 header->empty[target - size - 1]++; | |
80 } | |
81 HISTOGRAM_TIMES("DiskCache.CreateBlock", TimeTicks::Now() - start); | |
82 return true; | |
83 } | |
84 } | |
85 | |
86 // It is possible to have an undetected corruption (for example when the OS | |
87 // crashes), fix it here. | |
88 LOG(ERROR) << "Failing CreateMapBlock"; | |
89 FixAllocationCounters(header); | |
90 return false; | |
91 } | |
92 | |
93 // Deletes the block pointed by index from allocation_map, and updates the | |
94 // relevant counters on the header. | |
95 void DeleteMapBlock(int index, int size, disk_cache::BlockFileHeader* header) { | |
96 if (size < 0 || size > disk_cache::kMaxNumBlocks) { | |
97 NOTREACHED(); | |
98 return; | |
99 } | |
100 TimeTicks start = TimeTicks::Now(); | |
101 int byte_index = index / 8; | |
102 uint8* byte_map = reinterpret_cast<uint8*>(header->allocation_map); | |
103 uint8 map_block = byte_map[byte_index]; | |
104 | |
105 if (index % 8 >= 4) | |
106 map_block >>= 4; | |
107 | |
108 // See what type of block will be availabe after we delete this one. | |
109 int bits_at_end = 4 - size - index % 4; | |
110 uint8 end_mask = (0xf << (4 - bits_at_end)) & 0xf; | |
111 bool update_counters = (map_block & end_mask) == 0; | |
112 uint8 new_value = map_block & ~(((1 << size) - 1) << (index % 4)); | |
113 int new_type = GetMapBlockType(new_value); | |
114 | |
115 disk_cache::FileLock lock(header); | |
116 DCHECK((((1 << size) - 1) << (index % 8)) < 0x100); | |
117 uint8 to_clear = ((1 << size) - 1) << (index % 8); | |
118 DCHECK((byte_map[byte_index] & to_clear) == to_clear); | |
119 byte_map[byte_index] &= ~to_clear; | |
120 | |
121 if (update_counters) { | |
122 if (bits_at_end) | |
123 header->empty[bits_at_end - 1]--; | |
124 header->empty[new_type - 1]++; | |
125 DCHECK_GE(header->empty[bits_at_end - 1], 0); | |
126 } | |
127 base::subtle::MemoryBarrier(); | |
128 header->num_entries--; | |
129 DCHECK_GE(header->num_entries, 0); | |
130 HISTOGRAM_TIMES("DiskCache.DeleteBlock", TimeTicks::Now() - start); | |
131 } | |
132 | |
133 #ifndef NDEBUG | |
134 // Returns true if the specified block is used. Note that this is a simplified | |
135 // version of DeleteMapBlock(). | |
136 bool UsedMapBlock(int index, int size, disk_cache::BlockFileHeader* header) { | |
137 if (size < 0 || size > disk_cache::kMaxNumBlocks) { | |
138 NOTREACHED(); | |
139 return false; | |
140 } | |
141 int byte_index = index / 8; | |
142 uint8* byte_map = reinterpret_cast<uint8*>(header->allocation_map); | |
143 uint8 map_block = byte_map[byte_index]; | |
144 | |
145 if (index % 8 >= 4) | |
146 map_block >>= 4; | |
147 | |
148 DCHECK((((1 << size) - 1) << (index % 8)) < 0x100); | |
149 uint8 to_clear = ((1 << size) - 1) << (index % 8); | |
150 return ((byte_map[byte_index] & to_clear) == to_clear); | |
151 } | |
152 #endif // NDEBUG | |
153 | |
154 // Restores the "empty counters" and allocation hints. | |
155 void FixAllocationCounters(disk_cache::BlockFileHeader* header) { | |
156 for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) { | |
157 header->hints[i] = 0; | |
158 header->empty[i] = 0; | |
159 } | |
160 | |
161 for (int i = 0; i < header->max_entries / 32; i++) { | |
162 uint32 map_block = header->allocation_map[i]; | |
163 | |
164 for (int j = 0; j < 8; j++, map_block >>= 4) { | |
165 int type = GetMapBlockType(map_block); | |
166 if (type) | |
167 header->empty[type -1]++; | |
168 } | |
169 } | |
170 } | |
171 | |
172 // Returns true if the current block file should not be used as-is to store more | |
173 // records. |block_count| is the number of blocks to allocate. | |
174 bool NeedToGrowBlockFile(const disk_cache::BlockFileHeader* header, | |
175 int block_count) { | |
176 bool have_space = false; | |
177 int empty_blocks = 0; | |
178 for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) { | |
179 empty_blocks += header->empty[i] * (i + 1); | |
180 if (i >= block_count - 1 && header->empty[i]) | |
181 have_space = true; | |
182 } | |
183 | |
184 if (header->next_file && (empty_blocks < disk_cache::kMaxBlocks / 10)) { | |
185 // This file is almost full but we already created another one, don't use | |
186 // this file yet so that it is easier to find empty blocks when we start | |
187 // using this file again. | |
188 return true; | |
189 } | |
190 return !have_space; | |
191 } | |
192 | |
193 // Returns the number of empty blocks for this file. | |
194 int EmptyBlocks(const disk_cache::BlockFileHeader* header) { | |
195 int empty_blocks = 0; | |
196 for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) { | |
197 empty_blocks += header->empty[i] * (i + 1); | |
198 if (header->empty[i] < 0) | |
199 return false; | |
200 } | |
201 return empty_blocks; | |
202 } | |
203 | |
204 // Returns true if the counters look OK. | |
205 bool ValidateCounters(const disk_cache::BlockFileHeader* header) { | |
206 if (header->max_entries < 0 || header->max_entries > disk_cache::kMaxBlocks || | |
207 header->num_entries < 0) | |
208 return false; | |
209 | |
210 int empty_blocks = EmptyBlocks(header); | |
211 if (empty_blocks + header->num_entries > header->max_entries) | |
212 return false; | |
213 | |
214 return true; | |
215 } | |
216 | |
217 } // namespace | |
218 | |
219 namespace disk_cache { | 20 namespace disk_cache { |
220 | 21 |
221 BlockFiles::BlockFiles(const base::FilePath& path) | 22 BlockFiles::BlockFiles(const base::FilePath& path) |
222 : init_(false), zero_buffer_(NULL), path_(path) { | 23 : init_(false), zero_buffer_(NULL), path_(path) { |
223 } | 24 } |
224 | 25 |
225 BlockFiles::~BlockFiles() { | 26 BlockFiles::~BlockFiles() { |
226 if (zero_buffer_) | 27 if (zero_buffer_) |
227 delete[] zero_buffer_; | 28 delete[] zero_buffer_; |
228 CloseFiles(); | 29 CloseFiles(); |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
395 size_t offset = address.start_block() * address.BlockSize() + | 196 size_t offset = address.start_block() * address.BlockSize() + |
396 kBlockHeaderSize; | 197 kBlockHeaderSize; |
397 bool ok = file->Read(buffer.get(), size, offset); | 198 bool ok = file->Read(buffer.get(), size, offset); |
398 DCHECK(ok); | 199 DCHECK(ok); |
399 } | 200 } |
400 | 201 |
401 return rv; | 202 return rv; |
402 #endif | 203 #endif |
403 } | 204 } |
404 | 205 |
405 bool BlockFiles::CreateBlockFile(int index, FileType file_type, bool force) { | |
406 base::FilePath name = Name(index); | |
407 int flags = | |
408 force ? base::PLATFORM_FILE_CREATE_ALWAYS : base::PLATFORM_FILE_CREATE; | |
409 flags |= base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_WRITE; | |
410 | |
411 scoped_refptr<File> file(new File( | |
412 base::CreatePlatformFile(name, flags, NULL, NULL))); | |
413 if (!file->IsValid()) | |
414 return false; | |
415 | |
416 BlockFileHeader header; | |
417 header.entry_size = Addr::BlockSizeForFileType(file_type); | |
418 header.this_file = static_cast<int16>(index); | |
419 DCHECK(index <= kint16max && index >= 0); | |
420 | |
421 return file->Write(&header, sizeof(header), 0); | |
422 } | |
423 | |
424 bool BlockFiles::OpenBlockFile(int index) { | |
425 if (block_files_.size() - 1 < static_cast<unsigned int>(index)) { | |
426 DCHECK(index > 0); | |
427 int to_add = index - static_cast<int>(block_files_.size()) + 1; | |
428 block_files_.resize(block_files_.size() + to_add); | |
429 } | |
430 | |
431 base::FilePath name = Name(index); | |
432 scoped_refptr<MappedFile> file(new MappedFile()); | |
433 | |
434 if (!file->Init(name, kBlockHeaderSize)) { | |
435 LOG(ERROR) << "Failed to open " << name.value(); | |
436 return false; | |
437 } | |
438 | |
439 size_t file_len = file->GetLength(); | |
440 if (file_len < static_cast<size_t>(kBlockHeaderSize)) { | |
441 LOG(ERROR) << "File too small " << name.value(); | |
442 return false; | |
443 } | |
444 | |
445 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); | |
446 if (kBlockMagic != header->magic || kCurrentVersion != header->version) { | |
447 LOG(ERROR) << "Invalid file version or magic " << name.value(); | |
448 return false; | |
449 } | |
450 | |
451 if (header->updating || !ValidateCounters(header)) { | |
452 // Last instance was not properly shutdown, or counters are out of sync. | |
453 if (!FixBlockFileHeader(file)) { | |
454 LOG(ERROR) << "Unable to fix block file " << name.value(); | |
455 return false; | |
456 } | |
457 } | |
458 | |
459 if (static_cast<int>(file_len) < | |
460 header->max_entries * header->entry_size + kBlockHeaderSize) { | |
461 LOG(ERROR) << "File too small " << name.value(); | |
462 return false; | |
463 } | |
464 | |
465 if (index == 0) { | |
466 // Load the links file into memory with a single read. | |
467 scoped_ptr<char[]> buf(new char[file_len]); | |
468 if (!file->Read(buf.get(), file_len, 0)) | |
469 return false; | |
470 } | |
471 | |
472 ScopedFlush flush(file); | |
473 DCHECK(!block_files_[index]); | |
474 file.swap(&block_files_[index]); | |
475 return true; | |
476 } | |
477 | |
478 bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) { | 206 bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) { |
479 if (kMaxBlocks == header->max_entries) | 207 if (kMaxBlocks == header->max_entries) |
480 return false; | 208 return false; |
481 | 209 |
482 ScopedFlush flush(file); | 210 ScopedFlush flush(file); |
483 DCHECK(!header->empty[3]); | 211 DCHECK(!header->empty[3]); |
484 int new_size = header->max_entries + 1024; | 212 int new_size = header->max_entries + 1024; |
485 if (new_size > kMaxBlocks) | 213 if (new_size > kMaxBlocks) |
486 new_size = kMaxBlocks; | 214 new_size = kMaxBlocks; |
487 | 215 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
521 } | 249 } |
522 | 250 |
523 if (!GrowBlockFile(file, header)) | 251 if (!GrowBlockFile(file, header)) |
524 return NULL; | 252 return NULL; |
525 break; | 253 break; |
526 } | 254 } |
527 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start); | 255 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start); |
528 return file; | 256 return file; |
529 } | 257 } |
530 | 258 |
531 MappedFile* BlockFiles::NextFile(MappedFile* file) { | |
532 ScopedFlush flush(file); | |
533 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); | |
534 int new_file = header->next_file; | |
535 if (!new_file) { | |
536 // RANKINGS is not reported as a type for small entries, but we may be | |
537 // extending the rankings block file. | |
538 FileType type = Addr::RequiredFileType(header->entry_size); | |
539 if (header->entry_size == Addr::BlockSizeForFileType(RANKINGS)) | |
540 type = RANKINGS; | |
541 | |
542 new_file = CreateNextBlockFile(type); | |
543 if (!new_file) | |
544 return NULL; | |
545 | |
546 FileLock lock(header); | |
547 header->next_file = new_file; | |
548 } | |
549 | |
550 // Only the block_file argument is relevant for what we want. | |
551 Addr address(BLOCK_256, 1, new_file, 0); | |
552 return GetFile(address); | |
553 } | |
554 | |
555 int BlockFiles::CreateNextBlockFile(FileType block_type) { | |
556 for (int i = kFirstAdditionalBlockFile; i <= kMaxBlockFile; i++) { | |
557 if (CreateBlockFile(i, block_type, false)) | |
558 return i; | |
559 } | |
560 return 0; | |
561 } | |
562 | |
563 // We walk the list of files for this particular block type, deleting the ones | |
564 // that are empty. | |
565 bool BlockFiles::RemoveEmptyFile(FileType block_type) { | |
566 MappedFile* file = block_files_[block_type - 1]; | |
567 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); | |
568 | |
569 while (header->next_file) { | |
570 // Only the block_file argument is relevant for what we want. | |
571 Addr address(BLOCK_256, 1, header->next_file, 0); | |
572 MappedFile* next_file = GetFile(address); | |
573 if (!next_file) | |
574 return false; | |
575 | |
576 BlockFileHeader* next_header = | |
577 reinterpret_cast<BlockFileHeader*>(next_file->buffer()); | |
578 if (!next_header->num_entries) { | |
579 DCHECK_EQ(next_header->entry_size, header->entry_size); | |
580 // Delete next_file and remove it from the chain. | |
581 int file_index = header->next_file; | |
582 header->next_file = next_header->next_file; | |
583 DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index)); | |
584 file->Flush(); | |
585 | |
586 // We get a new handle to the file and release the old one so that the | |
587 // file gets unmmaped... so we can delete it. | |
588 base::FilePath name = Name(file_index); | |
589 scoped_refptr<File> this_file(new File(false)); | |
590 this_file->Init(name); | |
591 block_files_[file_index]->Release(); | |
592 block_files_[file_index] = NULL; | |
593 | |
594 int failure = DeleteCacheFile(name) ? 0 : 1; | |
595 UMA_HISTOGRAM_COUNTS("DiskCache.DeleteFailed2", failure); | |
596 if (failure) | |
597 LOG(ERROR) << "Failed to delete " << name.value() << " from the cache."; | |
598 continue; | |
599 } | |
600 | |
601 header = next_header; | |
602 file = next_file; | |
603 } | |
604 return true; | |
605 } | |
606 | |
607 // Note that we expect to be called outside of a FileLock... however, we cannot | 259 // Note that we expect to be called outside of a FileLock... however, we cannot |
608 // DCHECK on header->updating because we may be fixing a crash. | 260 // DCHECK on header->updating because we may be fixing a crash. |
609 bool BlockFiles::FixBlockFileHeader(MappedFile* file) { | 261 bool BlockFiles::FixBlockFileHeader(MappedFile* file) { |
610 ScopedFlush flush(file); | 262 ScopedFlush flush(file); |
611 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); | 263 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); |
612 int file_size = static_cast<int>(file->GetLength()); | 264 int file_size = static_cast<int>(file->GetLength()); |
613 if (file_size < static_cast<int>(sizeof(*header))) | 265 if (file_size < static_cast<int>(sizeof(*header))) |
614 return false; // file_size > 2GB is also an error. | 266 return false; // file_size > 2GB is also an error. |
615 | 267 |
616 const int kMinBlockSize = 36; | 268 const int kMinBlockSize = 36; |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
670 *used_count += used; | 322 *used_count += used; |
671 | 323 |
672 if (!header->next_file) | 324 if (!header->next_file) |
673 break; | 325 break; |
674 index = header->next_file; | 326 index = header->next_file; |
675 } | 327 } |
676 if (max_blocks) | 328 if (max_blocks) |
677 *load = *used_count * 100 / max_blocks; | 329 *load = *used_count * 100 / max_blocks; |
678 } | 330 } |
679 | 331 |
680 base::FilePath BlockFiles::Name(int index) { | |
681 // The file format allows for 256 files. | |
682 DCHECK(index < 256 || index >= 0); | |
683 std::string tmp = base::StringPrintf("%s%d", kBlockName, index); | |
684 return path_.AppendASCII(tmp); | |
685 } | |
686 | |
687 } // namespace disk_cache | 332 } // namespace disk_cache |
OLD | NEW |