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