1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
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
9 #include "net/tools/dump_cache/dump_files.h"
16 #include "base/command_line.h"
17 #include "base/files/file.h"
18 #include "base/files/file_enumerator.h"
19 #include "base/files/file_util.h"
20 #include "base/format_macros.h"
21 #include "base/message_loop/message_loop.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/stringprintf.h"
24 #include "net/disk_cache/blockfile/block_files.h"
25 #include "net/disk_cache/blockfile/disk_format.h"
26 #include "net/disk_cache/blockfile/mapped_file.h"
27 #include "net/disk_cache/blockfile/stats.h"
28 #include "net/disk_cache/blockfile/storage_block-inl.h"
29 #include "net/disk_cache/blockfile/storage_block.h"
30 #include "net/url_request/view_cache_helper.h"
34 const base::FilePath::CharType kIndexName
[] = FILE_PATH_LITERAL("index");
36 // Reads the |header_size| bytes from the beginning of file |name|.
37 bool ReadHeader(const base::FilePath
& name
, char* header
, int header_size
) {
38 base::File
file(name
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
39 if (!file
.IsValid()) {
40 printf("Unable to open file %s\n", name
.MaybeAsASCII().c_str());
44 int read
= file
.Read(0, header
, header_size
);
45 if (read
!= header_size
) {
46 printf("Unable to read file %s\n", name
.MaybeAsASCII().c_str());
52 int GetMajorVersionFromFile(const base::FilePath
& name
) {
53 disk_cache::IndexHeader header
;
54 if (!ReadHeader(name
, reinterpret_cast<char*>(&header
), sizeof(header
)))
57 return header
.version
>> 16;
60 // Dumps the contents of the Stats record.
61 void DumpStats(const base::FilePath
& path
, disk_cache::CacheAddr addr
) {
62 // We need a message loop, although we really don't run any task.
63 base::MessageLoopForIO loop
;
65 disk_cache::BlockFiles
block_files(path
);
66 if (!block_files
.Init(false)) {
67 printf("Unable to init block files\n");
71 disk_cache::Addr
address(addr
);
72 disk_cache::MappedFile
* file
= block_files
.GetFile(address
);
76 size_t length
= (2 + disk_cache::Stats::kDataSizesLength
) * sizeof(int32
) +
77 disk_cache::Stats::MAX_COUNTER
* sizeof(int64
);
79 size_t offset
= address
.start_block() * address
.BlockSize() +
80 disk_cache::kBlockHeaderSize
;
82 scoped_ptr
<int32
[]> buffer(new int32
[length
]);
83 if (!file
->Read(buffer
.get(), length
, offset
))
86 printf("Stats:\nSignatrure: 0x%x\n", buffer
[0]);
87 printf("Total size: %d\n", buffer
[1]);
88 for (int i
= 0; i
< disk_cache::Stats::kDataSizesLength
; i
++)
89 printf("Size(%d): %d\n", i
, buffer
[i
+ 2]);
91 int64
* counters
= reinterpret_cast<int64
*>(
92 buffer
.get() + 2 + disk_cache::Stats::kDataSizesLength
);
93 for (int i
= 0; i
< disk_cache::Stats::MAX_COUNTER
; i
++)
94 printf("Count(%d): %" PRId64
"\n", i
, *counters
++);
95 printf("-------------------------\n\n");
98 // Dumps the contents of the Index-file header.
99 void DumpIndexHeader(const base::FilePath
& name
,
100 disk_cache::CacheAddr
* stats_addr
) {
101 disk_cache::IndexHeader header
;
102 if (!ReadHeader(name
, reinterpret_cast<char*>(&header
), sizeof(header
)))
105 printf("Index file:\n");
106 printf("magic: %x\n", header
.magic
);
107 printf("version: %d.%d\n", header
.version
>> 16, header
.version
& 0xffff);
108 printf("entries: %d\n", header
.num_entries
);
109 printf("total bytes: %d\n", header
.num_bytes
);
110 printf("last file number: %d\n", header
.last_file
);
111 printf("current id: %d\n", header
.this_id
);
112 printf("table length: %d\n", header
.table_len
);
113 printf("last crash: %d\n", header
.crash
);
114 printf("experiment: %d\n", header
.experiment
);
115 printf("stats: %x\n", header
.stats
);
116 for (int i
= 0; i
< 5; i
++) {
117 printf("head %d: 0x%x\n", i
, header
.lru
.heads
[i
]);
118 printf("tail %d: 0x%x\n", i
, header
.lru
.tails
[i
]);
119 printf("size %d: 0x%x\n", i
, header
.lru
.sizes
[i
]);
121 printf("transaction: 0x%x\n", header
.lru
.transaction
);
122 printf("operation: %d\n", header
.lru
.operation
);
123 printf("operation list: %d\n", header
.lru
.operation_list
);
124 printf("-------------------------\n\n");
127 *stats_addr
= header
.stats
;
130 // Dumps the contents of a block-file header.
131 void DumpBlockHeader(const base::FilePath
& name
) {
132 disk_cache::BlockFileHeader header
;
133 if (!ReadHeader(name
, reinterpret_cast<char*>(&header
), sizeof(header
)))
136 printf("Block file: %s\n", name
.BaseName().MaybeAsASCII().c_str());
137 printf("magic: %x\n", header
.magic
);
138 printf("version: %d.%d\n", header
.version
>> 16, header
.version
& 0xffff);
139 printf("file id: %d\n", header
.this_file
);
140 printf("next file id: %d\n", header
.next_file
);
141 printf("entry size: %d\n", header
.entry_size
);
142 printf("current entries: %d\n", header
.num_entries
);
143 printf("max entries: %d\n", header
.max_entries
);
144 printf("updating: %d\n", header
.updating
);
145 printf("empty sz 1: %d\n", header
.empty
[0]);
146 printf("empty sz 2: %d\n", header
.empty
[1]);
147 printf("empty sz 3: %d\n", header
.empty
[2]);
148 printf("empty sz 4: %d\n", header
.empty
[3]);
149 printf("user 0: 0x%x\n", header
.user
[0]);
150 printf("user 1: 0x%x\n", header
.user
[1]);
151 printf("user 2: 0x%x\n", header
.user
[2]);
152 printf("user 3: 0x%x\n", header
.user
[3]);
153 printf("-------------------------\n\n");
156 // Simple class that interacts with the set of cache files.
159 explicit CacheDumper(const base::FilePath
& path
)
169 // Reads an entry from disk. Return false when all entries have been already
171 bool GetEntry(disk_cache::EntryStore
* entry
, disk_cache::CacheAddr
* addr
);
173 // Loads a specific block from the block files.
174 bool LoadEntry(disk_cache::CacheAddr addr
, disk_cache::EntryStore
* entry
);
175 bool LoadRankings(disk_cache::CacheAddr addr
,
176 disk_cache::RankingsNode
* rankings
);
178 // Appends the data store at |addr| to |out|.
179 bool HexDump(disk_cache::CacheAddr addr
, std::string
* out
);
182 base::FilePath path_
;
183 disk_cache::BlockFiles block_files_
;
184 scoped_refptr
<disk_cache::MappedFile
> index_file_
;
185 disk_cache::Index
* index_
;
187 disk_cache::CacheAddr next_addr_
;
188 std::set
<disk_cache::CacheAddr
> dumped_entries_
;
189 DISALLOW_COPY_AND_ASSIGN(CacheDumper
);
192 bool CacheDumper::Init() {
193 if (!block_files_
.Init(false)) {
194 printf("Unable to init block files\n");
198 base::FilePath
index_name(path_
.Append(kIndexName
));
199 index_file_
= new disk_cache::MappedFile
;
200 index_
= reinterpret_cast<disk_cache::Index
*>(
201 index_file_
->Init(index_name
, 0));
203 printf("Unable to map index\n");
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");
220 if (LoadEntry(next_addr_
, entry
))
223 printf("Unable to load entry at address 0x%x\n", next_addr_
);
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
]) {
233 *addr
= index_
->table
[i
];
234 if (LoadEntry(index_
->table
[i
], entry
))
237 printf("Unable to load entry at address 0x%x\n", index_
->table
[i
]);
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
);
250 disk_cache::StorageBlock
<disk_cache::EntryStore
> entry_block(file
, address
);
251 if (!entry_block
.Load())
254 memcpy(entry
, entry_block
.Data(), sizeof(*entry
));
255 if (!entry_block
.VerifyHash())
256 printf("Self hash failed at 0x%x\n", addr
);
258 // Prepare for the next entry to load.
259 next_addr_
= entry
->next
;
261 dumped_entries_
.insert(addr
);
264 dumped_entries_
.clear();
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
)
275 disk_cache::MappedFile
* file
= block_files_
.GetFile(address
);
279 disk_cache::StorageBlock
<disk_cache::RankingsNode
> rank_block(file
, address
);
280 if (!rank_block
.Load())
283 if (!rank_block
.VerifyHash())
284 printf("Self hash failed at 0x%x\n", addr
);
286 memcpy(rankings
, rank_block
.Data(), sizeof(*rankings
));
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
);
296 size_t size
= address
.num_blocks() * address
.BlockSize();
297 scoped_ptr
<char> buffer(new char[size
]);
299 size_t offset
= address
.start_block() * address
.BlockSize() +
300 disk_cache::kBlockHeaderSize
;
301 if (!file
->Read(buffer
.get(), size
, offset
))
304 base::StringAppendF(out
, "0x%x:\n", addr
);
305 net::ViewCacheHelper::HexDump(buffer
.get(), size
, out
);
309 std::string
ToLocalTime(int64 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
,
318 void DumpEntry(disk_cache::CacheAddr addr
,
319 const disk_cache::EntryStore
& entry
,
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
),
327 if (entry
.key_len
> 90 && !full_key
)
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());
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
]);
348 printf("----------\n\n");
352 void DumpRankings(disk_cache::CacheAddr addr
,
353 const disk_cache::RankingsNode
& rankings
,
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
);
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");
372 void PrintCSVHeader() {
374 "entry,rankings,next,prev,rank-contents,chain,reuse,key,"
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]);
386 if (addr
!= rankings
.contents
)
387 printf("Broken entry\n");
390 bool CanDump(disk_cache::CacheAddr addr
) {
391 disk_cache::Addr
address(addr
);
392 return address
.is_initialized() && address
.is_block_file();
397 // -----------------------------------------------------------------------
399 int GetMajorVersion(const base::FilePath
& input_path
) {
400 base::FilePath
index_name(input_path
.Append(kIndexName
));
402 int version
= GetMajorVersionFromFile(index_name
);
406 base::FilePath
data_name(input_path
.Append(FILE_PATH_LITERAL("data_0")));
407 if (version
!= GetMajorVersionFromFile(data_name
))
410 data_name
= input_path
.Append(FILE_PATH_LITERAL("data_1"));
411 if (version
!= GetMajorVersionFromFile(data_name
))
414 data_name
= input_path
.Append(FILE_PATH_LITERAL("data_2"));
415 if (version
!= GetMajorVersionFromFile(data_name
))
418 data_name
= input_path
.Append(FILE_PATH_LITERAL("data_3"));
419 if (version
!= GetMajorVersionFromFile(data_name
))
425 // Dumps the headers of all files.
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
);
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
);
437 DumpStats(input_path
, stats_addr
);
441 // Dumps all entries from the cache.
442 int DumpContents(const base::FilePath
& input_path
) {
443 bool print_csv
= base::CommandLine::ForCurrentProcess()->HasSwitch("csv");
445 DumpIndexHeader(input_path
.Append(kIndexName
), nullptr);
447 // We need a message loop, although we really don't run any task.
448 base::MessageLoopForIO loop
;
449 CacheDumper
dumper(input_path
);
456 disk_cache::EntryStore entry
;
457 disk_cache::CacheAddr addr
;
458 bool verbose
= base::CommandLine::ForCurrentProcess()->HasSwitch("v");
459 while (dumper
.GetEntry(&entry
, &addr
)) {
461 DumpEntry(addr
, entry
, verbose
);
462 disk_cache::RankingsNode rankings
;
463 if (!dumper
.LoadRankings(entry
.rankings_node
, &rankings
))
467 DumpCSV(addr
, entry
, rankings
);
469 DumpRankings(entry
.rankings_node
, rankings
, verbose
);
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
)))
483 // We need a message loop, although we really don't run any task.
484 base::MessageLoopForIO loop
;
485 CacheDumper
dumper(input_path
);
489 printf("list, addr, next, prev, entry\n");
491 const int kMaxLength
= 1 * 1000 * 1000;
492 for (int i
= 0; i
< 5; i
++) {
493 int32 size
= header
.lru
.sizes
[i
];
494 if (size
< 0 || size
> kMaxLength
) {
495 printf("Wrong size %d\n", size
);
499 disk_cache::CacheAddr addr
= header
.lru
.tails
[i
];
501 for (; size
&& addr
; size
--) {
503 disk_cache::RankingsNode rankings
;
504 if (!dumper
.LoadRankings(addr
, &rankings
)) {
505 printf("Failed to load node at 0x%x\n", addr
);
508 printf("%d, 0x%x, 0x%x, 0x%x, 0x%x\n", i
, addr
, rankings
.next
,
509 rankings
.prev
, rankings
.contents
);
511 if (rankings
.prev
== addr
)
514 addr
= rankings
.prev
;
516 printf("%d nodes found, %d reported\n", count
, header
.lru
.sizes
[i
]);
523 int DumpEntryAt(const base::FilePath
& input_path
, const std::string
& at
) {
524 disk_cache::CacheAddr addr
;
525 if (!base::HexStringToUInt(at
, &addr
))
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
)))
536 // We need a message loop, although we really don't run any task.
537 base::MessageLoopForIO loop
;
538 CacheDumper
dumper(input_path
);
542 disk_cache::CacheAddr entry_addr
= 0;
543 disk_cache::CacheAddr rankings_addr
= 0;
544 disk_cache::Addr
address(addr
);
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
);
555 disk_cache::EntryStore entry
= {};
556 if (address
.file_type() == disk_cache::BLOCK_256
&&
557 dumper
.LoadEntry(addr
, &entry
)) {
559 DumpEntry(addr
, entry
, true);
560 if (!rankings_addr
&& dumper
.LoadRankings(entry
.rankings_node
, &rankings
))
561 rankings_addr
= entry
.rankings_node
;
564 bool verbose
= base::CommandLine::ForCurrentProcess()->HasSwitch("v");
566 std::string hex_dump
;
567 if (!rankings_addr
|| verbose
)
568 dumper
.HexDump(addr
, &hex_dump
);
571 DumpRankings(rankings_addr
, rankings
, true);
573 if (entry_addr
&& verbose
) {
574 if (entry
.long_key
&& CanDump(entry
.long_key
))
575 dumper
.HexDump(entry
.long_key
, &hex_dump
);
577 for (int i
= 0; i
< 4; i
++) {
578 if (entry
.data_addr
[i
] && CanDump(entry
.data_addr
[i
]))
579 dumper
.HexDump(entry
.data_addr
[i
], &hex_dump
);
583 printf("%s\n", hex_dump
.c_str());
588 int DumpAllocation(const base::FilePath
& file
) {
589 disk_cache::BlockFileHeader header
;
590 if (!ReadHeader(file
, reinterpret_cast<char*>(&header
), sizeof(header
)))
594 net::ViewCacheHelper::HexDump(reinterpret_cast<char*>(&header
.allocation_map
),
595 sizeof(header
.allocation_map
), &out
);
596 printf("%s\n", out
.c_str());