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// Performs basic inspection of the disk cache files with minimal disruption
6// to the actual files (they still may change if an error is detected on the
7// files).
8
9#include "net/tools/dump_cache/dump_files.h"
10
11#include <stdio.h>
12
13#include <set>
14#include <string>
15
16#include "base/files/file.h"
17#include "base/files/file_enumerator.h"
18#include "base/files/file_util.h"
19#include "base/format_macros.h"
20#include "base/message_loop/message_loop.h"
21#include "net/disk_cache/blockfile/block_files.h"
22#include "net/disk_cache/blockfile/disk_format.h"
23#include "net/disk_cache/blockfile/mapped_file.h"
24#include "net/disk_cache/blockfile/stats.h"
25#include "net/disk_cache/blockfile/storage_block-inl.h"
26#include "net/disk_cache/blockfile/storage_block.h"
27
28namespace {
29
30const base::FilePath::CharType kIndexName[] = FILE_PATH_LITERAL("index");
31
32// Reads the |header_size| bytes from the beginning of file |name|.
33bool ReadHeader(const base::FilePath& name, char* header, int header_size) {
34  base::File file(name, base::File::FLAG_OPEN | base::File::FLAG_READ);
35  if (!file.IsValid()) {
36    printf("Unable to open file %s\n", name.MaybeAsASCII().c_str());
37    return false;
38  }
39
40  int read = file.Read(0, header, header_size);
41  if (read != header_size) {
42    printf("Unable to read file %s\n", name.MaybeAsASCII().c_str());
43    return false;
44  }
45  return true;
46}
47
48int GetMajorVersionFromFile(const base::FilePath& name) {
49  disk_cache::IndexHeader header;
50  if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
51    return 0;
52
53  return header.version >> 16;
54}
55
56// Dumps the contents of the Stats record.
57void DumpStats(const base::FilePath& path, disk_cache::CacheAddr addr) {
58  // We need a message loop, although we really don't run any task.
59  base::MessageLoopForIO loop;
60
61  disk_cache::BlockFiles block_files(path);
62  if (!block_files.Init(false)) {
63    printf("Unable to init block files\n");
64    return;
65  }
66
67  disk_cache::Addr address(addr);
68  disk_cache::MappedFile* file = block_files.GetFile(address);
69  if (!file)
70    return;
71
72  size_t length = (2 + disk_cache::Stats::kDataSizesLength) * sizeof(int32) +
73                  disk_cache::Stats::MAX_COUNTER * sizeof(int64);
74
75  size_t offset = address.start_block() * address.BlockSize() +
76                  disk_cache::kBlockHeaderSize;
77
78  scoped_ptr<int32[]> buffer(new int32[length]);
79  if (!file->Read(buffer.get(), length, offset))
80    return;
81
82  printf("Stats:\nSignatrure: 0x%x\n", buffer[0]);
83  printf("Total size: %d\n", buffer[1]);
84  for (int i = 0; i < disk_cache::Stats::kDataSizesLength; i++)
85    printf("Size(%d): %d\n", i, buffer[i + 2]);
86
87  int64* counters = reinterpret_cast<int64*>(
88                        buffer.get() + 2 + disk_cache::Stats::kDataSizesLength);
89  for (int i = 0; i < disk_cache::Stats::MAX_COUNTER; i++)
90    printf("Count(%d): %" PRId64 "\n", i, *counters++);
91  printf("-------------------------\n\n");
92}
93
94// Dumps the contents of the Index-file header.
95void DumpIndexHeader(const base::FilePath& name,
96                     disk_cache::CacheAddr* stats_addr) {
97  disk_cache::IndexHeader header;
98  if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
99    return;
100
101  printf("Index file:\n");
102  printf("magic: %x\n", header.magic);
103  printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
104  printf("entries: %d\n", header.num_entries);
105  printf("total bytes: %d\n", header.num_bytes);
106  printf("last file number: %d\n", header.last_file);
107  printf("current id: %d\n", header.this_id);
108  printf("table length: %d\n", header.table_len);
109  printf("last crash: %d\n", header.crash);
110  printf("experiment: %d\n", header.experiment);
111  printf("stats: %x\n", header.stats);
112  for (int i = 0; i < 5; i++) {
113    printf("head %d: 0x%x\n", i, header.lru.heads[i]);
114    printf("tail %d: 0x%x\n", i, header.lru.tails[i]);
115    printf("size %d: 0x%x\n", i, header.lru.sizes[i]);
116  }
117  printf("transaction: 0x%x\n", header.lru.transaction);
118  printf("operation: %d\n", header.lru.operation);
119  printf("operation list: %d\n", header.lru.operation_list);
120  printf("-------------------------\n\n");
121
122  *stats_addr = header.stats;
123}
124
125// Dumps the contents of a block-file header.
126void DumpBlockHeader(const base::FilePath& name) {
127  disk_cache::BlockFileHeader header;
128  if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
129    return;
130
131  printf("Block file: %s\n", name.BaseName().MaybeAsASCII().c_str());
132  printf("magic: %x\n", header.magic);
133  printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
134  printf("file id: %d\n", header.this_file);
135  printf("next file id: %d\n", header.next_file);
136  printf("entry size: %d\n", header.entry_size);
137  printf("current entries: %d\n", header.num_entries);
138  printf("max entries: %d\n", header.max_entries);
139  printf("updating: %d\n", header.updating);
140  printf("empty sz 1: %d\n", header.empty[0]);
141  printf("empty sz 2: %d\n", header.empty[1]);
142  printf("empty sz 3: %d\n", header.empty[2]);
143  printf("empty sz 4: %d\n", header.empty[3]);
144  printf("user 0: 0x%x\n", header.user[0]);
145  printf("user 1: 0x%x\n", header.user[1]);
146  printf("user 2: 0x%x\n", header.user[2]);
147  printf("user 3: 0x%x\n", header.user[3]);
148  printf("-------------------------\n\n");
149}
150
151// Simple class that interacts with the set of cache files.
152class CacheDumper {
153 public:
154  explicit CacheDumper(const base::FilePath& path)
155      : path_(path),
156        block_files_(path),
157        index_(NULL),
158        current_hash_(0),
159        next_addr_(0) {
160  }
161
162  bool Init();
163
164  // Reads an entry from disk. Return false when all entries have been already
165  // returned.
166  bool GetEntry(disk_cache::EntryStore* entry);
167
168  // Loads a specific block from the block files.
169  bool LoadEntry(disk_cache::CacheAddr addr, disk_cache::EntryStore* entry);
170  bool LoadRankings(disk_cache::CacheAddr addr,
171                    disk_cache::RankingsNode* rankings);
172
173 private:
174  base::FilePath path_;
175  disk_cache::BlockFiles block_files_;
176  scoped_refptr<disk_cache::MappedFile> index_file_;
177  disk_cache::Index* index_;
178  int current_hash_;
179  disk_cache::CacheAddr next_addr_;
180  std::set<disk_cache::CacheAddr> dumped_entries_;
181  DISALLOW_COPY_AND_ASSIGN(CacheDumper);
182};
183
184bool CacheDumper::Init() {
185  if (!block_files_.Init(false)) {
186    printf("Unable to init block files\n");
187    return false;
188  }
189
190  base::FilePath index_name(path_.Append(kIndexName));
191  index_file_ = new disk_cache::MappedFile;
192  index_ = reinterpret_cast<disk_cache::Index*>(
193      index_file_->Init(index_name, 0));
194  if (!index_) {
195    printf("Unable to map index\n");
196    return false;
197  }
198
199  return true;
200}
201
202bool CacheDumper::GetEntry(disk_cache::EntryStore* entry) {
203  if (dumped_entries_.find(next_addr_) != dumped_entries_.end()) {
204    printf("Loop detected\n");
205    next_addr_ = 0;
206    current_hash_++;
207  }
208
209  if (next_addr_) {
210    if (LoadEntry(next_addr_, entry))
211      return true;
212
213    printf("Unable to load entry at address 0x%x\n", next_addr_);
214    next_addr_ = 0;
215    current_hash_++;
216  }
217
218  for (int i = current_hash_; i < index_->header.table_len; i++) {
219    // Yes, we'll crash if the table is shorter than expected, but only after
220    // dumping every entry that we can find.
221    if (index_->table[i]) {
222      current_hash_ = i;
223      if (LoadEntry(index_->table[i], entry))
224        return true;
225
226      printf("Unable to load entry at address 0x%x\n", index_->table[i]);
227    }
228  }
229  return false;
230}
231
232bool CacheDumper::LoadEntry(disk_cache::CacheAddr addr,
233                            disk_cache::EntryStore* entry) {
234  disk_cache::Addr address(addr);
235  disk_cache::MappedFile* file = block_files_.GetFile(address);
236  if (!file)
237    return false;
238
239  disk_cache::StorageBlock<disk_cache::EntryStore> entry_block(file, address);
240  if (!entry_block.Load())
241    return false;
242
243  memcpy(entry, entry_block.Data(), sizeof(*entry));
244  printf("Entry at 0x%x\n", addr);
245
246  // Prepare for the next entry to load.
247  next_addr_ = entry->next;
248  if (next_addr_) {
249    dumped_entries_.insert(addr);
250  } else {
251    current_hash_++;
252    dumped_entries_.clear();
253  }
254  return true;
255}
256
257bool CacheDumper::LoadRankings(disk_cache::CacheAddr addr,
258                               disk_cache::RankingsNode* rankings) {
259  disk_cache::Addr address(addr);
260  disk_cache::MappedFile* file = block_files_.GetFile(address);
261  if (!file)
262    return false;
263
264  disk_cache::StorageBlock<disk_cache::RankingsNode> rank_block(file, address);
265  if (!rank_block.Load())
266    return false;
267
268  memcpy(rankings, rank_block.Data(), sizeof(*rankings));
269  printf("Rankings at 0x%x\n", addr);
270  return true;
271}
272
273void DumpEntry(const disk_cache::EntryStore& entry) {
274  std::string key;
275  if (!entry.long_key) {
276    key = entry.key;
277    if (key.size() > 50)
278      key.resize(50);
279  }
280
281  printf("hash: 0x%x\n", entry.hash);
282  printf("next entry: 0x%x\n", entry.next);
283  printf("rankings: 0x%x\n", entry.rankings_node);
284  printf("key length: %d\n", entry.key_len);
285  printf("key: \"%s\"\n", key.c_str());
286  printf("key addr: 0x%x\n", entry.long_key);
287  printf("reuse count: %d\n", entry.reuse_count);
288  printf("refetch count: %d\n", entry.refetch_count);
289  printf("state: %d\n", entry.state);
290  for (int i = 0; i < 4; i++) {
291    printf("data size %d: %d\n", i, entry.data_size[i]);
292    printf("data addr %d: 0x%x\n", i, entry.data_addr[i]);
293  }
294  printf("----------\n\n");
295}
296
297void DumpRankings(const disk_cache::RankingsNode& rankings) {
298  printf("next: 0x%x\n", rankings.next);
299  printf("prev: 0x%x\n", rankings.prev);
300  printf("entry: 0x%x\n", rankings.contents);
301  printf("dirty: %d\n", rankings.dirty);
302  printf("hash: 0x%x\n", rankings.self_hash);
303  printf("----------\n\n");
304}
305
306}  // namespace.
307
308// -----------------------------------------------------------------------
309
310int GetMajorVersion(const base::FilePath& input_path) {
311  base::FilePath index_name(input_path.Append(kIndexName));
312
313  int version = GetMajorVersionFromFile(index_name);
314  if (!version)
315    return 0;
316
317  base::FilePath data_name(input_path.Append(FILE_PATH_LITERAL("data_0")));
318  if (version != GetMajorVersionFromFile(data_name))
319    return 0;
320
321  data_name = input_path.Append(FILE_PATH_LITERAL("data_1"));
322  if (version != GetMajorVersionFromFile(data_name))
323    return 0;
324
325  data_name = input_path.Append(FILE_PATH_LITERAL("data_2"));
326  if (version != GetMajorVersionFromFile(data_name))
327    return 0;
328
329  data_name = input_path.Append(FILE_PATH_LITERAL("data_3"));
330  if (version != GetMajorVersionFromFile(data_name))
331    return 0;
332
333  return version;
334}
335
336// Dumps the headers of all files.
337int DumpHeaders(const base::FilePath& input_path) {
338  base::FilePath index_name(input_path.Append(kIndexName));
339  disk_cache::CacheAddr stats_addr = 0;
340  DumpIndexHeader(index_name, &stats_addr);
341
342  base::FileEnumerator iter(input_path, false,
343                            base::FileEnumerator::FILES,
344                            FILE_PATH_LITERAL("data_*"));
345  for (base::FilePath file = iter.Next(); !file.empty(); file = iter.Next())
346    DumpBlockHeader(file);
347
348  DumpStats(input_path, stats_addr);
349  return 0;
350}
351
352// Dumps all entries from the cache.
353int DumpContents(const base::FilePath& input_path) {
354  DumpHeaders(input_path);
355
356  // We need a message loop, although we really don't run any task.
357  base::MessageLoopForIO loop;
358  CacheDumper dumper(input_path);
359  if (!dumper.Init())
360    return -1;
361
362  disk_cache::EntryStore entry;
363  while (dumper.GetEntry(&entry)) {
364    DumpEntry(entry);
365    disk_cache::RankingsNode rankings;
366    if (dumper.LoadRankings(entry.rankings_node, &rankings))
367      DumpRankings(rankings);
368  }
369
370  printf("Done.\n");
371
372  return 0;
373}
374