| // Copyright (c) 2010 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. |
| |
| // Performs basic inspection of the disk cache files with minimal disruption |
| // to the actual files (they still may change if an error is detected on the |
| // files). |
| |
| #include <set> |
| #include <stdio.h> |
| #include <string> |
| |
| #include "base/file_util.h" |
| #include "base/message_loop.h" |
| #include "net/base/file_stream.h" |
| #include "net/disk_cache/block_files.h" |
| #include "net/disk_cache/disk_format.h" |
| #include "net/disk_cache/mapped_file.h" |
| #include "net/disk_cache/storage_block.h" |
| |
| namespace { |
| |
| const wchar_t kIndexName[] = L"index"; |
| const wchar_t kDataPrefix[] = L"data_"; |
| |
| // Reads the |header_size| bytes from the beginning of file |name|. |
| bool ReadHeader(const std::wstring& name, char* header, int header_size) { |
| net::FileStream file; |
| file.Open(FilePath::FromWStringHack(name), |
| base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ); |
| if (!file.IsOpen()) { |
| printf("Unable to open file %ls\n", name.c_str()); |
| return false; |
| } |
| |
| int read = file.Read(header, header_size, NULL); |
| if (read != header_size) { |
| printf("Unable to read file %ls\n", name.c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| int GetMajorVersionFromFile(const std::wstring& name) { |
| disk_cache::IndexHeader header; |
| if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) |
| return 0; |
| |
| return header.version >> 16; |
| } |
| |
| // Dumps the contents of the Index-file header. |
| void DumpIndexHeader(const std::wstring& name) { |
| disk_cache::IndexHeader header; |
| if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) |
| return; |
| |
| printf("Index file:\n"); |
| printf("magic: %x\n", header.magic); |
| printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff); |
| printf("entries: %d\n", header.num_entries); |
| printf("total bytes: %d\n", header.num_bytes); |
| printf("last file number: %d\n", header.last_file); |
| printf("current id: %d\n", header.this_id); |
| printf("table length: %d\n", header.table_len); |
| printf("last crash: %d\n", header.crash); |
| printf("experiment: %d\n", header.experiment); |
| for (int i = 0; i < 5; i++) { |
| printf("head %d: 0x%x\n", i, header.lru.heads[i]); |
| printf("tail %d: 0x%x\n", i, header.lru.tails[i]); |
| printf("size %d: 0x%x\n", i, header.lru.sizes[i]); |
| } |
| printf("transaction: 0x%x\n", header.lru.transaction); |
| printf("operation: %d\n", header.lru.operation); |
| printf("operation list: %d\n", header.lru.operation_list); |
| printf("-------------------------\n\n"); |
| } |
| |
| // Dumps the contents of a block-file header. |
| void DumpBlockHeader(const std::wstring& name) { |
| disk_cache::BlockFileHeader header; |
| if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) |
| return; |
| |
| std::wstring file_name = FilePath(name).BaseName().value(); |
| |
| printf("Block file: %ls\n", file_name.c_str()); |
| printf("magic: %x\n", header.magic); |
| printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff); |
| printf("file id: %d\n", header.this_file); |
| printf("next file id: %d\n", header.next_file); |
| printf("entry size: %d\n", header.entry_size); |
| printf("current entries: %d\n", header.num_entries); |
| printf("max entries: %d\n", header.max_entries); |
| printf("updating: %d\n", header.updating); |
| printf("empty sz 1: %d\n", header.empty[0]); |
| printf("empty sz 2: %d\n", header.empty[1]); |
| printf("empty sz 3: %d\n", header.empty[2]); |
| printf("empty sz 4: %d\n", header.empty[3]); |
| printf("user 0: 0x%x\n", header.user[0]); |
| printf("user 1: 0x%x\n", header.user[1]); |
| printf("user 2: 0x%x\n", header.user[2]); |
| printf("user 3: 0x%x\n", header.user[3]); |
| printf("-------------------------\n\n"); |
| } |
| |
| // Simple class that interacts with the set of cache files. |
| class CacheDumper { |
| public: |
| explicit CacheDumper(const std::wstring& path) |
| : path_(path), |
| block_files_(FilePath::FromWStringHack(path)), |
| index_(NULL), |
| current_hash_(0), |
| next_addr_(0) { |
| } |
| |
| bool Init(); |
| |
| // Reads an entry from disk. Return false when all entries have been already |
| // returned. |
| bool GetEntry(disk_cache::EntryStore* entry); |
| |
| // Loads a specific block from the block files. |
| bool LoadEntry(disk_cache::CacheAddr addr, disk_cache::EntryStore* entry); |
| bool LoadRankings(disk_cache::CacheAddr addr, |
| disk_cache::RankingsNode* rankings); |
| |
| private: |
| std::wstring path_; |
| disk_cache::BlockFiles block_files_; |
| scoped_refptr<disk_cache::MappedFile> index_file_; |
| disk_cache::Index* index_; |
| int current_hash_; |
| disk_cache::CacheAddr next_addr_; |
| std::set<disk_cache::CacheAddr> dumped_entries_; |
| DISALLOW_COPY_AND_ASSIGN(CacheDumper); |
| }; |
| |
| bool CacheDumper::Init() { |
| if (!block_files_.Init(false)) { |
| printf("Unable to init block files\n"); |
| return false; |
| } |
| |
| std::wstring index_name(path_); |
| file_util::AppendToPath(&index_name, kIndexName); |
| index_file_ = new disk_cache::MappedFile; |
| index_ = reinterpret_cast<disk_cache::Index*>(index_file_->Init( |
| FilePath::FromWStringHack(index_name), 0)); |
| if (!index_) { |
| printf("Unable to map index\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CacheDumper::GetEntry(disk_cache::EntryStore* entry) { |
| if (dumped_entries_.find(next_addr_) != dumped_entries_.end()) { |
| printf("Loop detected\n"); |
| next_addr_ = 0; |
| current_hash_++; |
| } |
| |
| if (next_addr_) { |
| if (LoadEntry(next_addr_, entry)) |
| return true; |
| |
| printf("Unable to load entry at address 0x%x\n", next_addr_); |
| next_addr_ = 0; |
| current_hash_++; |
| } |
| |
| for (int i = current_hash_; i < index_->header.table_len; i++) { |
| // Yes, we'll crash if the table is shorter than expected, but only after |
| // dumping every entry that we can find. |
| if (index_->table[i]) { |
| current_hash_ = i; |
| if (LoadEntry(index_->table[i], entry)) |
| return true; |
| |
| printf("Unable to load entry at address 0x%x\n", index_->table[i]); |
| } |
| } |
| return false; |
| } |
| |
| bool CacheDumper::LoadEntry(disk_cache::CacheAddr addr, |
| disk_cache::EntryStore* entry) { |
| disk_cache::Addr address(addr); |
| disk_cache::MappedFile* file = block_files_.GetFile(address); |
| if (!file) |
| return false; |
| |
| disk_cache::CacheEntryBlock entry_block(file, address); |
| if (!entry_block.Load()) |
| return false; |
| |
| memcpy(entry, entry_block.Data(), sizeof(*entry)); |
| printf("Entry at 0x%x\n", addr); |
| |
| // Prepare for the next entry to load. |
| next_addr_ = entry->next; |
| if (next_addr_) { |
| dumped_entries_.insert(addr); |
| } else { |
| current_hash_++; |
| dumped_entries_.clear(); |
| } |
| return true; |
| } |
| |
| bool CacheDumper::LoadRankings(disk_cache::CacheAddr addr, |
| disk_cache::RankingsNode* rankings) { |
| disk_cache::Addr address(addr); |
| disk_cache::MappedFile* file = block_files_.GetFile(address); |
| if (!file) |
| return false; |
| |
| disk_cache::CacheRankingsBlock rank_block(file, address); |
| if (!rank_block.Load()) |
| return false; |
| |
| memcpy(rankings, rank_block.Data(), sizeof(*rankings)); |
| printf("Rankings at 0x%x\n", addr); |
| return true; |
| } |
| |
| void DumpEntry(const disk_cache::EntryStore& entry) { |
| std::string key; |
| if (!entry.long_key) { |
| key = entry.key; |
| if (key.size() > 50) |
| key.resize(50); |
| } |
| |
| printf("hash: 0x%x\n", entry.hash); |
| printf("next entry: 0x%x\n", entry.next); |
| printf("rankings: 0x%x\n", entry.rankings_node); |
| printf("key length: %d\n", entry.key_len); |
| printf("key: \"%s\"\n", key.c_str()); |
| printf("key addr: 0x%x\n", entry.long_key); |
| printf("reuse count: %d\n", entry.reuse_count); |
| printf("refetch count: %d\n", entry.refetch_count); |
| printf("state: %d\n", entry.state); |
| for (int i = 0; i < 4; i++) { |
| printf("data size %d: %d\n", i, entry.data_size[i]); |
| printf("data addr %d: 0x%x\n", i, entry.data_addr[i]); |
| } |
| printf("----------\n\n"); |
| } |
| |
| void DumpRankings(const disk_cache::RankingsNode& rankings) { |
| printf("next: 0x%x\n", rankings.next); |
| printf("prev: 0x%x\n", rankings.prev); |
| printf("entry: 0x%x\n", rankings.contents); |
| printf("dirty: %d\n", rankings.dirty); |
| printf("pointer: 0x%x\n", rankings.dummy); |
| printf("----------\n\n"); |
| } |
| |
| } // namespace. |
| |
| // ----------------------------------------------------------------------- |
| |
| int GetMajorVersion(const std::wstring& input_path) { |
| std::wstring index_name(input_path); |
| file_util::AppendToPath(&index_name, kIndexName); |
| |
| int version = GetMajorVersionFromFile(index_name); |
| if (!version) |
| return 0; |
| |
| std::wstring data_name(input_path); |
| file_util::AppendToPath(&data_name, L"data_0"); |
| if (version != GetMajorVersionFromFile(data_name)) |
| return 0; |
| |
| data_name = input_path; |
| file_util::AppendToPath(&data_name, L"data_1"); |
| if (version != GetMajorVersionFromFile(data_name)) |
| return 0; |
| |
| return version; |
| } |
| |
| // Dumps the headers of all files. |
| int DumpHeaders(const std::wstring& input_path) { |
| std::wstring index_name(input_path); |
| file_util::AppendToPath(&index_name, kIndexName); |
| DumpIndexHeader(index_name); |
| |
| std::wstring pattern(kDataPrefix); |
| pattern.append(L"*"); |
| file_util::FileEnumerator iter(FilePath(input_path), false, |
| file_util::FileEnumerator::FILES, pattern); |
| for (std::wstring file = iter.Next().value(); !file.empty(); |
| file = iter.Next().value()) { |
| DumpBlockHeader(file); |
| } |
| |
| return 0; |
| } |
| |
| // Dumps all entries from the cache. |
| int DumpContents(const std::wstring& input_path) { |
| DumpHeaders(input_path); |
| |
| // We need a message loop, although we really don't run any task. |
| MessageLoop loop(MessageLoop::TYPE_IO); |
| CacheDumper dumper(input_path); |
| if (!dumper.Init()) |
| return -1; |
| |
| disk_cache::EntryStore entry; |
| while (dumper.GetEntry(&entry)) { |
| DumpEntry(entry); |
| disk_cache::RankingsNode rankings; |
| if (dumper.LoadRankings(entry.rankings_node, &rankings)) |
| DumpRankings(rankings); |
| } |
| |
| printf("Done.\n"); |
| |
| return 0; |
| } |