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/file_util.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 disk_cache { 21 22 BlockFiles::BlockFiles(const base::FilePath& path) 23 : init_(false), zero_buffer_(NULL), path_(path) { 24 } 25 26 BlockFiles::~BlockFiles() { 27 if (zero_buffer_) 28 delete[] zero_buffer_; 29 CloseFiles(); 30 } 31 32 bool BlockFiles::Init(bool create_files) { 33 DCHECK(!init_); 34 if (init_) 35 return false; 36 37 thread_checker_.reset(new base::ThreadChecker); 38 39 block_files_.resize(kFirstAdditionalBlockFile); 40 for (int i = 0; i < kFirstAdditionalBlockFile; i++) { 41 if (create_files) 42 if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true)) 43 return false; 44 45 if (!OpenBlockFile(i)) 46 return false; 47 48 // Walk this chain of files removing empty ones. 49 if (!RemoveEmptyFile(static_cast<FileType>(i + 1))) 50 return false; 51 } 52 53 init_ = true; 54 return true; 55 } 56 57 bool BlockFiles::CreateBlock(FileType block_type, int block_count, 58 Addr* block_address) { 59 DCHECK(thread_checker_->CalledOnValidThread()); 60 if (block_type < RANKINGS || block_type > BLOCK_4K || 61 block_count < 1 || block_count > 4) 62 return false; 63 if (!init_) 64 return false; 65 66 MappedFile* file = FileForNewBlock(block_type, block_count); 67 if (!file) 68 return false; 69 70 ScopedFlush flush(file); 71 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); 72 73 int target_size = 0; 74 for (int i = block_count; i <= 4; i++) { 75 if (header->empty[i - 1]) { 76 target_size = i; 77 break; 78 } 79 } 80 81 DCHECK(target_size); 82 int index; 83 if (!CreateMapBlock(target_size, block_count, header, &index)) 84 return false; 85 86 Addr address(block_type, block_count, header->this_file, index); 87 block_address->set_value(address.value()); 88 Trace("CreateBlock 0x%x", address.value()); 89 return true; 90 } 91 92 void BlockFiles::DeleteBlock(Addr address, bool deep) { 93 DCHECK(thread_checker_->CalledOnValidThread()); 94 if (!address.is_initialized() || address.is_separate_file()) 95 return; 96 97 if (!zero_buffer_) { 98 zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]; 99 memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4); 100 } 101 MappedFile* file = GetFile(address); 102 if (!file) 103 return; 104 105 Trace("DeleteBlock 0x%x", address.value()); 106 107 size_t size = address.BlockSize() * address.num_blocks(); 108 size_t offset = address.start_block() * address.BlockSize() + 109 kBlockHeaderSize; 110 if (deep) 111 file->Write(zero_buffer_, size, offset); 112 113 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); 114 DeleteMapBlock(address.start_block(), address.num_blocks(), header); 115 file->Flush(); 116 117 if (!header->num_entries) { 118 // This file is now empty. Let's try to delete it. 119 FileType type = Addr::RequiredFileType(header->entry_size); 120 if (Addr::BlockSizeForFileType(RANKINGS) == header->entry_size) 121 type = RANKINGS; 122 RemoveEmptyFile(type); // Ignore failures. 123 } 124 } 125 126 void BlockFiles::CloseFiles() { 127 if (init_) { 128 DCHECK(thread_checker_->CalledOnValidThread()); 129 } 130 init_ = false; 131 for (unsigned int i = 0; i < block_files_.size(); i++) { 132 if (block_files_[i]) { 133 block_files_[i]->Release(); 134 block_files_[i] = NULL; 135 } 136 } 137 block_files_.clear(); 138 } 139 140 void BlockFiles::ReportStats() { 141 DCHECK(thread_checker_->CalledOnValidThread()); 142 int used_blocks[kFirstAdditionalBlockFile]; 143 int load[kFirstAdditionalBlockFile]; 144 for (int i = 0; i < kFirstAdditionalBlockFile; i++) { 145 GetFileStats(i, &used_blocks[i], &load[i]); 146 } 147 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks[0]); 148 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks[1]); 149 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks[2]); 150 UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks[3]); 151 152 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load[0], 101); 153 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load[1], 101); 154 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load[2], 101); 155 UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101); 156 } 157 158 bool BlockFiles::IsValid(Addr address) { 159 #ifdef NDEBUG 160 return true; 161 #else 162 if (!address.is_initialized() || address.is_separate_file()) 163 return false; 164 165 MappedFile* file = GetFile(address); 166 if (!file) 167 return false; 168 169 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); 170 bool rv = UsedMapBlock(address.start_block(), address.num_blocks(), header); 171 DCHECK(rv); 172 173 static bool read_contents = false; 174 if (read_contents) { 175 scoped_ptr<char[]> buffer; 176 buffer.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]); 177 size_t size = address.BlockSize() * address.num_blocks(); 178 size_t offset = address.start_block() * address.BlockSize() + 179 kBlockHeaderSize; 180 bool ok = file->Read(buffer.get(), size, offset); 181 DCHECK(ok); 182 } 183 184 return rv; 185 #endif 186 } 187 188 MappedFile* BlockFiles::GetFile(Addr address) { 189 DCHECK(thread_checker_->CalledOnValidThread()); 190 DCHECK(block_files_.size() >= 4); 191 DCHECK(address.is_block_file() || !address.is_initialized()); 192 if (!address.is_initialized()) 193 return NULL; 194 195 int file_index = address.FileNumber(); 196 if (static_cast<unsigned int>(file_index) >= block_files_.size() || 197 !block_files_[file_index]) { 198 // We need to open the file 199 if (!OpenBlockFile(file_index)) 200 return NULL; 201 } 202 DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index)); 203 return block_files_[file_index]; 204 } 205 206 bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) { 207 if (kMaxBlocks == header->max_entries) 208 return false; 209 210 ScopedFlush flush(file); 211 DCHECK(!header->empty[3]); 212 int new_size = header->max_entries + 1024; 213 if (new_size > kMaxBlocks) 214 new_size = kMaxBlocks; 215 216 int new_size_bytes = new_size * header->entry_size + sizeof(*header); 217 218 if (!file->SetLength(new_size_bytes)) { 219 // Most likely we are trying to truncate the file, so the header is wrong. 220 if (header->updating < 10 && !FixBlockFileHeader(file)) { 221 // If we can't fix the file increase the lock guard so we'll pick it on 222 // the next start and replace it. 223 header->updating = 100; 224 return false; 225 } 226 return (header->max_entries >= new_size); 227 } 228 229 FileLock lock(header); 230 header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries 231 header->max_entries = new_size; 232 233 return true; 234 } 235 236 MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) { 237 COMPILE_ASSERT(RANKINGS == 1, invalid_file_type); 238 MappedFile* file = block_files_[block_type - 1]; 239 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); 240 241 TimeTicks start = TimeTicks::Now(); 242 while (NeedToGrowBlockFile(header, block_count)) { 243 if (kMaxBlocks == header->max_entries) { 244 file = NextFile(file); 245 if (!file) 246 return NULL; 247 header = reinterpret_cast<BlockFileHeader*>(file->buffer()); 248 continue; 249 } 250 251 if (!GrowBlockFile(file, header)) 252 return NULL; 253 break; 254 } 255 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start); 256 return file; 257 } 258 259 // Note that we expect to be called outside of a FileLock... however, we cannot 260 // DCHECK on header->updating because we may be fixing a crash. 261 bool BlockFiles::FixBlockFileHeader(MappedFile* file) { 262 ScopedFlush flush(file); 263 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); 264 int file_size = static_cast<int>(file->GetLength()); 265 if (file_size < static_cast<int>(sizeof(*header))) 266 return false; // file_size > 2GB is also an error. 267 268 const int kMinBlockSize = 36; 269 const int kMaxBlockSize = 4096; 270 if (header->entry_size < kMinBlockSize || 271 header->entry_size > kMaxBlockSize || header->num_entries < 0) 272 return false; 273 274 // Make sure that we survive crashes. 275 header->updating = 1; 276 int expected = header->entry_size * header->max_entries + sizeof(*header); 277 if (file_size != expected) { 278 int max_expected = header->entry_size * kMaxBlocks + sizeof(*header); 279 if (file_size < expected || header->empty[3] || file_size > max_expected) { 280 NOTREACHED(); 281 LOG(ERROR) << "Unexpected file size"; 282 return false; 283 } 284 // We were in the middle of growing the file. 285 int num_entries = (file_size - sizeof(*header)) / header->entry_size; 286 header->max_entries = num_entries; 287 } 288 289 FixAllocationCounters(header); 290 int empty_blocks = EmptyBlocks(header); 291 if (empty_blocks + header->num_entries > header->max_entries) 292 header->num_entries = header->max_entries - empty_blocks; 293 294 if (!ValidateCounters(header)) 295 return false; 296 297 header->updating = 0; 298 return true; 299 } 300 301 // We are interested in the total number of blocks used by this file type, and 302 // the max number of blocks that we can store (reported as the percentage of 303 // used blocks). In order to find out the number of used blocks, we have to 304 // substract the empty blocks from the total blocks for each file in the chain. 305 void BlockFiles::GetFileStats(int index, int* used_count, int* load) { 306 int max_blocks = 0; 307 *used_count = 0; 308 *load = 0; 309 for (;;) { 310 if (!block_files_[index] && !OpenBlockFile(index)) 311 return; 312 313 BlockFileHeader* header = 314 reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer()); 315 316 max_blocks += header->max_entries; 317 int used = header->max_entries; 318 for (int i = 0; i < 4; i++) { 319 used -= header->empty[i] * (i + 1); 320 DCHECK_GE(used, 0); 321 } 322 *used_count += used; 323 324 if (!header->next_file) 325 break; 326 index = header->next_file; 327 } 328 if (max_blocks) 329 *load = *used_count * 100 / max_blocks; 330 } 331 332 } // namespace disk_cache 333