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