1 // Copyright 2013 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 #include "net/disk_cache/simple/simple_index_file.h"
6
7 #include <utility>
8 #include <vector>
9
10 #include "base/files/file.h"
11 #include "base/files/file_util.h"
12 #include "base/functional/bind.h"
13 #include "base/logging.h"
14 #include "base/numerics/safe_conversions.h"
15 #include "base/pickle.h"
16 #include "base/strings/string_util.h"
17 #include "base/task/sequenced_task_runner.h"
18 #include "base/task/thread_pool.h"
19 #include "base/threading/thread_restrictions.h"
20 #include "base/time/time.h"
21 #include "build/build_config.h"
22 #include "net/disk_cache/simple/simple_backend_impl.h"
23 #include "net/disk_cache/simple/simple_entry_format.h"
24 #include "net/disk_cache/simple/simple_file_enumerator.h"
25 #include "net/disk_cache/simple/simple_histogram_macros.h"
26 #include "net/disk_cache/simple/simple_index.h"
27 #include "net/disk_cache/simple/simple_synchronous_entry.h"
28 #include "net/disk_cache/simple/simple_util.h"
29
30 namespace disk_cache {
31 namespace {
32
33 const int kEntryFilesHashLength = 16;
34 const int kEntryFilesSuffixLength = 2;
35
36 // Limit on how big a file we are willing to work with, to avoid crashes
37 // when its corrupt.
38 const int kMaxEntriesInIndex = 1000000;
39
40 // Here 8 comes from the key size.
41 const int64_t kMaxIndexFileSizeBytes =
42 kMaxEntriesInIndex * (8 + EntryMetadata::kOnDiskSizeBytes);
43
CalculatePickleCRC(const base::Pickle & pickle)44 uint32_t CalculatePickleCRC(const base::Pickle& pickle) {
45 return simple_util::Crc32(pickle.payload(), pickle.payload_size());
46 }
47
48 // Used in histograms. Please only add new values at the end.
49 enum IndexFileState {
50 INDEX_STATE_CORRUPT = 0,
51 INDEX_STATE_STALE = 1,
52 INDEX_STATE_FRESH = 2,
53 INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3,
54 INDEX_STATE_MAX = 4,
55 };
56
57 enum StaleIndexQuality {
58 STALE_INDEX_OK = 0,
59 STALE_INDEX_MISSED_ENTRIES = 1,
60 STALE_INDEX_EXTRA_ENTRIES = 2,
61 STALE_INDEX_BOTH_MISSED_AND_EXTRA_ENTRIES = 3,
62 STALE_INDEX_MAX = 4,
63 };
64
UmaRecordIndexFileState(IndexFileState state,net::CacheType cache_type)65 void UmaRecordIndexFileState(IndexFileState state, net::CacheType cache_type) {
66 SIMPLE_CACHE_UMA(ENUMERATION,
67 "IndexFileStateOnLoad", cache_type, state, INDEX_STATE_MAX);
68 }
69
UmaRecordIndexInitMethod(SimpleIndex::IndexInitMethod method,net::CacheType cache_type)70 void UmaRecordIndexInitMethod(SimpleIndex::IndexInitMethod method,
71 net::CacheType cache_type) {
72 SIMPLE_CACHE_UMA(ENUMERATION, "IndexInitializeMethod", cache_type, method,
73 SimpleIndex::INITIALIZE_METHOD_MAX);
74 }
75
UmaRecordStaleIndexQuality(int missed_entry_count,int extra_entry_count,net::CacheType cache_type)76 void UmaRecordStaleIndexQuality(int missed_entry_count,
77 int extra_entry_count,
78 net::CacheType cache_type) {
79 SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "StaleIndexMissedEntryCount", cache_type,
80 missed_entry_count, 1, 100, 5);
81 SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "StaleIndexExtraEntryCount", cache_type,
82 extra_entry_count, 1, 100, 5);
83
84 StaleIndexQuality quality;
85 if (missed_entry_count > 0 && extra_entry_count > 0)
86 quality = STALE_INDEX_BOTH_MISSED_AND_EXTRA_ENTRIES;
87 else if (missed_entry_count > 0)
88 quality = STALE_INDEX_MISSED_ENTRIES;
89 else if (extra_entry_count > 0)
90 quality = STALE_INDEX_EXTRA_ENTRIES;
91 else
92 quality = STALE_INDEX_OK;
93 SIMPLE_CACHE_UMA(ENUMERATION, "StaleIndexQuality", cache_type, quality,
94 STALE_INDEX_MAX);
95 }
96
97 struct PickleHeader : public base::Pickle::Header {
98 uint32_t crc;
99 };
100
101 class SimpleIndexPickle : public base::Pickle {
102 public:
SimpleIndexPickle()103 SimpleIndexPickle() : base::Pickle(sizeof(PickleHeader)) {}
SimpleIndexPickle(const char * data,int data_len)104 SimpleIndexPickle(const char* data, int data_len)
105 : base::Pickle(data, data_len) {}
106
HeaderValid() const107 bool HeaderValid() const { return header_size() == sizeof(PickleHeader); }
108 };
109
WritePickleFile(BackendFileOperations * file_operations,base::Pickle * pickle,const base::FilePath & file_name)110 bool WritePickleFile(BackendFileOperations* file_operations,
111 base::Pickle* pickle,
112 const base::FilePath& file_name) {
113 base::File file = file_operations->OpenFile(
114 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
115 base::File::FLAG_WIN_SHARE_DELETE);
116 if (!file.IsValid())
117 return false;
118
119 int bytes_written = file.Write(0, pickle->data_as_char(), pickle->size());
120 if (bytes_written != base::checked_cast<int>(pickle->size())) {
121 file_operations->DeleteFile(
122 file_name,
123 BackendFileOperations::DeleteFileMode::kEnsureImmediateAvailability);
124 return false;
125 }
126 return true;
127 }
128
129 // Called for each cache directory traversal iteration.
ProcessEntryFile(BackendFileOperations * file_operations,net::CacheType cache_type,SimpleIndex::EntrySet * entries,const base::FilePath & file_path,base::Time last_accessed,base::Time last_modified,int64_t size)130 void ProcessEntryFile(BackendFileOperations* file_operations,
131 net::CacheType cache_type,
132 SimpleIndex::EntrySet* entries,
133 const base::FilePath& file_path,
134 base::Time last_accessed,
135 base::Time last_modified,
136 int64_t size) {
137 static const size_t kEntryFilesLength =
138 kEntryFilesHashLength + kEntryFilesSuffixLength;
139 // Converting to std::string is OK since we never use UTF8 wide chars in our
140 // file names.
141 const base::FilePath::StringType base_name = file_path.BaseName().value();
142 const std::string file_name(base_name.begin(), base_name.end());
143
144 // Cleanup any left over doomed entries.
145 if (base::StartsWith(file_name, "todelete_", base::CompareCase::SENSITIVE)) {
146 file_operations->DeleteFile(file_path);
147 return;
148 }
149
150 if (file_name.size() != kEntryFilesLength)
151 return;
152 const auto hash_string = base::MakeStringPiece(
153 file_name.begin(), file_name.begin() + kEntryFilesHashLength);
154 uint64_t hash_key = 0;
155 if (!simple_util::GetEntryHashKeyFromHexString(hash_string, &hash_key)) {
156 LOG(WARNING) << "Invalid entry hash key filename while restoring index from"
157 << " disk: " << file_name;
158 return;
159 }
160
161 base::Time last_used_time;
162 #if BUILDFLAG(IS_POSIX)
163 // For POSIX systems, a last access time is available. However, it's not
164 // guaranteed to be more accurate than mtime. It is no worse though.
165 last_used_time = last_accessed;
166 #endif
167 if (last_used_time.is_null())
168 last_used_time = last_modified;
169
170 auto it = entries->find(hash_key);
171 base::CheckedNumeric<uint32_t> total_entry_size = size;
172
173 // Sometimes we see entry sizes here which are nonsense. We can't use them
174 // as-is, as they simply won't fit the type. The options that come to mind
175 // are:
176 // 1) Ignore the file.
177 // 2) Make something up.
178 // 3) Delete the files for the hash.
179 // ("crash the browser" isn't considered a serious alternative).
180 //
181 // The problem with doing (1) is that we are recovering the index here, so if
182 // we don't include the info on the file here, we may completely lose track of
183 // the entry and never clean the file up.
184 //
185 // (2) is actually mostly fine: we may trigger eviction too soon or too late,
186 // but we can't really do better since we can't trust the size. If the entry
187 // is never opened, it will eventually get evicted. If it is opened, we will
188 // re-check the file size, and if it's nonsense delete it there, and if it's
189 // fine we will fix up the index via a UpdateDataFromEntryStat to have the
190 // correct size.
191 //
192 // (3) does the best thing except when the wrong size is some weird interim
193 // thing just on directory listing (in which case it may evict an entry
194 // prematurely). It's a little harder to think about since it involves
195 // mutating the disk while there are other mutations going on, however,
196 // while (2) is single-threaded.
197 //
198 // Hence this picks (2).
199
200 const int kPlaceHolderSizeWhenInvalid = 32768;
201 if (!total_entry_size.IsValid()) {
202 LOG(WARNING) << "Invalid file size while restoring index from disk: "
203 << size << " on file:" << file_name;
204 }
205
206 if (it == entries->end()) {
207 uint32_t size_to_use =
208 total_entry_size.ValueOrDefault(kPlaceHolderSizeWhenInvalid);
209 if (cache_type == net::APP_CACHE) {
210 SimpleIndex::InsertInEntrySet(
211 hash_key, EntryMetadata(0 /* trailer_prefetch_size */, size_to_use),
212 entries);
213 } else {
214 SimpleIndex::InsertInEntrySet(
215 hash_key, EntryMetadata(last_used_time, size_to_use), entries);
216 }
217 } else {
218 // Summing up the total size of the entry through all the *_[0-1] files
219 total_entry_size += it->second.GetEntrySize();
220 it->second.SetEntrySize(
221 total_entry_size.ValueOrDefault(kPlaceHolderSizeWhenInvalid));
222 }
223 }
224
225 } // namespace
226
227 SimpleIndexLoadResult::SimpleIndexLoadResult() = default;
228
229 SimpleIndexLoadResult::~SimpleIndexLoadResult() = default;
230
Reset()231 void SimpleIndexLoadResult::Reset() {
232 did_load = false;
233 index_write_reason = SimpleIndex::INDEX_WRITE_REASON_MAX;
234 flush_required = false;
235 entries.clear();
236 }
237
238 // static
239 const char SimpleIndexFile::kIndexFileName[] = "the-real-index";
240 // static
241 const char SimpleIndexFile::kIndexDirectory[] = "index-dir";
242 // static
243 const char SimpleIndexFile::kTempIndexFileName[] = "temp-index";
244
IndexMetadata()245 SimpleIndexFile::IndexMetadata::IndexMetadata()
246 : reason_(SimpleIndex::INDEX_WRITE_REASON_MAX),
247 entry_count_(0),
248 cache_size_(0) {}
249
IndexMetadata(SimpleIndex::IndexWriteToDiskReason reason,uint64_t entry_count,uint64_t cache_size)250 SimpleIndexFile::IndexMetadata::IndexMetadata(
251 SimpleIndex::IndexWriteToDiskReason reason,
252 uint64_t entry_count,
253 uint64_t cache_size)
254 : reason_(reason), entry_count_(entry_count), cache_size_(cache_size) {}
255
Serialize(base::Pickle * pickle) const256 void SimpleIndexFile::IndexMetadata::Serialize(base::Pickle* pickle) const {
257 DCHECK(pickle);
258 pickle->WriteUInt64(magic_number_);
259 pickle->WriteUInt32(version_);
260 pickle->WriteUInt64(entry_count_);
261 pickle->WriteUInt64(cache_size_);
262 pickle->WriteUInt32(static_cast<uint32_t>(reason_));
263 }
264
265 // static
SerializeFinalData(base::Time cache_modified,base::Pickle * pickle)266 void SimpleIndexFile::SerializeFinalData(base::Time cache_modified,
267 base::Pickle* pickle) {
268 pickle->WriteInt64(cache_modified.ToInternalValue());
269 PickleHeader* header_p = pickle->headerT<PickleHeader>();
270 header_p->crc = CalculatePickleCRC(*pickle);
271 }
272
Deserialize(base::PickleIterator * it)273 bool SimpleIndexFile::IndexMetadata::Deserialize(base::PickleIterator* it) {
274 DCHECK(it);
275
276 bool v6_format_index_read_results =
277 it->ReadUInt64(&magic_number_) && it->ReadUInt32(&version_) &&
278 it->ReadUInt64(&entry_count_) && it->ReadUInt64(&cache_size_);
279 if (!v6_format_index_read_results)
280 return false;
281 if (version_ >= 7) {
282 uint32_t tmp_reason;
283 if (!it->ReadUInt32(&tmp_reason))
284 return false;
285 reason_ = static_cast<SimpleIndex::IndexWriteToDiskReason>(tmp_reason);
286 }
287 return true;
288 }
289
SyncWriteToDisk(std::unique_ptr<BackendFileOperations> file_operations,net::CacheType cache_type,const base::FilePath & cache_directory,const base::FilePath & index_filename,const base::FilePath & temp_index_filename,std::unique_ptr<base::Pickle> pickle)290 void SimpleIndexFile::SyncWriteToDisk(
291 std::unique_ptr<BackendFileOperations> file_operations,
292 net::CacheType cache_type,
293 const base::FilePath& cache_directory,
294 const base::FilePath& index_filename,
295 const base::FilePath& temp_index_filename,
296 std::unique_ptr<base::Pickle> pickle) {
297 DCHECK_EQ(index_filename.DirName().value(),
298 temp_index_filename.DirName().value());
299 base::FilePath index_file_directory = temp_index_filename.DirName();
300 if (!file_operations->DirectoryExists(index_file_directory) &&
301 !file_operations->CreateDirectory(index_file_directory)) {
302 LOG(ERROR) << "Could not create a directory to hold the index file";
303 return;
304 }
305
306 // There is a chance that the index containing all the necessary data about
307 // newly created entries will appear to be stale. This can happen if on-disk
308 // part of a Create operation does not fit into the time budget for the index
309 // flush delay. This simple approach will be reconsidered if it does not allow
310 // for maintaining freshness.
311 base::Time cache_dir_mtime;
312 absl::optional<base::File::Info> file_info =
313 file_operations->GetFileInfo(cache_directory);
314 if (!file_info) {
315 LOG(ERROR) << "Could not obtain information about cache age";
316 return;
317 }
318 cache_dir_mtime = file_info->last_modified;
319 SerializeFinalData(cache_dir_mtime, pickle.get());
320 if (!WritePickleFile(file_operations.get(), pickle.get(),
321 temp_index_filename)) {
322 LOG(ERROR) << "Failed to write the temporary index file";
323 return;
324 }
325
326 // Atomically rename the temporary index file to become the real one.
327 if (!file_operations->ReplaceFile(temp_index_filename, index_filename,
328 nullptr)) {
329 return;
330 }
331 }
332
CheckIndexMetadata()333 bool SimpleIndexFile::IndexMetadata::CheckIndexMetadata() {
334 if (entry_count_ > kMaxEntriesInIndex ||
335 magic_number_ != kSimpleIndexMagicNumber) {
336 return false;
337 }
338
339 static_assert(kSimpleVersion == 9, "index metadata reader out of date");
340 // No |reason_| is saved in the version 6 file format.
341 if (version_ == 6)
342 return reason_ == SimpleIndex::INDEX_WRITE_REASON_MAX;
343 return (version_ == 7 || version_ == 8 || version_ == 9) &&
344 reason_ < SimpleIndex::INDEX_WRITE_REASON_MAX;
345 }
346
SimpleIndexFile(scoped_refptr<base::SequencedTaskRunner> cache_runner,scoped_refptr<BackendFileOperationsFactory> file_operations_factory,net::CacheType cache_type,const base::FilePath & cache_directory)347 SimpleIndexFile::SimpleIndexFile(
348 scoped_refptr<base::SequencedTaskRunner> cache_runner,
349 scoped_refptr<BackendFileOperationsFactory> file_operations_factory,
350 net::CacheType cache_type,
351 const base::FilePath& cache_directory)
352 : cache_runner_(std::move(cache_runner)),
353 file_operations_factory_(std::move(file_operations_factory)),
354 cache_type_(cache_type),
355 cache_directory_(cache_directory),
356 index_file_(cache_directory_.AppendASCII(kIndexDirectory)
357 .AppendASCII(kIndexFileName)),
358 temp_index_file_(cache_directory_.AppendASCII(kIndexDirectory)
359 .AppendASCII(kTempIndexFileName)) {}
360
361 SimpleIndexFile::~SimpleIndexFile() = default;
362
LoadIndexEntries(base::Time cache_last_modified,base::OnceClosure callback,SimpleIndexLoadResult * out_result)363 void SimpleIndexFile::LoadIndexEntries(base::Time cache_last_modified,
364 base::OnceClosure callback,
365 SimpleIndexLoadResult* out_result) {
366 auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
367 SimpleBackendImpl::kWorkerPoolTaskTraits);
368 base::OnceClosure task = base::BindOnce(
369 &SimpleIndexFile::SyncLoadIndexEntries,
370 file_operations_factory_->Create(task_runner), cache_type_,
371 cache_last_modified, cache_directory_, index_file_, out_result);
372 task_runner->PostTaskAndReply(FROM_HERE, std::move(task),
373 std::move(callback));
374 }
375
WriteToDisk(net::CacheType cache_type,SimpleIndex::IndexWriteToDiskReason reason,const SimpleIndex::EntrySet & entry_set,uint64_t cache_size,base::OnceClosure callback)376 void SimpleIndexFile::WriteToDisk(net::CacheType cache_type,
377 SimpleIndex::IndexWriteToDiskReason reason,
378 const SimpleIndex::EntrySet& entry_set,
379 uint64_t cache_size,
380 base::OnceClosure callback) {
381 IndexMetadata index_metadata(reason, entry_set.size(), cache_size);
382 std::unique_ptr<base::Pickle> pickle =
383 Serialize(cache_type, index_metadata, entry_set);
384 auto file_operations = file_operations_factory_->Create(cache_runner_);
385 base::OnceClosure task =
386 base::BindOnce(&SimpleIndexFile::SyncWriteToDisk,
387 std::move(file_operations), cache_type_, cache_directory_,
388 index_file_, temp_index_file_, std::move(pickle));
389 if (callback.is_null())
390 cache_runner_->PostTask(FROM_HERE, std::move(task));
391 else
392 cache_runner_->PostTaskAndReply(FROM_HERE, std::move(task),
393 std::move(callback));
394 }
395
396 // static
SyncLoadIndexEntries(std::unique_ptr<BackendFileOperations> file_operations,net::CacheType cache_type,base::Time cache_last_modified,const base::FilePath & cache_directory,const base::FilePath & index_file_path,SimpleIndexLoadResult * out_result)397 void SimpleIndexFile::SyncLoadIndexEntries(
398 std::unique_ptr<BackendFileOperations> file_operations,
399 net::CacheType cache_type,
400 base::Time cache_last_modified,
401 const base::FilePath& cache_directory,
402 const base::FilePath& index_file_path,
403 SimpleIndexLoadResult* out_result) {
404 // Load the index and find its age.
405 base::Time last_cache_seen_by_index;
406 SyncLoadFromDisk(file_operations.get(), cache_type, index_file_path,
407 &last_cache_seen_by_index, out_result);
408
409 // Consider the index loaded if it is fresh.
410 const bool index_file_existed = file_operations->PathExists(index_file_path);
411 if (!out_result->did_load) {
412 if (index_file_existed)
413 UmaRecordIndexFileState(INDEX_STATE_CORRUPT, cache_type);
414 } else {
415 if (cache_last_modified <= last_cache_seen_by_index) {
416 base::Time latest_dir_mtime;
417 if (auto info = file_operations->GetFileInfo(cache_directory)) {
418 latest_dir_mtime = info->last_modified;
419 }
420 if (LegacyIsIndexFileStale(file_operations.get(), latest_dir_mtime,
421 index_file_path)) {
422 UmaRecordIndexFileState(INDEX_STATE_FRESH_CONCURRENT_UPDATES,
423 cache_type);
424 } else {
425 UmaRecordIndexFileState(INDEX_STATE_FRESH, cache_type);
426 }
427 out_result->init_method = SimpleIndex::INITIALIZE_METHOD_LOADED;
428 UmaRecordIndexInitMethod(out_result->init_method, cache_type);
429 return;
430 }
431 UmaRecordIndexFileState(INDEX_STATE_STALE, cache_type);
432 }
433
434 // Reconstruct the index by scanning the disk for entries.
435 SimpleIndex::EntrySet entries_from_stale_index;
436 entries_from_stale_index.swap(out_result->entries);
437 const base::TimeTicks start = base::TimeTicks::Now();
438 SyncRestoreFromDisk(file_operations.get(), cache_type, cache_directory,
439 index_file_path, out_result);
440 SIMPLE_CACHE_UMA(MEDIUM_TIMES, "IndexRestoreTime", cache_type,
441 base::TimeTicks::Now() - start);
442 if (index_file_existed) {
443 out_result->init_method = SimpleIndex::INITIALIZE_METHOD_RECOVERED;
444
445 int missed_entry_count = 0;
446 for (const auto& i : out_result->entries) {
447 if (entries_from_stale_index.count(i.first) == 0)
448 ++missed_entry_count;
449 }
450 int extra_entry_count = 0;
451 for (const auto& i : entries_from_stale_index) {
452 if (out_result->entries.count(i.first) == 0)
453 ++extra_entry_count;
454 }
455 UmaRecordStaleIndexQuality(missed_entry_count, extra_entry_count,
456 cache_type);
457 } else {
458 out_result->init_method = SimpleIndex::INITIALIZE_METHOD_NEWCACHE;
459 SIMPLE_CACHE_UMA(COUNTS_1M,
460 "IndexCreatedEntryCount", cache_type,
461 out_result->entries.size());
462 }
463 UmaRecordIndexInitMethod(out_result->init_method, cache_type);
464 }
465
466 // static
SyncLoadFromDisk(BackendFileOperations * file_operations,net::CacheType cache_type,const base::FilePath & index_filename,base::Time * out_last_cache_seen_by_index,SimpleIndexLoadResult * out_result)467 void SimpleIndexFile::SyncLoadFromDisk(BackendFileOperations* file_operations,
468 net::CacheType cache_type,
469 const base::FilePath& index_filename,
470 base::Time* out_last_cache_seen_by_index,
471 SimpleIndexLoadResult* out_result) {
472 out_result->Reset();
473
474 base::File file = file_operations->OpenFile(
475 index_filename, base::File::FLAG_OPEN | base::File::FLAG_READ |
476 base::File::FLAG_WIN_SHARE_DELETE |
477 base::File::FLAG_WIN_SEQUENTIAL_SCAN);
478 if (!file.IsValid())
479 return;
480
481 // Sanity-check the length. We don't want to crash trying to read some corrupt
482 // 10GiB file or such.
483 int64_t file_length = file.GetLength();
484 if (file_length < 0 || file_length > kMaxIndexFileSizeBytes) {
485 file_operations->DeleteFile(
486 index_filename,
487 BackendFileOperations::DeleteFileMode::kEnsureImmediateAvailability);
488 return;
489 }
490
491 // Make sure to preallocate in one chunk, so we don't induce fragmentation
492 // reallocating a growing buffer.
493 auto buffer = std::make_unique<char[]>(file_length);
494
495 int read = file.Read(0, buffer.get(), file_length);
496 if (read < file_length) {
497 file_operations->DeleteFile(
498 index_filename,
499 BackendFileOperations::DeleteFileMode::kEnsureImmediateAvailability);
500 return;
501 }
502
503 SimpleIndexFile::Deserialize(cache_type, buffer.get(), read,
504 out_last_cache_seen_by_index, out_result);
505
506 if (!out_result->did_load) {
507 file_operations->DeleteFile(
508 index_filename,
509 BackendFileOperations::DeleteFileMode::kEnsureImmediateAvailability);
510 }
511 }
512
513 // static
Serialize(net::CacheType cache_type,const SimpleIndexFile::IndexMetadata & index_metadata,const SimpleIndex::EntrySet & entries)514 std::unique_ptr<base::Pickle> SimpleIndexFile::Serialize(
515 net::CacheType cache_type,
516 const SimpleIndexFile::IndexMetadata& index_metadata,
517 const SimpleIndex::EntrySet& entries) {
518 std::unique_ptr<base::Pickle> pickle = std::make_unique<SimpleIndexPickle>();
519
520 index_metadata.Serialize(pickle.get());
521 for (const auto& entry : entries) {
522 pickle->WriteUInt64(entry.first);
523 entry.second.Serialize(cache_type, pickle.get());
524 }
525 return pickle;
526 }
527
528 // static
Deserialize(net::CacheType cache_type,const char * data,int data_len,base::Time * out_cache_last_modified,SimpleIndexLoadResult * out_result)529 void SimpleIndexFile::Deserialize(net::CacheType cache_type,
530 const char* data,
531 int data_len,
532 base::Time* out_cache_last_modified,
533 SimpleIndexLoadResult* out_result) {
534 DCHECK(data);
535
536 out_result->Reset();
537 SimpleIndex::EntrySet* entries = &out_result->entries;
538
539 SimpleIndexPickle pickle(data, data_len);
540 if (!pickle.data() || !pickle.HeaderValid()) {
541 LOG(WARNING) << "Corrupt Simple Index File.";
542 return;
543 }
544
545 base::PickleIterator pickle_it(pickle);
546 PickleHeader* header_p = pickle.headerT<PickleHeader>();
547 const uint32_t crc_read = header_p->crc;
548 const uint32_t crc_calculated = CalculatePickleCRC(pickle);
549
550 if (crc_read != crc_calculated) {
551 LOG(WARNING) << "Invalid CRC in Simple Index file.";
552 return;
553 }
554
555 SimpleIndexFile::IndexMetadata index_metadata;
556 if (!index_metadata.Deserialize(&pickle_it)) {
557 LOG(ERROR) << "Invalid index_metadata on Simple Cache Index.";
558 return;
559 }
560
561 if (!index_metadata.CheckIndexMetadata()) {
562 LOG(ERROR) << "Invalid index_metadata on Simple Cache Index.";
563 return;
564 }
565
566 entries->reserve(index_metadata.entry_count() + kExtraSizeForMerge);
567 while (entries->size() < index_metadata.entry_count()) {
568 uint64_t hash_key;
569 EntryMetadata entry_metadata;
570 if (!pickle_it.ReadUInt64(&hash_key) ||
571 !entry_metadata.Deserialize(
572 cache_type, &pickle_it, index_metadata.has_entry_in_memory_data(),
573 index_metadata.app_cache_has_trailer_prefetch_size())) {
574 LOG(WARNING) << "Invalid EntryMetadata in Simple Index file.";
575 entries->clear();
576 return;
577 }
578 SimpleIndex::InsertInEntrySet(hash_key, entry_metadata, entries);
579 }
580
581 int64_t cache_last_modified;
582 if (!pickle_it.ReadInt64(&cache_last_modified)) {
583 entries->clear();
584 return;
585 }
586 DCHECK(out_cache_last_modified);
587 *out_cache_last_modified = base::Time::FromInternalValue(cache_last_modified);
588
589 out_result->index_write_reason = index_metadata.reason();
590 out_result->did_load = true;
591 }
592
593 // static
SyncRestoreFromDisk(BackendFileOperations * file_operations,net::CacheType cache_type,const base::FilePath & cache_directory,const base::FilePath & index_file_path,SimpleIndexLoadResult * out_result)594 void SimpleIndexFile::SyncRestoreFromDisk(
595 BackendFileOperations* file_operations,
596 net::CacheType cache_type,
597 const base::FilePath& cache_directory,
598 const base::FilePath& index_file_path,
599 SimpleIndexLoadResult* out_result) {
600 VLOG(1) << "Simple Cache Index is being restored from disk.";
601 file_operations->DeleteFile(
602 index_file_path,
603 BackendFileOperations::DeleteFileMode::kEnsureImmediateAvailability);
604 out_result->Reset();
605 SimpleIndex::EntrySet* entries = &out_result->entries;
606
607 auto enumerator = file_operations->EnumerateFiles(cache_directory);
608 while (absl::optional<SimpleFileEnumerator::Entry> entry =
609 enumerator->Next()) {
610 ProcessEntryFile(file_operations, cache_type, entries, entry->path,
611 entry->last_accessed, entry->last_modified, entry->size);
612 }
613 if (enumerator->HasError()) {
614 LOG(ERROR) << "Could not reconstruct index from disk";
615 return;
616 }
617 out_result->did_load = true;
618 // When we restore from disk we write the merged index file to disk right
619 // away, this might save us from having to restore again next time.
620 out_result->flush_required = true;
621 }
622
623 // static
LegacyIsIndexFileStale(BackendFileOperations * file_operations,base::Time cache_last_modified,const base::FilePath & index_file_path)624 bool SimpleIndexFile::LegacyIsIndexFileStale(
625 BackendFileOperations* file_operations,
626 base::Time cache_last_modified,
627 const base::FilePath& index_file_path) {
628 if (auto info = file_operations->GetFileInfo(index_file_path)) {
629 return info->last_modified < cache_last_modified;
630 }
631 return true;
632 }
633
634 } // namespace disk_cache
635