summaryrefslogtreecommitdiffstats
path: root/net/disk_cache/block_files.cc
diff options
context:
space:
mode:
Diffstat (limited to 'net/disk_cache/block_files.cc')
-rw-r--r--net/disk_cache/block_files.cc441
1 files changed, 441 insertions, 0 deletions
diff --git a/net/disk_cache/block_files.cc b/net/disk_cache/block_files.cc
new file mode 100644
index 0000000..e985765
--- /dev/null
+++ b/net/disk_cache/block_files.cc
@@ -0,0 +1,441 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/disk_cache/block_files.h"
+
+#include "base/scoped_handle.h"
+#include "base/string_util.h"
+#include "net/disk_cache/file_lock.h"
+
+namespace {
+
+const wchar_t* kBlockName = L"\\data_";
+
+// This array is used to perform a fast lookup of the nibble bit pattern to the
+// type of entry that can be stored there (number of consecutive blocks).
+const char s_types[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
+
+// Returns the type of block (number of consecutive blocks that can be stored)
+// for a given nibble of the bitmap.
+inline int GetMapBlockType(uint8 value) {
+ value &= 0xf;
+ return s_types[value];
+}
+
+void FixAllocationCounters(disk_cache::BlockFileHeader* header);
+
+// Creates a new entry on the allocation map, updating the apropriate counters.
+// target is the type of block to use (number of empty blocks), and size is the
+// actual number of blocks to use.
+bool CreateMapBlock(int target, int size, disk_cache::BlockFileHeader* header,
+ int* index) {
+ if (target <= 0 || target > disk_cache::kMaxNumBlocks ||
+ size <= 0 || size > disk_cache::kMaxNumBlocks) {
+ NOTREACHED();
+ return false;
+ }
+
+ // We are going to process the map on 32-block chunks (32 bits), and on every
+ // chunk, iterate through the 8 nibbles where the new block can be located.
+ int current = header->hints[target - 1];
+ for (int i = 0; i < header->max_entries / 32; i++, current++) {
+ if (current == header->max_entries / 32)
+ current = 0;
+ uint32 map_block = header->allocation_map[current];
+
+ for (int j = 0; j < 8; j++, map_block >>= 4) {
+ if (GetMapBlockType(map_block) != target)
+ continue;
+
+ disk_cache::FileLock lock(header);
+ int index_offset = j * 4 + 4 - target;
+ *index = current * 32 + index_offset;
+ uint32 to_add = ((1 << size) - 1) << index_offset;
+ header->allocation_map[current] |= to_add;
+
+ header->hints[target - 1] = current;
+ header->empty[target - 1]--;
+ DCHECK(header->empty[target - 1] >= 0);
+ header->num_entries++;
+ if (target != size) {
+ header->empty[target - size - 1]++;
+ }
+ return true;
+ }
+ }
+
+ // It is possible to have an undetected corruption (for example when the OS
+ // crashes), fix it here.
+ LOG(ERROR) << "Failing CreateMapBlock";
+ FixAllocationCounters(header);
+ return false;
+}
+
+// Deletes the block pointed by index from allocation_map, and updates the
+// relevant counters on the header.
+void DeleteMapBlock(int index, int size, disk_cache::BlockFileHeader* header) {
+ if (size < 0 || size > disk_cache::kMaxNumBlocks) {
+ NOTREACHED();
+ return;
+ }
+ int byte_index = index / 8;
+ uint8* byte_map = reinterpret_cast<uint8*>(header->allocation_map);
+ uint8 map_block = byte_map[byte_index];
+
+ if (index % 8 >= 4)
+ map_block >>= 4;
+
+ // See what type of block will be availabe after we delete this one.
+ int bits_at_end = 4 - size - index % 4;
+ uint8 end_mask = (0xf << (4 - bits_at_end)) & 0xf;
+ bool update_counters = (map_block & end_mask) == 0;
+ uint8 new_value = map_block & ~(((1 << size) - 1) << (index % 4));
+ int new_type = GetMapBlockType(new_value);
+
+ disk_cache::FileLock lock(header);
+ DCHECK((((1 << size) - 1) << (index % 8)) < 0x100);
+ uint8 to_clear = ((1 << size) - 1) << (index % 8);
+ DCHECK((byte_map[byte_index] & to_clear) == to_clear);
+ byte_map[byte_index] &= ~to_clear;
+
+ if (update_counters) {
+ if (bits_at_end)
+ header->empty[bits_at_end - 1]--;
+ header->empty[new_type - 1]++;
+ DCHECK(header->empty[bits_at_end - 1] >= 0);
+ }
+ header->num_entries--;
+ DCHECK(header->num_entries >= 0);
+}
+
+// Restores the "empty counters" and allocation hints.
+void FixAllocationCounters(disk_cache::BlockFileHeader* header) {
+ for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) {
+ header->hints[i] = 0;
+ header->empty[i] = 0;
+ }
+
+ for (int i = 0; i < header->max_entries / 32; i++) {
+ uint32 map_block = header->allocation_map[i];
+
+ for (int j = 0; j < 8; j++, map_block >>= 4) {
+ int type = GetMapBlockType(map_block);
+ if (type)
+ header->empty[type -1]++;
+ }
+ }
+}
+
+bool NeedToGrowBlockFile(const disk_cache::BlockFileHeader* header,
+ int block_count) {
+ for (int i = block_count; i <= disk_cache::kMaxNumBlocks; i++) {
+ if (header->empty[i - 1])
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+BlockFiles::~BlockFiles() {
+ if (zero_buffer_)
+ delete[] zero_buffer_;
+ CloseFiles();
+}
+
+bool BlockFiles::Init(bool create_files) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ block_files_.resize(kFirstAdditionlBlockFile);
+ for (int i = 0; i < kFirstAdditionlBlockFile; i++) {
+ if (create_files)
+ if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true))
+ return false;
+
+ if (!OpenBlockFile(i))
+ return false;
+ }
+
+ init_ = true;
+ return true;
+}
+
+void BlockFiles::CloseFiles() {
+ init_ = false;
+ for (unsigned int i = 0; i < block_files_.size(); i++) {
+ if (block_files_[i]) {
+ block_files_[i]->Release();
+ block_files_[i] = NULL;
+ }
+ }
+ block_files_.clear();
+}
+
+std::wstring BlockFiles::Name(int index) {
+ // The file format allows for 256 files.
+ DCHECK(index < 256 || index >= 0);
+ std::wstring name = StringPrintf(L"%s%s%d", path_.c_str(), kBlockName, index);
+
+ return name;
+}
+
+bool BlockFiles::CreateBlockFile(int index, FileType file_type, bool force) {
+ std::wstring name = Name(index);
+ DWORD disposition = force ? CREATE_ALWAYS : CREATE_NEW;
+
+ ScopedHandle file(CreateFile(name.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, disposition, 0, NULL));
+ if (!file.IsValid())
+ return false;
+
+ BlockFileHeader header;
+ header.entry_size = Addr::BlockSizeForFileType(file_type);
+ header.this_file = static_cast<int16>(index);
+ DCHECK(index <= kint16max && index >= 0);
+
+ DWORD actual;
+ if (!WriteFile(file.Get(), &header, sizeof(header), &actual, NULL) ||
+ sizeof(header) != actual)
+ return false;
+
+ return true;
+}
+
+bool BlockFiles::OpenBlockFile(int index) {
+ if (block_files_.size() - 1 < static_cast<unsigned int>(index)) {
+ DCHECK(index > 0);
+ int to_add = index - static_cast<int>(block_files_.size()) + 1;
+ block_files_.resize(block_files_.size() + to_add);
+ }
+
+ std::wstring name = Name(index);
+ MappedFile* file = new MappedFile();
+ file->AddRef();
+
+ if (!file->Init(name, kBlockHeaderSize)) {
+ NOTREACHED();
+ LOG(ERROR) << "Failed to open " << name;
+ file->Release();
+ return false;
+ }
+
+ block_files_[index] = file;
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ if (kBlockMagic != header->magic || kCurrentVersion != header->version) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+
+ if (header->updating) {
+ // Last instance was not properly shutdown.
+ if (!FixBlockFileHeader(file))
+ return false;
+ }
+ return true;
+}
+
+MappedFile* BlockFiles::GetFile(Addr address) {
+ CHECK(block_files_.size() >= 4);
+
+ int file_index = address.FileNumber();
+ if (static_cast<unsigned int>(file_index) >= block_files_.size() ||
+ !block_files_[file_index]) {
+ // We need to open the file
+ if (!OpenBlockFile(file_index))
+ return NULL;
+ }
+ DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index));
+ return block_files_[file_index];
+}
+
+bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) {
+ if (kMaxBlocks == header->max_entries)
+ return false;
+
+ DCHECK(!header->empty[3]);
+ int new_size = header->max_entries + 1024;
+ if (new_size > kMaxBlocks)
+ new_size = kMaxBlocks;
+
+ int new_size_bytes = new_size * header->entry_size + sizeof(*header);
+
+ FileLock lock(header);
+ if (!file->SetLength(new_size_bytes)) {
+ // Most likely we are trying to truncate the file, so the header is wrong.
+ if (header->updating < 10 && !FixBlockFileHeader(file)) {
+ // If we can't fix the file increase the lock guard so we'll pick it on
+ // the next start and replace it.
+ header->updating = 100;
+ return false;
+ }
+ return (header->max_entries >= new_size);
+ }
+
+ header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries
+ header->max_entries = new_size;
+
+ return true;
+}
+
+MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) {
+ COMPILE_ASSERT(RANKINGS == 1, invalid_fily_type);
+ MappedFile* file = block_files_[block_type - 1];
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+
+ while (NeedToGrowBlockFile(header, block_count)) {
+ if (kMaxBlocks == header->max_entries) {
+ file = NextFile(file);
+ if (!file)
+ return NULL;
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ continue;
+ }
+
+ if (!GrowBlockFile(file, header))
+ return NULL;
+ break;
+ }
+ return file;
+}
+
+MappedFile* BlockFiles::NextFile(const MappedFile* file) {
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ int new_file = header->next_file;
+ if (!new_file) {
+ // RANKINGS is not reported as a type for small entries, but we may be
+ // extending the rankings block file.
+ FileType type = Addr::RequiredFileType(header->entry_size);
+ if (header->entry_size == Addr::BlockSizeForFileType(RANKINGS))
+ type = RANKINGS;
+
+ new_file = CreateNextBlockFile(type);
+ if (!new_file)
+ return NULL;
+
+ FileLock lock(header);
+ header->next_file = new_file;
+ }
+
+ // Only the block_file argument is relevant for what we want.
+ Addr address(BLOCK_256, 1, new_file, 0);
+ return GetFile(address);
+}
+
+int BlockFiles::CreateNextBlockFile(FileType block_type) {
+ for (int i = kFirstAdditionlBlockFile; i <= kMaxBlockFile; i++) {
+ if (CreateBlockFile(i, block_type, false))
+ return i;
+ }
+ return 0;
+}
+
+bool BlockFiles::CreateBlock(FileType block_type, int block_count,
+ Addr* block_address) {
+ if (block_type < RANKINGS || block_type > BLOCK_4K ||
+ block_count < 1 || block_count > 4)
+ return false;
+ if (!init_)
+ return false;
+
+ MappedFile* file = FileForNewBlock(block_type, block_count);
+ if (!file)
+ return false;
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+
+ int target_size = 0;
+ for (int i = block_count; i <= 4; i++) {
+ if (header->empty[i - 1]) {
+ target_size = i;
+ break;
+ }
+ }
+
+ DCHECK(target_size);
+ int index;
+ if (!CreateMapBlock(target_size, block_count, header, &index))
+ return false;
+
+ Addr address(block_type, block_count, header->this_file, index);
+ block_address->set_value(address.value());
+ return true;
+}
+
+void BlockFiles::DeleteBlock(Addr address, bool deep) {
+ if (!address.is_initialized() || address.is_separate_file())
+ return;
+
+ if (!zero_buffer_) {
+ zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4];
+ memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4);
+ }
+ MappedFile* file = GetFile(address);
+ if (!file)
+ return;
+
+ size_t size = address.BlockSize() * address.num_blocks();
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ if (deep)
+ file->Write(zero_buffer_, size, offset);
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ DeleteMapBlock(address.start_block(), address.num_blocks(), header);
+}
+
+bool BlockFiles::FixBlockFileHeader(MappedFile* file) {
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ int file_size = static_cast<int>(file->GetLength());
+ if (file_size < static_cast<int>(sizeof(*header)))
+ return false; // file_size > 2GB is also an error.
+
+ int expected = header->entry_size * header->max_entries + sizeof(*header);
+ if (file_size != expected) {
+ int max_expected = header->entry_size * kMaxBlocks + sizeof(*header);
+ if (file_size < expected || header->empty[3] || file_size > max_expected) {
+ NOTREACHED();
+ LOG(ERROR) << "Unexpected file size";
+ return false;
+ }
+ // We were in the middle of growing the file.
+ int num_entries = (file_size - sizeof(*header)) / header->entry_size;
+ header->max_entries = num_entries;
+ }
+
+ FixAllocationCounters(header);
+ header->updating = 0;
+ return true;
+}
+
+} // namespace disk_cache