root/net/disk_cache/blockfile/index_table_v3.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetCellLocation
  2. GetCellSmallTableLocation
  3. GetCellId
  4. GetCellSmallTableId
  5. GetCellTimestamp
  6. GetCellReuse
  7. GetCellState
  8. GetCellGroup
  9. GetCellSum
  10. SetCellLocation
  11. SetCellSmallTableLocation
  12. SetCellId
  13. SetCellSmallTableId
  14. SetCellTimestamp
  15. SetCellReuse
  16. SetCellState
  17. SetCellGroup
  18. SetCellSum
  19. CalculateCellSum
  20. SanityCheck
  21. FileNumberFromLocation
  22. StartBlockFromLocation
  23. IsValidAddress
  24. IsNormalState
  25. GetNextBucket
  26. UpdateIterator
  27. InitIterator
  28. IsValid
  29. GetAddress
  30. GetState
  31. GetGroup
  32. GetReuse
  33. GetTimestamp
  34. SetState
  35. SetGroup
  36. SetReuse
  37. SetTimestamp
  38. GetEntryCellForTest
  39. SerializaForTest
  40. small_table_
  41. small_table_
  42. small_table_
  43. FixSum
  44. GetLocation
  45. RecomputeHash
  46. Serialize
  47. current
  48. small_table_
  49. Init
  50. Shutdown
  51. LookupEntries
  52. CreateEntryCell
  53. FindEntryCell
  54. CalculateTimestamp
  55. TimeFromTimestamp
  56. SetSate
  57. UpdateTime
  58. Save
  59. GetOldest
  60. GetNextCells
  61. OnBackupTimer
  62. FindEntryCellImpl
  63. CheckState
  64. Write
  65. NewExtraBucket
  66. WalkTables
  67. UpdateFromBucket
  68. MoveCells
  69. MoveSingleCell
  70. HandleMisplacedCell
  71. CheckBucketList
  72. GetLocation
  73. GetHashValue
  74. GetFullHash
  75. IsHashMatch
  76. MisplacedHash

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/disk_cache/blockfile/index_table_v3.h"

#include <algorithm>
#include <set>
#include <utility>

#include "base/bits.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"

using base::Time;
using base::TimeDelta;
using disk_cache::CellInfo;
using disk_cache::CellList;
using disk_cache::IndexCell;
using disk_cache::IndexIterator;

namespace {

// The following constants describe the bitfields of an IndexCell so they are
// implicitly synchronized with the descrption of IndexCell on file_format_v3.h.
const uint64 kCellLocationMask = (1 << 22) - 1;
const uint64 kCellIdMask = (1 << 18) - 1;
const uint64 kCellTimestampMask = (1 << 20) - 1;
const uint64 kCellReuseMask = (1 << 4) - 1;
const uint8 kCellStateMask = (1 << 3) - 1;
const uint8 kCellGroupMask = (1 << 3) - 1;
const uint8 kCellSumMask = (1 << 2) - 1;

const uint64 kCellSmallTableLocationMask = (1 << 16) - 1;
const uint64 kCellSmallTableIdMask = (1 << 24) - 1;

const int kCellIdOffset = 22;
const int kCellTimestampOffset = 40;
const int kCellReuseOffset = 60;
const int kCellGroupOffset = 3;
const int kCellSumOffset = 6;

const int kCellSmallTableIdOffset = 16;

// The number of bits that a hash has to be shifted to grab the part that
// defines the cell id.
const int kHashShift = 14;
const int kSmallTableHashShift = 8;

// Unfortunately we have to break the abstaction a little here: the file number
// where entries are stored is outside of the control of this code, and it is
// usually part of the stored address. However, for small tables we only store
// 16 bits of the address so the file number is never stored on a cell. We have
// to infere the file number from the type of entry (normal vs evicted), and
// the knowledge that given that the table will not keep more than 64k entries,
// a single file of each type is enough.
const int kEntriesFile = disk_cache::BLOCK_ENTRIES - 1;
const int kEvictedEntriesFile = disk_cache::BLOCK_EVICTED - 1;
const int kMaxLocation = 1 << 22;
const int kMinFileNumber = 1 << 16;

uint32 GetCellLocation(const IndexCell& cell) {
  return cell.first_part & kCellLocationMask;
}

uint32 GetCellSmallTableLocation(const IndexCell& cell) {
  return cell.first_part & kCellSmallTableLocationMask;
}

uint32 GetCellId(const IndexCell& cell) {
  return (cell.first_part >> kCellIdOffset) & kCellIdMask;
}

uint32 GetCellSmallTableId(const IndexCell& cell) {
  return (cell.first_part >> kCellSmallTableIdOffset) &
         kCellSmallTableIdMask;
}

int GetCellTimestamp(const IndexCell& cell) {
  return (cell.first_part >> kCellTimestampOffset) & kCellTimestampMask;
}

int GetCellReuse(const IndexCell& cell) {
  return (cell.first_part >> kCellReuseOffset) & kCellReuseMask;
}

int GetCellState(const IndexCell& cell) {
  return cell.last_part & kCellStateMask;
}

int GetCellGroup(const IndexCell& cell) {
  return (cell.last_part >> kCellGroupOffset) & kCellGroupMask;
}

int GetCellSum(const IndexCell& cell) {
  return (cell.last_part >> kCellSumOffset) & kCellSumMask;
}

void SetCellLocation(IndexCell* cell, uint32 address) {
  DCHECK_LE(address, static_cast<uint32>(kCellLocationMask));
  cell->first_part &= ~kCellLocationMask;
  cell->first_part |= address;
}

void SetCellSmallTableLocation(IndexCell* cell, uint32 address) {
  DCHECK_LE(address, static_cast<uint32>(kCellSmallTableLocationMask));
  cell->first_part &= ~kCellSmallTableLocationMask;
  cell->first_part |= address;
}

void SetCellId(IndexCell* cell, uint32 hash) {
  DCHECK_LE(hash, static_cast<uint32>(kCellIdMask));
  cell->first_part &= ~(kCellIdMask << kCellIdOffset);
  cell->first_part |= static_cast<int64>(hash) << kCellIdOffset;
}

void SetCellSmallTableId(IndexCell* cell, uint32 hash) {
  DCHECK_LE(hash, static_cast<uint32>(kCellSmallTableIdMask));
  cell->first_part &= ~(kCellSmallTableIdMask << kCellSmallTableIdOffset);
  cell->first_part |= static_cast<int64>(hash) << kCellSmallTableIdOffset;
}

void SetCellTimestamp(IndexCell* cell, int timestamp) {
  DCHECK_LT(timestamp, 1 << 20);
  DCHECK_GE(timestamp, 0);
  cell->first_part &= ~(kCellTimestampMask << kCellTimestampOffset);
  cell->first_part |= static_cast<int64>(timestamp) << kCellTimestampOffset;
}

void SetCellReuse(IndexCell* cell, int count) {
  DCHECK_LT(count, 16);
  DCHECK_GE(count, 0);
  cell->first_part &= ~(kCellReuseMask << kCellReuseOffset);
  cell->first_part |= static_cast<int64>(count) << kCellReuseOffset;
}

void SetCellState(IndexCell* cell, disk_cache::EntryState state) {
  cell->last_part &= ~kCellStateMask;
  cell->last_part |= state;
}

void SetCellGroup(IndexCell* cell, disk_cache::EntryGroup group) {
  cell->last_part &= ~(kCellGroupMask << kCellGroupOffset);
  cell->last_part |= group << kCellGroupOffset;
}

void SetCellSum(IndexCell* cell, int sum) {
  DCHECK_LT(sum, 4);
  DCHECK_GE(sum, 0);
  cell->last_part &= ~(kCellSumMask << kCellSumOffset);
  cell->last_part |= sum << kCellSumOffset;
}

// This is a very particular way to calculate the sum, so it will not match if
// compared a gainst a pure 2 bit, modulo 2 sum.
int CalculateCellSum(const IndexCell& cell) {
  uint32* words = bit_cast<uint32*>(&cell);
  uint8* bytes = bit_cast<uint8*>(&cell);
  uint32 result = words[0] + words[1];
  result += result >> 16;
  result += (result >> 8) + (bytes[8] & 0x3f);
  result += result >> 4;
  result += result >> 2;
  return result & 3;
}

bool SanityCheck(const IndexCell& cell) {
  if (GetCellSum(cell) != CalculateCellSum(cell))
    return false;

  if (GetCellState(cell) > disk_cache::ENTRY_USED ||
      GetCellGroup(cell) == disk_cache::ENTRY_RESERVED ||
      GetCellGroup(cell) > disk_cache::ENTRY_EVICTED) {
    return false;
  }

  return true;
}

int FileNumberFromLocation(int location) {
  return location / kMinFileNumber;
}

int StartBlockFromLocation(int location) {
  return location % kMinFileNumber;
}

bool IsValidAddress(disk_cache::Addr address) {
  if (!address.is_initialized() ||
      (address.file_type() != disk_cache::BLOCK_EVICTED &&
       address.file_type() != disk_cache::BLOCK_ENTRIES)) {
    return false;
  }

  return address.FileNumber() < FileNumberFromLocation(kMaxLocation);
}

bool IsNormalState(const IndexCell& cell) {
  disk_cache::EntryState state =
      static_cast<disk_cache::EntryState>(GetCellState(cell));
  DCHECK_NE(state, disk_cache::ENTRY_FREE);
  return state != disk_cache::ENTRY_DELETED &&
         state != disk_cache::ENTRY_FIXING;
}

inline int GetNextBucket(int min_bucket_num, int max_bucket_num,
                         disk_cache::IndexBucket* table,
                         disk_cache::IndexBucket** bucket) {
  if (!(*bucket)->next)
    return 0;

  int bucket_num = (*bucket)->next / disk_cache::kCellsPerBucket;
  if (bucket_num < min_bucket_num || bucket_num > max_bucket_num) {
    // The next bucket must fall within the extra table. Note that this is not
    // an uncommon path as growing the table may not cleanup the link from the
    // main table to the extra table, and that cleanup is performed here when
    // accessing that bucket for the first time. This behavior has to change if
    // the tables are ever shrinked.
    (*bucket)->next = 0;
    return 0;
  }
  *bucket = &table[bucket_num - min_bucket_num];
  return bucket_num;
}

// Updates the |iterator| with the current |cell|. This cell may cause all
// previous cells to be deleted (when a new target timestamp is found), the cell
// may be added to the list (if it matches the target timestamp), or may it be
// ignored.
void UpdateIterator(const disk_cache::EntryCell& cell,
                    int limit_time,
                    IndexIterator* iterator) {
  int time = cell.GetTimestamp();
  // Look for not interesting times.
  if (iterator->forward && time <= limit_time)
    return;
  if (!iterator->forward && time >= limit_time)
    return;

  if ((iterator->forward && time < iterator->timestamp) ||
      (!iterator->forward && time > iterator->timestamp)) {
    // This timestamp is better than the one we had.
    iterator->timestamp = time;
    iterator->cells.clear();
  }
  if (time == iterator->timestamp) {
    CellInfo cell_info = { cell.hash(), cell.GetAddress() };
    iterator->cells.push_back(cell_info);
  }
}

void InitIterator(IndexIterator* iterator) {
  iterator->cells.clear();
  iterator->timestamp = iterator->forward ? kint32max : 0;
}

}  // namespace

namespace disk_cache {

EntryCell::~EntryCell() {
}

bool EntryCell::IsValid() const {
  return GetCellLocation(cell_) != 0;
}

// This code has to map the cell address (up to 22 bits) to a general cache Addr
// (up to 24 bits of general addressing). It also set the implied file_number
// in the case of small tables. See also the comment by the definition of
// kEntriesFile.
Addr EntryCell::GetAddress() const {
  uint32 location = GetLocation();
  int file_number = FileNumberFromLocation(location);
  if (small_table_) {
    DCHECK_EQ(0, file_number);
    file_number = (GetGroup() == ENTRY_EVICTED) ? kEvictedEntriesFile :
                                                  kEntriesFile;
  }
  DCHECK_NE(0, file_number);
  FileType file_type = (GetGroup() == ENTRY_EVICTED) ? BLOCK_EVICTED :
                                                       BLOCK_ENTRIES;
  return Addr(file_type, 1, file_number, StartBlockFromLocation(location));
}

EntryState EntryCell::GetState() const {
  return static_cast<EntryState>(GetCellState(cell_));
}

EntryGroup EntryCell::GetGroup() const {
  return static_cast<EntryGroup>(GetCellGroup(cell_));
}

int EntryCell::GetReuse() const {
  return GetCellReuse(cell_);
}

int EntryCell::GetTimestamp() const {
  return GetCellTimestamp(cell_);
}

void EntryCell::SetState(EntryState state) {
  SetCellState(&cell_, state);
}

void EntryCell::SetGroup(EntryGroup group) {
  SetCellGroup(&cell_, group);
}

void EntryCell::SetReuse(int count) {
  SetCellReuse(&cell_, count);
}

void EntryCell::SetTimestamp(int timestamp) {
  SetCellTimestamp(&cell_, timestamp);
}

// Static.
EntryCell EntryCell::GetEntryCellForTest(int32 cell_num,
                                         uint32 hash,
                                         Addr address,
                                         IndexCell* cell,
                                         bool small_table) {
  if (cell) {
    EntryCell entry_cell(cell_num, hash, *cell, small_table);
    return entry_cell;
  }

  return EntryCell(cell_num, hash, address, small_table);
}

void EntryCell::SerializaForTest(IndexCell* destination) {
  FixSum();
  Serialize(destination);
}

EntryCell::EntryCell() : cell_num_(0), hash_(0), small_table_(false) {
  cell_.Clear();
}

EntryCell::EntryCell(int32 cell_num,
                     uint32 hash,
                     Addr address,
                     bool small_table)
    : cell_num_(cell_num),
      hash_(hash),
      small_table_(small_table) {
  DCHECK(IsValidAddress(address) || !address.value());

  cell_.Clear();
  SetCellState(&cell_, ENTRY_NEW);
  SetCellGroup(&cell_, ENTRY_NO_USE);
  if (small_table) {
    DCHECK(address.FileNumber() == kEntriesFile ||
           address.FileNumber() == kEvictedEntriesFile);
    SetCellSmallTableLocation(&cell_, address.start_block());
    SetCellSmallTableId(&cell_, hash >> kSmallTableHashShift);
  } else {
    uint32 location = address.FileNumber() << 16 | address.start_block();
    SetCellLocation(&cell_, location);
    SetCellId(&cell_, hash >> kHashShift);
  }
}

EntryCell::EntryCell(int32 cell_num,
                     uint32 hash,
                     const IndexCell& cell,
                     bool small_table)
    : cell_num_(cell_num),
      hash_(hash),
      cell_(cell),
      small_table_(small_table) {
}

void EntryCell::FixSum() {
  SetCellSum(&cell_, CalculateCellSum(cell_));
}

uint32 EntryCell::GetLocation() const {
  if (small_table_)
    return GetCellSmallTableLocation(cell_);

  return GetCellLocation(cell_);
}

uint32 EntryCell::RecomputeHash() {
  if (small_table_) {
    hash_ &= (1 << kSmallTableHashShift) - 1;
    hash_ |= GetCellSmallTableId(cell_) << kSmallTableHashShift;
    return hash_;
  }

  hash_ &= (1 << kHashShift) - 1;
  hash_ |= GetCellId(cell_) << kHashShift;
  return hash_;
}

void EntryCell::Serialize(IndexCell* destination) const {
  *destination = cell_;
}

EntrySet::EntrySet() : evicted_count(0), current(0) {
}

EntrySet::~EntrySet() {
}

IndexIterator::IndexIterator() {
}

IndexIterator::~IndexIterator() {
}

IndexTableInitData::IndexTableInitData() {
}

IndexTableInitData::~IndexTableInitData() {
}

// -----------------------------------------------------------------------

IndexTable::IndexTable(IndexTableBackend* backend)
    : backend_(backend),
      header_(NULL),
      main_table_(NULL),
      extra_table_(NULL),
      modified_(false),
      small_table_(false) {
}

IndexTable::~IndexTable() {
}

// For a general description of the index tables see:
// http://www.chromium.org/developers/design-documents/network-stack/disk-cache/disk-cache-v3#TOC-Index
//
// The index is split between two tables: the main_table_ and the extra_table_.
// The main table can grow only by doubling its number of cells, while the
// extra table can grow slowly, because it only contain cells that overflow
// from the main table. In order to locate a given cell, part of the hash is
// used directly as an index into the main table; once that bucket is located,
// all cells with that partial hash (i.e., belonging to that bucket) are
// inspected, and if present, the next bucket (located on the extra table) is
// then located. For more information on bucket chaining see:
// http://www.chromium.org/developers/design-documents/network-stack/disk-cache/disk-cache-v3#TOC-Buckets
//
// There are two cases when increasing the size:
//  - Doubling the size of the main table
//  - Adding more entries to the extra table
//
// For example, consider a 64k main table with 8k cells on the extra table (for
// a total of 72k cells). Init can be called to add another 8k cells at the end
// (grow to 80k cells). When the size of the extra table approaches 64k, Init
// can be called to double the main table (to 128k) and go back to a small extra
// table.
void IndexTable::Init(IndexTableInitData* params) {
  bool growing = header_ != NULL;
  scoped_ptr<IndexBucket[]> old_extra_table;
  header_ = &params->index_bitmap->header;

  if (params->main_table) {
    if (main_table_) {
      // This is doubling the size of main table.
      DCHECK_EQ(base::bits::Log2Floor(header_->table_len),
                base::bits::Log2Floor(backup_header_->table_len) + 1);
      int extra_size = (header()->max_bucket - mask_) * kCellsPerBucket;
      DCHECK_GE(extra_size, 0);

      // Doubling the size implies deleting the extra table and moving as many
      // cells as we can to the main table, so we first copy the old one. This
      // is not required when just growing the extra table because we don't
      // move any cell in that case.
      old_extra_table.reset(new IndexBucket[extra_size]);
      memcpy(old_extra_table.get(), extra_table_,
             extra_size * sizeof(IndexBucket));
      memset(params->extra_table, 0, extra_size * sizeof(IndexBucket));
    }
    main_table_ = params->main_table;
  }
  DCHECK(main_table_);
  extra_table_ = params->extra_table;

  // extra_bits_ is really measured against table-size specific values.
  const int kMaxAbsoluteExtraBits = 12;  // From smallest to largest table.
  const int kMaxExtraBitsSmallTable = 6;  // From smallest to 64K table.

  extra_bits_ = base::bits::Log2Floor(header_->table_len) -
                base::bits::Log2Floor(kBaseTableLen);
  DCHECK_GE(extra_bits_, 0);
  DCHECK_LT(extra_bits_, kMaxAbsoluteExtraBits);

  // Note that following the previous code the constants could be derived as
  // kMaxAbsoluteExtraBits = base::bits::Log2Floor(max table len) -
  //                         base::bits::Log2Floor(kBaseTableLen);
  //                       = 22 - base::bits::Log2Floor(1024) = 22 - 10;
  // kMaxExtraBitsSmallTable = base::bits::Log2Floor(max 16 bit table) - 10.

  mask_ = ((kBaseTableLen / kCellsPerBucket) << extra_bits_) - 1;
  small_table_ = extra_bits_ < kMaxExtraBitsSmallTable;
  if (!small_table_)
    extra_bits_ -= kMaxExtraBitsSmallTable;

  // table_len keeps the max number of cells stored by the index. We need a
  // bitmap with 1 bit per cell, and that bitmap has num_words 32-bit words.
  int num_words = (header_->table_len + 31) / 32;

  if (old_extra_table) {
    // All the cells from the extra table are moving to the new tables so before
    // creating the bitmaps, clear the part of the bitmap referring to the extra
    // table.
    int old_main_table_bit_words = ((mask_ >> 1) + 1) * kCellsPerBucket / 32;
    DCHECK_GT(num_words, old_main_table_bit_words);
    memset(params->index_bitmap->bitmap + old_main_table_bit_words, 0,
           (num_words - old_main_table_bit_words) * sizeof(int32));

    DCHECK(growing);
    int old_num_words = (backup_header_.get()->table_len + 31) / 32;
    DCHECK_GT(old_num_words, old_main_table_bit_words);
    memset(backup_bitmap_storage_.get() + old_main_table_bit_words, 0,
           (old_num_words - old_main_table_bit_words) * sizeof(int32));
  }
  bitmap_.reset(new Bitmap(params->index_bitmap->bitmap, header_->table_len,
                           num_words));

  if (growing) {
    int old_num_words = (backup_header_.get()->table_len + 31) / 32;
    DCHECK_GE(num_words, old_num_words);
    scoped_ptr<uint32[]> storage(new uint32[num_words]);
    memcpy(storage.get(), backup_bitmap_storage_.get(),
           old_num_words * sizeof(int32));
    memset(storage.get() + old_num_words, 0,
           (num_words - old_num_words) * sizeof(int32));

    backup_bitmap_storage_.swap(storage);
    backup_header_->table_len = header_->table_len;
  } else {
    backup_bitmap_storage_.reset(params->backup_bitmap.release());
    backup_header_.reset(params->backup_header.release());
  }

  num_words = (backup_header_->table_len + 31) / 32;
  backup_bitmap_.reset(new Bitmap(backup_bitmap_storage_.get(),
                                  backup_header_->table_len, num_words));
  if (old_extra_table)
    MoveCells(old_extra_table.get());

  if (small_table_)
    DCHECK(header_->flags & SMALL_CACHE);

  // All tables and backups are needed for operation.
  DCHECK(main_table_);
  DCHECK(extra_table_);
  DCHECK(bitmap_.get());
}

void IndexTable::Shutdown() {
  header_ = NULL;
  main_table_ = NULL;
  extra_table_ = NULL;
  bitmap_.reset();
  backup_bitmap_.reset();
  backup_header_.reset();
  backup_bitmap_storage_.reset();
  modified_ = false;
}

// The general method for locating cells is to:
// 1. Get the first bucket. This usually means directly indexing the table (as
//     this method does), or iterating through all possible buckets.
// 2. Iterate through all the cells in that first bucket.
// 3. If there is a linked bucket, locate it directly in the extra table.
// 4. Go back to 2, as needed.
//
// One consequence of this pattern is that we never start looking at buckets in
// the extra table, unless we are following a link from the main table.
EntrySet IndexTable::LookupEntries(uint32 hash) {
  EntrySet entries;
  int bucket_num = static_cast<int>(hash & mask_);
  IndexBucket* bucket = &main_table_[bucket_num];
  do {
    for (int i = 0; i < kCellsPerBucket; i++) {
      IndexCell* current_cell = &bucket->cells[i];
      if (!GetLocation(*current_cell))
        continue;
      if (!SanityCheck(*current_cell)) {
        NOTREACHED();
        int cell_num = bucket_num * kCellsPerBucket + i;
        current_cell->Clear();
        bitmap_->Set(cell_num, false);
        backup_bitmap_->Set(cell_num, false);
        modified_ = true;
        continue;
      }
      int cell_num = bucket_num * kCellsPerBucket + i;
      if (MisplacedHash(*current_cell, hash)) {
        HandleMisplacedCell(current_cell, cell_num, hash & mask_);
      } else if (IsHashMatch(*current_cell, hash)) {
        EntryCell entry_cell(cell_num, hash, *current_cell, small_table_);
        CheckState(entry_cell);
        if (entry_cell.GetState() != ENTRY_DELETED) {
          entries.cells.push_back(entry_cell);
          if (entry_cell.GetGroup() == ENTRY_EVICTED)
            entries.evicted_count++;
        }
      }
    }
    bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
                               &bucket);
  } while (bucket_num);
  return entries;
}

EntryCell IndexTable::CreateEntryCell(uint32 hash, Addr address) {
  DCHECK(IsValidAddress(address));
  DCHECK(address.FileNumber() || address.start_block());

  int bucket_num = static_cast<int>(hash & mask_);
  int cell_num = 0;
  IndexBucket* bucket = &main_table_[bucket_num];
  IndexCell* current_cell = NULL;
  bool found = false;
  do {
    for (int i = 0; i < kCellsPerBucket && !found; i++) {
      current_cell = &bucket->cells[i];
      if (!GetLocation(*current_cell)) {
        cell_num = bucket_num * kCellsPerBucket + i;
        found = true;
      }
    }
    if (found)
      break;
    bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
                               &bucket);
  } while (bucket_num);

  if (!found) {
    bucket_num = NewExtraBucket();
    if (bucket_num) {
      cell_num = bucket_num * kCellsPerBucket;
      bucket->next = cell_num;
      bucket = &extra_table_[bucket_num - (mask_ + 1)];
      bucket->hash = hash & mask_;
      found = true;
    } else {
      // address 0 is a reserved value, and the caller interprets it as invalid.
      address.set_value(0);
    }
  }

  EntryCell entry_cell(cell_num, hash, address, small_table_);
  if (address.file_type() == BLOCK_EVICTED)
    entry_cell.SetGroup(ENTRY_EVICTED);
  else
    entry_cell.SetGroup(ENTRY_NO_USE);
  Save(&entry_cell);

  if (found) {
    bitmap_->Set(cell_num, true);
    backup_bitmap_->Set(cell_num, true);
    header()->used_cells++;
    modified_ = true;
  }

  return entry_cell;
}

EntryCell IndexTable::FindEntryCell(uint32 hash, Addr address) {
  return FindEntryCellImpl(hash, address, false);
}

int IndexTable::CalculateTimestamp(Time time) {
  TimeDelta delta = time - Time::FromInternalValue(header_->base_time);
  return std::max(delta.InMinutes(), 0);
}

base::Time IndexTable::TimeFromTimestamp(int timestamp) {
  return Time::FromInternalValue(header_->base_time) +
         TimeDelta::FromMinutes(timestamp);
}

void IndexTable::SetSate(uint32 hash, Addr address, EntryState state) {
  EntryCell cell = FindEntryCellImpl(hash, address, state == ENTRY_FREE);
  if (!cell.IsValid()) {
    NOTREACHED();
    return;
  }

  EntryState old_state = cell.GetState();
  switch (state) {
    case ENTRY_FREE:
      DCHECK_EQ(old_state, ENTRY_DELETED);
      break;
    case ENTRY_NEW:
      DCHECK_EQ(old_state, ENTRY_FREE);
      break;
    case ENTRY_OPEN:
      DCHECK_EQ(old_state, ENTRY_USED);
      break;
    case ENTRY_MODIFIED:
      DCHECK_EQ(old_state, ENTRY_OPEN);
      break;
    case ENTRY_DELETED:
      DCHECK(old_state == ENTRY_NEW || old_state == ENTRY_OPEN ||
             old_state == ENTRY_MODIFIED);
      break;
    case ENTRY_USED:
      DCHECK(old_state == ENTRY_NEW || old_state == ENTRY_OPEN ||
             old_state == ENTRY_MODIFIED);
      break;
    case ENTRY_FIXING:
      break;
  };

  modified_ = true;
  if (state == ENTRY_DELETED) {
    bitmap_->Set(cell.cell_num(), false);
    backup_bitmap_->Set(cell.cell_num(), false);
  } else if (state == ENTRY_FREE) {
    cell.Clear();
    Write(cell);
    header()->used_cells--;
    return;
  }
  cell.SetState(state);

  Save(&cell);
}

void IndexTable::UpdateTime(uint32 hash, Addr address, base::Time current) {
  EntryCell cell = FindEntryCell(hash, address);
  if (!cell.IsValid())
    return;

  int minutes = CalculateTimestamp(current);

  // Keep about 3 months of headroom.
  const int kMaxTimestamp = (1 << 20) - 60 * 24 * 90;
  if (minutes > kMaxTimestamp) {
    // TODO(rvargas):
    // Update header->old_time and trigger a timer
    // Rebaseline timestamps and don't update sums
    // Start a timer (about 2 backups)
    // fix all ckecksums and trigger another timer
    // update header->old_time because rebaseline is done.
    minutes = std::min(minutes, (1 << 20) - 1);
  }

  cell.SetTimestamp(minutes);
  Save(&cell);
}

void IndexTable::Save(EntryCell* cell) {
  cell->FixSum();
  Write(*cell);
}

void IndexTable::GetOldest(IndexIterator* no_use,
                           IndexIterator* low_use,
                           IndexIterator* high_use) {
  no_use->forward = true;
  low_use->forward = true;
  high_use->forward = true;
  InitIterator(no_use);
  InitIterator(low_use);
  InitIterator(high_use);

  WalkTables(-1, no_use, low_use, high_use);
}

bool IndexTable::GetNextCells(IndexIterator* iterator) {
  int current_time = iterator->timestamp;
  InitIterator(iterator);

  WalkTables(current_time, iterator, iterator, iterator);
  return !iterator->cells.empty();
}

void IndexTable::OnBackupTimer() {
  if (!modified_)
    return;

  int num_words = (header_->table_len + 31) / 32;
  int num_bytes = num_words * 4 + static_cast<int>(sizeof(*header_));
  scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(num_bytes));
  memcpy(buffer->data(), header_, sizeof(*header_));
  memcpy(buffer->data() + sizeof(*header_), backup_bitmap_storage_.get(),
         num_words * 4);
  backend_->SaveIndex(buffer, num_bytes);
  modified_ = false;
}

// -----------------------------------------------------------------------

EntryCell IndexTable::FindEntryCellImpl(uint32 hash, Addr address,
                                        bool allow_deleted) {
  int bucket_num = static_cast<int>(hash & mask_);
  IndexBucket* bucket = &main_table_[bucket_num];
  do {
    for (int i = 0; i < kCellsPerBucket; i++) {
      IndexCell* current_cell = &bucket->cells[i];
      if (!GetLocation(*current_cell))
        continue;
      DCHECK(SanityCheck(*current_cell));
      if (IsHashMatch(*current_cell, hash)) {
        // We have a match.
        int cell_num = bucket_num * kCellsPerBucket + i;
        EntryCell entry_cell(cell_num, hash, *current_cell, small_table_);
        if (entry_cell.GetAddress() != address)
          continue;

        if (!allow_deleted && entry_cell.GetState() == ENTRY_DELETED)
          continue;

        return entry_cell;
      }
    }
    bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
                               &bucket);
  } while (bucket_num);
  return EntryCell();
}

void IndexTable::CheckState(const EntryCell& cell) {
  int current_state = cell.GetState();
  if (current_state != ENTRY_FIXING) {
    bool present = ((current_state & 3) != 0);  // Look at the last two bits.
    if (present != bitmap_->Get(cell.cell_num()) ||
        present != backup_bitmap_->Get(cell.cell_num())) {
      // There's a mismatch.
      if (current_state == ENTRY_DELETED) {
        // We were in the process of deleting this entry. Finish now.
        backend_->DeleteCell(cell);
      } else {
        current_state = ENTRY_FIXING;
        EntryCell bad_cell(cell);
        bad_cell.SetState(ENTRY_FIXING);
        Save(&bad_cell);
      }
    }
  }

  if (current_state == ENTRY_FIXING)
    backend_->FixCell(cell);
}

void IndexTable::Write(const EntryCell& cell) {
  IndexBucket* bucket = NULL;
  int bucket_num = cell.cell_num() / kCellsPerBucket;
  if (bucket_num < static_cast<int32>(mask_ + 1)) {
    bucket = &main_table_[bucket_num];
  } else {
    DCHECK_LE(bucket_num, header()->max_bucket);
    bucket = &extra_table_[bucket_num - (mask_ + 1)];
  }

  int cell_number = cell.cell_num() % kCellsPerBucket;
  if (GetLocation(bucket->cells[cell_number]) && cell.GetLocation()) {
    DCHECK_EQ(cell.GetLocation(),
              GetLocation(bucket->cells[cell_number]));
  }
  cell.Serialize(&bucket->cells[cell_number]);
}

int IndexTable::NewExtraBucket() {
  int safe_window = (header()->table_len < kNumExtraBlocks * 2) ?
                    kNumExtraBlocks / 4 : kNumExtraBlocks;
  if (header()->table_len - header()->max_bucket * kCellsPerBucket <
      safe_window) {
    backend_->GrowIndex();
  }

  if (header()->max_bucket * kCellsPerBucket ==
      header()->table_len - kCellsPerBucket) {
    return 0;
  }

  header()->max_bucket++;
  return header()->max_bucket;
}

void IndexTable::WalkTables(int limit_time,
                            IndexIterator* no_use,
                            IndexIterator* low_use,
                            IndexIterator* high_use) {
  header_->num_no_use_entries = 0;
  header_->num_low_use_entries = 0;
  header_->num_high_use_entries = 0;
  header_->num_evicted_entries = 0;

  for (int i = 0; i < static_cast<int32>(mask_ + 1); i++) {
    int bucket_num = i;
    IndexBucket* bucket = &main_table_[i];
    do {
      UpdateFromBucket(bucket, i, limit_time, no_use, low_use, high_use);

      bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
                                 &bucket);
    } while (bucket_num);
  }
  header_->num_entries = header_->num_no_use_entries +
                         header_->num_low_use_entries +
                         header_->num_high_use_entries +
                         header_->num_evicted_entries;
  modified_ = true;
}

void IndexTable::UpdateFromBucket(IndexBucket* bucket, int bucket_hash,
                                  int limit_time,
                                  IndexIterator* no_use,
                                  IndexIterator* low_use,
                                  IndexIterator* high_use) {
  for (int i = 0; i < kCellsPerBucket; i++) {
    IndexCell& current_cell = bucket->cells[i];
    if (!GetLocation(current_cell))
      continue;
    DCHECK(SanityCheck(current_cell));
    if (!IsNormalState(current_cell))
      continue;

    EntryCell entry_cell(0, GetFullHash(current_cell, bucket_hash),
                         current_cell, small_table_);
    switch (GetCellGroup(current_cell)) {
      case ENTRY_NO_USE:
        UpdateIterator(entry_cell, limit_time, no_use);
        header_->num_no_use_entries++;
        break;
      case ENTRY_LOW_USE:
        UpdateIterator(entry_cell, limit_time, low_use);
        header_->num_low_use_entries++;
        break;
      case ENTRY_HIGH_USE:
        UpdateIterator(entry_cell, limit_time, high_use);
        header_->num_high_use_entries++;
        break;
      case ENTRY_EVICTED:
        header_->num_evicted_entries++;
        break;
      default:
        NOTREACHED();
    }
  }
}

// This code is only called from Init() so the internal state of this object is
// in flux (this method is performing the last steps of re-initialization). As
// such, random methods are not supposed to work at this point, so whatever this
// method calls should be relatively well controlled and it may require some
// degree of "stable state faking".
void IndexTable::MoveCells(IndexBucket* old_extra_table) {
  int max_hash = (mask_ + 1) / 2;
  int max_bucket = header()->max_bucket;
  header()->max_bucket = mask_;
  int used_cells = header()->used_cells;

  // Consider a large cache: a cell stores the upper 18 bits of the hash
  // (h >> 14). If the table is say 8 times the original size (growing from 4x),
  // the bit that we are interested in would be the 3rd bit of the stored value,
  // in other words 'multiplier' >> 1.
  uint32 new_bit = (1 << extra_bits_) >> 1;

  scoped_ptr<IndexBucket[]> old_main_table;
  IndexBucket* source_table = main_table_;
  bool upgrade_format = !extra_bits_;
  if (upgrade_format) {
    // This method should deal with migrating a small table to a big one. Given
    // that the first thing to do is read the old table, set small_table_ for
    // the size of the old table. Now, when moving a cell, the result cannot be
    // placed in the old table or we will end up reading it again and attempting
    // to move it, so we have to copy the whole table at once.
    DCHECK(!small_table_);
    small_table_ = true;
    old_main_table.reset(new IndexBucket[max_hash]);
    memcpy(old_main_table.get(), main_table_, max_hash * sizeof(IndexBucket));
    memset(main_table_, 0, max_hash * sizeof(IndexBucket));
    source_table = old_main_table.get();
  }

  for (int i = 0; i < max_hash; i++) {
    int bucket_num = i;
    IndexBucket* bucket = &source_table[i];
    do {
      for (int j = 0; j < kCellsPerBucket; j++) {
        IndexCell& current_cell = bucket->cells[j];
        if (!GetLocation(current_cell))
          continue;
        DCHECK(SanityCheck(current_cell));
        if (bucket_num == i) {
          if (upgrade_format || (GetHashValue(current_cell) & new_bit)) {
            // Move this cell to the upper half of the table.
            MoveSingleCell(&current_cell, bucket_num * kCellsPerBucket + j, i,
                           true);
          }
        } else {
          // All cells on extra buckets have to move.
          MoveSingleCell(&current_cell, bucket_num * kCellsPerBucket + j, i,
                         true);
        }
      }

      // There is no need to clear the old bucket->next value because if falls
      // within the main table so it will be fixed when attempting to follow
      // the link.
      bucket_num = GetNextBucket(max_hash, max_bucket, old_extra_table,
                                 &bucket);
    } while (bucket_num);
  }

  DCHECK_EQ(header()->used_cells, used_cells);

  if (upgrade_format) {
    small_table_ = false;
    header()->flags &= ~SMALL_CACHE;
  }
}

void IndexTable::MoveSingleCell(IndexCell* current_cell, int cell_num,
                                int main_table_index, bool growing) {
  uint32 hash = GetFullHash(*current_cell, main_table_index);
  EntryCell old_cell(cell_num, hash, *current_cell, small_table_);

  // This method may be called when moving entries from a small table to a
  // normal table. In that case, the caller (MoveCells) has to read the old
  // table, so it needs small_table_ set to true, but this method needs to
  // write to the new table so small_table_ has to be set to false, and the
  // value restored to true before returning.
  bool upgrade_format = !extra_bits_ && growing;
  if (upgrade_format)
    small_table_ = false;
  EntryCell new_cell = CreateEntryCell(hash, old_cell.GetAddress());

  if (!new_cell.IsValid()) {
    // We'll deal with this entry later.
    if (upgrade_format)
      small_table_ = true;
    return;
  }

  new_cell.SetState(old_cell.GetState());
  new_cell.SetGroup(old_cell.GetGroup());
  new_cell.SetReuse(old_cell.GetReuse());
  new_cell.SetTimestamp(old_cell.GetTimestamp());
  Save(&new_cell);
  modified_ = true;
  if (upgrade_format)
    small_table_ = true;

  if (old_cell.GetState() == ENTRY_DELETED) {
    bitmap_->Set(new_cell.cell_num(), false);
    backup_bitmap_->Set(new_cell.cell_num(), false);
  }

  if (!growing || cell_num / kCellsPerBucket == main_table_index) {
    // Only delete entries that live on the main table.
    if (!upgrade_format) {
      old_cell.Clear();
      Write(old_cell);
    }

    if (cell_num != new_cell.cell_num()) {
      bitmap_->Set(old_cell.cell_num(), false);
      backup_bitmap_->Set(old_cell.cell_num(), false);
    }
  }
  header()->used_cells--;
}

void IndexTable::HandleMisplacedCell(IndexCell* current_cell, int cell_num,
                                     int main_table_index) {
  NOTREACHED();  // No unit tests yet.

  // The cell may be misplaced, or a duplicate cell exists with this data.
  uint32 hash = GetFullHash(*current_cell, main_table_index);
  MoveSingleCell(current_cell, cell_num, main_table_index, false);

  // Now look for a duplicate cell.
  CheckBucketList(hash & mask_);
}

void IndexTable::CheckBucketList(int bucket_num) {
  typedef std::pair<int, EntryGroup> AddressAndGroup;
  std::set<AddressAndGroup> entries;
  IndexBucket* bucket = &main_table_[bucket_num];
  int bucket_hash = bucket_num;
  do {
    for (int i = 0; i < kCellsPerBucket; i++) {
      IndexCell* current_cell = &bucket->cells[i];
      if (!GetLocation(*current_cell))
        continue;
      if (!SanityCheck(*current_cell)) {
        NOTREACHED();
        current_cell->Clear();
        continue;
      }
      int cell_num = bucket_num * kCellsPerBucket + i;
      EntryCell cell(cell_num, GetFullHash(*current_cell, bucket_hash),
                     *current_cell, small_table_);
      if (!entries.insert(std::make_pair(cell.GetAddress().value(),
                                         cell.GetGroup())).second) {
        current_cell->Clear();
        continue;
      }
      CheckState(cell);
    }

    bucket_num = GetNextBucket(mask_ + 1, header()->max_bucket, extra_table_,
                              &bucket);
  } while (bucket_num);
}

uint32 IndexTable::GetLocation(const IndexCell& cell) {
  if (small_table_)
    return GetCellSmallTableLocation(cell);

  return GetCellLocation(cell);
}

uint32 IndexTable::GetHashValue(const IndexCell& cell) {
  if (small_table_)
    return GetCellSmallTableId(cell);

  return GetCellId(cell);
}

uint32 IndexTable::GetFullHash(const IndexCell& cell, uint32 lower_part) {
  // It is OK for the high order bits of lower_part to overlap with the stored
  // part of the hash.
  if (small_table_)
    return (GetCellSmallTableId(cell) << kSmallTableHashShift) | lower_part;

  return (GetCellId(cell) << kHashShift) | lower_part;
}

// All the bits stored in the cell should match the provided hash.
bool IndexTable::IsHashMatch(const IndexCell& cell, uint32 hash) {
  hash = small_table_ ? hash >> kSmallTableHashShift : hash >> kHashShift;
  return GetHashValue(cell) == hash;
}

bool IndexTable::MisplacedHash(const IndexCell& cell, uint32 hash) {
  if (!extra_bits_)
    return false;

  uint32 mask = (1 << extra_bits_) - 1;
  hash = small_table_ ? hash >> kSmallTableHashShift : hash >> kHashShift;
  return (GetHashValue(cell) & mask) != (hash & mask);
}

}  // namespace disk_cache

/* [<][>][^][v][top][bottom][index][help] */