• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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