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