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
17using base::TimeTicks;
18
19namespace {
20
21const 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).
25const 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.
29inline int GetMapBlockType(uint8 value) {
30  value &= 0xf;
31  return s_types[value];
32}
33
34void 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.
39bool 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.
88void 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().
128bool 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.
147void 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.
166bool 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
187namespace disk_cache {
188
189BlockFiles::BlockFiles(const FilePath& path)
190    : init_(false), zero_buffer_(NULL), path_(path) {
191}
192
193BlockFiles::~BlockFiles() {
194  if (zero_buffer_)
195    delete[] zero_buffer_;
196  CloseFiles();
197}
198
199bool 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
223MappedFile* 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
241bool 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
275void 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
308void 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
322void 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
340bool 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
370bool 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
389bool 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
440bool 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
469MappedFile* 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
492MappedFile* 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
515int 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.
525void 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
565bool 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.
593void 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
620FilePath 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