1 // Copyright 2012 The Chromium Authors
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 <memory>
14 #include <set>
15 #include <string>
16
17 #include "base/command_line.h"
18 #include "base/files/file.h"
19 #include "base/files/file_enumerator.h"
20 #include "base/files/file_util.h"
21 #include "base/format_macros.h"
22 #include "base/i18n/time_formatting.h"
23 #include "base/message_loop/message_pump_type.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/task/single_thread_task_executor.h"
27 #include "base/time/time.h"
28 #include "net/disk_cache/blockfile/block_files.h"
29 #include "net/disk_cache/blockfile/disk_format.h"
30 #include "net/disk_cache/blockfile/mapped_file.h"
31 #include "net/disk_cache/blockfile/stats.h"
32 #include "net/disk_cache/blockfile/storage_block-inl.h"
33 #include "net/disk_cache/blockfile/storage_block.h"
34 #include "net/url_request/view_cache_helper.h"
35
36 namespace {
37
38 const base::FilePath::CharType kIndexName[] = FILE_PATH_LITERAL("index");
39
40 // Reads the |header_size| bytes from the beginning of file |name|.
ReadHeader(const base::FilePath & name,char * header,int header_size)41 bool ReadHeader(const base::FilePath& name, char* header, int header_size) {
42 base::File file(name, base::File::FLAG_OPEN | base::File::FLAG_READ);
43 if (!file.IsValid()) {
44 printf("Unable to open file %s\n", name.MaybeAsASCII().c_str());
45 return false;
46 }
47
48 int read = file.Read(0, header, header_size);
49 if (read != header_size) {
50 printf("Unable to read file %s\n", name.MaybeAsASCII().c_str());
51 return false;
52 }
53 return true;
54 }
55
GetMajorVersionFromIndexFile(const base::FilePath & name)56 int GetMajorVersionFromIndexFile(const base::FilePath& name) {
57 disk_cache::IndexHeader header;
58 if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
59 return 0;
60 if (header.magic != disk_cache::kIndexMagic) {
61 return 0;
62 }
63 return header.version;
64 }
65
GetMajorVersionFromBlockFile(const base::FilePath & name)66 int GetMajorVersionFromBlockFile(const base::FilePath& name) {
67 disk_cache::BlockFileHeader header;
68 if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header))) {
69 return 0;
70 }
71
72 if (header.magic != disk_cache::kBlockMagic) {
73 return 0;
74 }
75
76 return header.version;
77 }
78
79 // Dumps the contents of the Stats record.
DumpStats(const base::FilePath & path,disk_cache::CacheAddr addr)80 void DumpStats(const base::FilePath& path, disk_cache::CacheAddr addr) {
81 // We need a task executor, although we really don't run any task.
82 base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
83
84 disk_cache::BlockFiles block_files(path);
85 if (!block_files.Init(false)) {
86 printf("Unable to init block files\n");
87 return;
88 }
89
90 disk_cache::Addr address(addr);
91 disk_cache::MappedFile* file = block_files.GetFile(address);
92 if (!file)
93 return;
94
95 size_t length = (2 + disk_cache::Stats::kDataSizesLength) * sizeof(int32_t) +
96 disk_cache::Stats::MAX_COUNTER * sizeof(int64_t);
97
98 size_t offset = address.start_block() * address.BlockSize() +
99 disk_cache::kBlockHeaderSize;
100
101 auto buffer = std::make_unique<int32_t[]>(length);
102 if (!file->Read(buffer.get(), length, offset))
103 return;
104
105 printf("Stats:\nSignatrure: 0x%x\n", buffer[0]);
106 printf("Total size: %d\n", buffer[1]);
107 for (int i = 0; i < disk_cache::Stats::kDataSizesLength; i++)
108 printf("Size(%d): %d\n", i, buffer[i + 2]);
109
110 int64_t* counters = reinterpret_cast<int64_t*>(
111 buffer.get() + 2 + disk_cache::Stats::kDataSizesLength);
112 for (int i = 0; i < disk_cache::Stats::MAX_COUNTER; i++)
113 printf("Count(%d): %" PRId64 "\n", i, *counters++);
114 printf("-------------------------\n\n");
115 }
116
117 // Dumps the contents of the Index-file header.
DumpIndexHeader(const base::FilePath & name,disk_cache::CacheAddr * stats_addr)118 void DumpIndexHeader(const base::FilePath& name,
119 disk_cache::CacheAddr* stats_addr) {
120 disk_cache::IndexHeader header;
121 if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
122 return;
123
124 printf("Index file:\n");
125 printf("magic: %x\n", header.magic);
126 printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
127 printf("entries: %d\n", header.num_entries);
128 printf("total bytes: %" PRId64 "\n", header.num_bytes);
129 printf("last file number: %d\n", header.last_file);
130 printf("current id: %d\n", header.this_id);
131 printf("table length: %d\n", header.table_len);
132 printf("last crash: %d\n", header.crash);
133 printf("experiment: %d\n", header.experiment);
134 printf("stats: %x\n", header.stats);
135 for (int i = 0; i < 5; i++) {
136 printf("head %d: 0x%x\n", i, header.lru.heads[i]);
137 printf("tail %d: 0x%x\n", i, header.lru.tails[i]);
138 printf("size %d: 0x%x\n", i, header.lru.sizes[i]);
139 }
140 printf("transaction: 0x%x\n", header.lru.transaction);
141 printf("operation: %d\n", header.lru.operation);
142 printf("operation list: %d\n", header.lru.operation_list);
143 printf("-------------------------\n\n");
144
145 if (stats_addr)
146 *stats_addr = header.stats;
147 }
148
149 // Dumps the contents of a block-file header.
DumpBlockHeader(const base::FilePath & name)150 void DumpBlockHeader(const base::FilePath& name) {
151 disk_cache::BlockFileHeader header;
152 if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
153 return;
154
155 printf("Block file: %s\n", name.BaseName().MaybeAsASCII().c_str());
156 printf("magic: %x\n", header.magic);
157 printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
158 printf("file id: %d\n", header.this_file);
159 printf("next file id: %d\n", header.next_file);
160 printf("entry size: %d\n", header.entry_size);
161 printf("current entries: %d\n", header.num_entries);
162 printf("max entries: %d\n", header.max_entries);
163 printf("updating: %d\n", header.updating);
164 printf("empty sz 1: %d\n", header.empty[0]);
165 printf("empty sz 2: %d\n", header.empty[1]);
166 printf("empty sz 3: %d\n", header.empty[2]);
167 printf("empty sz 4: %d\n", header.empty[3]);
168 printf("user 0: 0x%x\n", header.user[0]);
169 printf("user 1: 0x%x\n", header.user[1]);
170 printf("user 2: 0x%x\n", header.user[2]);
171 printf("user 3: 0x%x\n", header.user[3]);
172 printf("-------------------------\n\n");
173 }
174
175 // Simple class that interacts with the set of cache files.
176 class CacheDumper {
177 public:
CacheDumper(const base::FilePath & path)178 explicit CacheDumper(const base::FilePath& path)
179 : path_(path), block_files_(path) {}
180
181 CacheDumper(const CacheDumper&) = delete;
182 CacheDumper& operator=(const CacheDumper&) = delete;
183
184 bool Init();
185
186 // Reads an entry from disk. Return false when all entries have been already
187 // returned.
188 bool GetEntry(disk_cache::EntryStore* entry, disk_cache::CacheAddr* addr);
189
190 // Loads a specific block from the block files.
191 bool LoadEntry(disk_cache::CacheAddr addr, disk_cache::EntryStore* entry);
192 bool LoadRankings(disk_cache::CacheAddr addr,
193 disk_cache::RankingsNode* rankings);
194
195 // Appends the data store at |addr| to |out|.
196 bool HexDump(disk_cache::CacheAddr addr, std::string* out);
197
198 private:
199 base::FilePath path_;
200 disk_cache::BlockFiles block_files_;
201 scoped_refptr<disk_cache::MappedFile> index_file_;
202 disk_cache::Index* index_ = nullptr;
203 int current_hash_ = 0;
204 disk_cache::CacheAddr next_addr_ = 0;
205 std::set<disk_cache::CacheAddr> dumped_entries_;
206 };
207
Init()208 bool CacheDumper::Init() {
209 if (!block_files_.Init(false)) {
210 printf("Unable to init block files\n");
211 return false;
212 }
213
214 base::FilePath index_name(path_.Append(kIndexName));
215 index_file_ = base::MakeRefCounted<disk_cache::MappedFile>();
216 index_ = reinterpret_cast<disk_cache::Index*>(
217 index_file_->Init(index_name, 0));
218 if (!index_) {
219 printf("Unable to map index\n");
220 return false;
221 }
222
223 return true;
224 }
225
GetEntry(disk_cache::EntryStore * entry,disk_cache::CacheAddr * addr)226 bool CacheDumper::GetEntry(disk_cache::EntryStore* entry,
227 disk_cache::CacheAddr* addr) {
228 if (dumped_entries_.find(next_addr_) != dumped_entries_.end()) {
229 printf("Loop detected\n");
230 next_addr_ = 0;
231 current_hash_++;
232 }
233
234 if (next_addr_) {
235 *addr = next_addr_;
236 if (LoadEntry(next_addr_, entry))
237 return true;
238
239 printf("Unable to load entry at address 0x%x\n", next_addr_);
240 next_addr_ = 0;
241 current_hash_++;
242 }
243
244 for (int i = current_hash_; i < index_->header.table_len; i++) {
245 // Yes, we'll crash if the table is shorter than expected, but only after
246 // dumping every entry that we can find.
247 if (index_->table[i]) {
248 current_hash_ = i;
249 *addr = index_->table[i];
250 if (LoadEntry(index_->table[i], entry))
251 return true;
252
253 printf("Unable to load entry at address 0x%x\n", index_->table[i]);
254 }
255 }
256 return false;
257 }
258
LoadEntry(disk_cache::CacheAddr addr,disk_cache::EntryStore * entry)259 bool CacheDumper::LoadEntry(disk_cache::CacheAddr addr,
260 disk_cache::EntryStore* entry) {
261 disk_cache::Addr address(addr);
262 disk_cache::MappedFile* file = block_files_.GetFile(address);
263 if (!file)
264 return false;
265
266 disk_cache::StorageBlock<disk_cache::EntryStore> entry_block(file, address);
267 if (!entry_block.Load())
268 return false;
269
270 memcpy(entry, entry_block.Data(), sizeof(*entry));
271 if (!entry_block.VerifyHash())
272 printf("Self hash failed at 0x%x\n", addr);
273
274 // Prepare for the next entry to load.
275 next_addr_ = entry->next;
276 if (next_addr_) {
277 dumped_entries_.insert(addr);
278 } else {
279 current_hash_++;
280 dumped_entries_.clear();
281 }
282 return true;
283 }
284
LoadRankings(disk_cache::CacheAddr addr,disk_cache::RankingsNode * rankings)285 bool CacheDumper::LoadRankings(disk_cache::CacheAddr addr,
286 disk_cache::RankingsNode* rankings) {
287 disk_cache::Addr address(addr);
288 if (address.file_type() != disk_cache::RANKINGS)
289 return false;
290
291 disk_cache::MappedFile* file = block_files_.GetFile(address);
292 if (!file)
293 return false;
294
295 disk_cache::StorageBlock<disk_cache::RankingsNode> rank_block(file, address);
296 if (!rank_block.Load())
297 return false;
298
299 if (!rank_block.VerifyHash())
300 printf("Self hash failed at 0x%x\n", addr);
301
302 memcpy(rankings, rank_block.Data(), sizeof(*rankings));
303 return true;
304 }
305
HexDump(disk_cache::CacheAddr addr,std::string * out)306 bool CacheDumper::HexDump(disk_cache::CacheAddr addr, std::string* out) {
307 disk_cache::Addr address(addr);
308 disk_cache::MappedFile* file = block_files_.GetFile(address);
309 if (!file)
310 return false;
311
312 size_t size = address.num_blocks() * address.BlockSize();
313 auto buffer = std::make_unique<char[]>(size);
314
315 size_t offset = address.start_block() * address.BlockSize() +
316 disk_cache::kBlockHeaderSize;
317 if (!file->Read(buffer.get(), size, offset))
318 return false;
319
320 base::StringAppendF(out, "0x%x:\n", addr);
321 net::ViewCacheHelper::HexDump(buffer.get(), size, out);
322 return true;
323 }
324
ToLocalTime(int64_t time_us)325 std::string ToLocalTime(int64_t time_us) {
326 return base::UnlocalizedTimeFormatWithPattern(
327 base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(time_us)),
328 "y/M/d H:m:s.S");
329 }
330
DumpEntry(disk_cache::CacheAddr addr,const disk_cache::EntryStore & entry,bool verbose)331 void DumpEntry(disk_cache::CacheAddr addr,
332 const disk_cache::EntryStore& entry,
333 bool verbose) {
334 std::string key;
335 static bool full_key =
336 base::CommandLine::ForCurrentProcess()->HasSwitch("full-key");
337 if (!entry.long_key) {
338 key = std::string(entry.key, std::min(static_cast<size_t>(entry.key_len),
339 sizeof(entry.key)));
340 if (entry.key_len > 90 && !full_key)
341 key.resize(90);
342 }
343
344 printf("Entry at 0x%x\n", addr);
345 printf("rankings: 0x%x\n", entry.rankings_node);
346 printf("key length: %d\n", entry.key_len);
347 printf("key: \"%s\"\n", key.c_str());
348
349 if (verbose) {
350 printf("key addr: 0x%x\n", entry.long_key);
351 printf("hash: 0x%x\n", entry.hash);
352 printf("next entry: 0x%x\n", entry.next);
353 printf("reuse count: %d\n", entry.reuse_count);
354 printf("refetch count: %d\n", entry.refetch_count);
355 printf("state: %d\n", entry.state);
356 printf("creation: %s\n", ToLocalTime(entry.creation_time).c_str());
357 for (int i = 0; i < 4; i++) {
358 printf("data size %d: %d\n", i, entry.data_size[i]);
359 printf("data addr %d: 0x%x\n", i, entry.data_addr[i]);
360 }
361 printf("----------\n\n");
362 }
363 }
364
DumpRankings(disk_cache::CacheAddr addr,const disk_cache::RankingsNode & rankings,bool verbose)365 void DumpRankings(disk_cache::CacheAddr addr,
366 const disk_cache::RankingsNode& rankings,
367 bool verbose) {
368 printf("Rankings at 0x%x\n", addr);
369 printf("next: 0x%x\n", rankings.next);
370 printf("prev: 0x%x\n", rankings.prev);
371 printf("entry: 0x%x\n", rankings.contents);
372
373 if (verbose) {
374 printf("dirty: %d\n", rankings.dirty);
375 if (rankings.last_used != rankings.last_modified)
376 printf("used: %s\n", ToLocalTime(rankings.last_used).c_str());
377 printf("modified: %s\n", ToLocalTime(rankings.last_modified).c_str());
378 printf("hash: 0x%x\n", rankings.self_hash);
379 printf("----------\n\n");
380 } else {
381 printf("\n");
382 }
383 }
384
PrintCSVHeader()385 void PrintCSVHeader() {
386 printf(
387 "entry,rankings,next,prev,rank-contents,chain,reuse,key,"
388 "d0,d1,d2,d3\n");
389 }
390
DumpCSV(disk_cache::CacheAddr addr,const disk_cache::EntryStore & entry,const disk_cache::RankingsNode & rankings)391 void DumpCSV(disk_cache::CacheAddr addr,
392 const disk_cache::EntryStore& entry,
393 const disk_cache::RankingsNode& rankings) {
394 printf("0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n", addr,
395 entry.rankings_node, rankings.next, rankings.prev, rankings.contents,
396 entry.next, entry.reuse_count, entry.long_key, entry.data_addr[0],
397 entry.data_addr[1], entry.data_addr[2], entry.data_addr[3]);
398
399 if (addr != rankings.contents)
400 printf("Broken entry\n");
401 }
402
CanDump(disk_cache::CacheAddr addr)403 bool CanDump(disk_cache::CacheAddr addr) {
404 disk_cache::Addr address(addr);
405 return address.is_initialized() && address.is_block_file();
406 }
407
408 } // namespace.
409
410 // -----------------------------------------------------------------------
411
CheckFileVersion(const base::FilePath & input_path)412 bool CheckFileVersion(const base::FilePath& input_path) {
413 base::FilePath index_name(input_path.Append(kIndexName));
414
415 int index_version = GetMajorVersionFromIndexFile(index_name);
416 if (!index_version || index_version != disk_cache::kVersion3_0) {
417 return false;
418 }
419
420 constexpr int kCurrentBlockVersion = disk_cache::kBlockVersion2;
421 for (int i = 0; i < disk_cache::kFirstAdditionalBlockFile; i++) {
422 std::string data_name = "data_" + base::NumberToString(i);
423 auto data_path = input_path.AppendASCII(data_name);
424 int block_version = GetMajorVersionFromBlockFile(data_path);
425 if (!block_version || block_version != kCurrentBlockVersion) {
426 return false;
427 }
428 }
429 return true;
430 }
431
432 // Dumps the headers of all files.
DumpHeaders(const base::FilePath & input_path)433 int DumpHeaders(const base::FilePath& input_path) {
434 base::FilePath index_name(input_path.Append(kIndexName));
435 disk_cache::CacheAddr stats_addr = 0;
436 DumpIndexHeader(index_name, &stats_addr);
437
438 base::FileEnumerator iter(input_path, false,
439 base::FileEnumerator::FILES,
440 FILE_PATH_LITERAL("data_*"));
441 for (base::FilePath file = iter.Next(); !file.empty(); file = iter.Next())
442 DumpBlockHeader(file);
443
444 DumpStats(input_path, stats_addr);
445 return 0;
446 }
447
448 // Dumps all entries from the cache.
DumpContents(const base::FilePath & input_path)449 int DumpContents(const base::FilePath& input_path) {
450 bool print_csv = base::CommandLine::ForCurrentProcess()->HasSwitch("csv");
451 if (!print_csv)
452 DumpIndexHeader(input_path.Append(kIndexName), nullptr);
453
454 // We need a task executor, although we really don't run any task.
455 base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
456 CacheDumper dumper(input_path);
457 if (!dumper.Init())
458 return -1;
459
460 if (print_csv)
461 PrintCSVHeader();
462
463 disk_cache::EntryStore entry;
464 disk_cache::CacheAddr addr;
465 bool verbose = base::CommandLine::ForCurrentProcess()->HasSwitch("v");
466 while (dumper.GetEntry(&entry, &addr)) {
467 if (!print_csv)
468 DumpEntry(addr, entry, verbose);
469 disk_cache::RankingsNode rankings;
470 if (!dumper.LoadRankings(entry.rankings_node, &rankings))
471 continue;
472
473 if (print_csv)
474 DumpCSV(addr, entry, rankings);
475 else
476 DumpRankings(entry.rankings_node, rankings, verbose);
477 }
478
479 printf("Done.\n");
480
481 return 0;
482 }
483
DumpLists(const base::FilePath & input_path)484 int DumpLists(const base::FilePath& input_path) {
485 base::FilePath index_name(input_path.Append(kIndexName));
486 disk_cache::IndexHeader header;
487 if (!ReadHeader(index_name, reinterpret_cast<char*>(&header), sizeof(header)))
488 return -1;
489
490 // We need a task executor, although we really don't run any task.
491 base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
492 CacheDumper dumper(input_path);
493 if (!dumper.Init())
494 return -1;
495
496 printf("list, addr, next, prev, entry\n");
497
498 const int kMaxLength = 1 * 1000 * 1000;
499 for (int i = 0; i < 5; i++) {
500 int32_t size = header.lru.sizes[i];
501 if (size < 0 || size > kMaxLength) {
502 printf("Wrong size %d\n", size);
503 }
504
505 disk_cache::CacheAddr addr = header.lru.tails[i];
506 int count = 0;
507 while (addr) {
508 count++;
509 disk_cache::RankingsNode rankings;
510 if (!dumper.LoadRankings(addr, &rankings)) {
511 printf("Failed to load node at 0x%x\n", addr);
512 break;
513 }
514 printf("%d, 0x%x, 0x%x, 0x%x, 0x%x\n", i, addr, rankings.next,
515 rankings.prev, rankings.contents);
516
517 if (rankings.prev == addr)
518 break;
519
520 addr = rankings.prev;
521 }
522 printf("%d nodes found, %d reported\n", count, header.lru.sizes[i]);
523 }
524
525 printf("Done.\n");
526 return 0;
527 }
528
DumpEntryAt(const base::FilePath & input_path,const std::string & at)529 int DumpEntryAt(const base::FilePath& input_path, const std::string& at) {
530 disk_cache::CacheAddr addr;
531 if (!base::HexStringToUInt(at, &addr))
532 return -1;
533
534 if (!CanDump(addr))
535 return -1;
536
537 base::FilePath index_name(input_path.Append(kIndexName));
538 disk_cache::IndexHeader header;
539 if (!ReadHeader(index_name, reinterpret_cast<char*>(&header), sizeof(header)))
540 return -1;
541
542 // We need a task executor, although we really don't run any task.
543 base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
544 CacheDumper dumper(input_path);
545 if (!dumper.Init())
546 return -1;
547
548 disk_cache::CacheAddr entry_addr = 0;
549 disk_cache::CacheAddr rankings_addr = 0;
550 disk_cache::Addr address(addr);
551
552 disk_cache::RankingsNode rankings;
553 if (address.file_type() == disk_cache::RANKINGS) {
554 if (dumper.LoadRankings(addr, &rankings)) {
555 rankings_addr = addr;
556 addr = rankings.contents;
557 address = disk_cache::Addr(addr);
558 }
559 }
560
561 disk_cache::EntryStore entry = {};
562 if (address.file_type() == disk_cache::BLOCK_256 &&
563 dumper.LoadEntry(addr, &entry)) {
564 entry_addr = addr;
565 DumpEntry(addr, entry, true);
566 if (!rankings_addr && dumper.LoadRankings(entry.rankings_node, &rankings))
567 rankings_addr = entry.rankings_node;
568 }
569
570 bool verbose = base::CommandLine::ForCurrentProcess()->HasSwitch("v");
571
572 std::string hex_dump;
573 if (!rankings_addr || verbose)
574 dumper.HexDump(addr, &hex_dump);
575
576 if (rankings_addr)
577 DumpRankings(rankings_addr, rankings, true);
578
579 if (entry_addr && verbose) {
580 if (entry.long_key && CanDump(entry.long_key))
581 dumper.HexDump(entry.long_key, &hex_dump);
582
583 for (disk_cache::CacheAddr data_addr : entry.data_addr) {
584 if (data_addr && CanDump(data_addr))
585 dumper.HexDump(data_addr, &hex_dump);
586 }
587 }
588
589 printf("%s\n", hex_dump.c_str());
590 printf("Done.\n");
591 return 0;
592 }
593
DumpAllocation(const base::FilePath & file)594 int DumpAllocation(const base::FilePath& file) {
595 disk_cache::BlockFileHeader header;
596 if (!ReadHeader(file, reinterpret_cast<char*>(&header), sizeof(header)))
597 return -1;
598
599 std::string out;
600 net::ViewCacheHelper::HexDump(reinterpret_cast<char*>(&header.allocation_map),
601 sizeof(header.allocation_map), &out);
602 printf("%s\n", out.c_str());
603 return 0;
604 }
605