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