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
28 namespace {
29
30 const base::FilePath::CharType kIndexName[] = FILE_PATH_LITERAL("index");
31
32 // Reads the |header_size| bytes from the beginning of file |name|.
ReadHeader(const base::FilePath & name,char * header,int header_size)33 bool 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
GetMajorVersionFromFile(const base::FilePath & name)48 int 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.
DumpStats(const base::FilePath & path,disk_cache::CacheAddr addr)57 void 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.
DumpIndexHeader(const base::FilePath & name,disk_cache::CacheAddr * stats_addr)95 void 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.
DumpBlockHeader(const base::FilePath & name)126 void 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.
152 class CacheDumper {
153 public:
CacheDumper(const base::FilePath & path)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
Init()184 bool 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
GetEntry(disk_cache::EntryStore * entry)202 bool 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
LoadEntry(disk_cache::CacheAddr addr,disk_cache::EntryStore * entry)232 bool 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
LoadRankings(disk_cache::CacheAddr addr,disk_cache::RankingsNode * rankings)257 bool 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
DumpEntry(const disk_cache::EntryStore & entry)273 void 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
DumpRankings(const disk_cache::RankingsNode & rankings)297 void 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
GetMajorVersion(const base::FilePath & input_path)310 int 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.
DumpHeaders(const base::FilePath & input_path)337 int 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.
DumpContents(const base::FilePath & input_path)353 int 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