| // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "net/disk_cache/simple/simple_index_file.h" | 
 |  | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "base/files/file.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/files/memory_mapped_file.h" | 
 | #include "base/hash.h" | 
 | #include "base/logging.h" | 
 | #include "base/numerics/safe_conversions.h" | 
 | #include "base/pickle.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/task_runner_util.h" | 
 | #include "base/threading/thread_restrictions.h" | 
 | #include "net/disk_cache/simple/simple_backend_version.h" | 
 | #include "net/disk_cache/simple/simple_entry_format.h" | 
 | #include "net/disk_cache/simple/simple_histogram_macros.h" | 
 | #include "net/disk_cache/simple/simple_index.h" | 
 | #include "net/disk_cache/simple/simple_synchronous_entry.h" | 
 | #include "net/disk_cache/simple/simple_util.h" | 
 | #include "starboard/file.h" | 
 |  | 
 | namespace disk_cache { | 
 | namespace { | 
 |  | 
 | const int kEntryFilesHashLength = 16; | 
 | const int kEntryFilesSuffixLength = 2; | 
 |  | 
 | // Limit on how big a file we are willing to work with, to avoid crashes | 
 | // when its corrupt. | 
 | const int kMaxEntriesInIndex = 1000000; | 
 |  | 
 | // Here 8 comes from the key size. | 
 | const int64_t kMaxIndexFileSizeBytes = | 
 |     kMaxEntriesInIndex * (8 + EntryMetadata::kOnDiskSizeBytes); | 
 |  | 
 | uint32_t CalculatePickleCRC(const base::Pickle& pickle) { | 
 |   return simple_util::Crc32(pickle.payload(), pickle.payload_size()); | 
 | } | 
 |  | 
 | // Used in histograms. Please only add new values at the end. | 
 | enum IndexFileState { | 
 |   INDEX_STATE_CORRUPT = 0, | 
 |   INDEX_STATE_STALE = 1, | 
 |   INDEX_STATE_FRESH = 2, | 
 |   INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3, | 
 |   INDEX_STATE_MAX = 4, | 
 | }; | 
 |  | 
 | enum StaleIndexQuality { | 
 |   STALE_INDEX_OK = 0, | 
 |   STALE_INDEX_MISSED_ENTRIES = 1, | 
 |   STALE_INDEX_EXTRA_ENTRIES = 2, | 
 |   STALE_INDEX_BOTH_MISSED_AND_EXTRA_ENTRIES = 3, | 
 |   STALE_INDEX_MAX = 4, | 
 | }; | 
 |  | 
 | void UmaRecordIndexFileState(IndexFileState state, net::CacheType cache_type) { | 
 |   SIMPLE_CACHE_UMA(ENUMERATION, | 
 |                    "IndexFileStateOnLoad", cache_type, state, INDEX_STATE_MAX); | 
 | } | 
 |  | 
 | void UmaRecordIndexInitMethod(SimpleIndex::IndexInitMethod method, | 
 |                               net::CacheType cache_type) { | 
 |   SIMPLE_CACHE_UMA(ENUMERATION, "IndexInitializeMethod", cache_type, method, | 
 |                    SimpleIndex::INITIALIZE_METHOD_MAX); | 
 | } | 
 |  | 
 | void UmaRecordIndexWriteReason(SimpleIndex::IndexWriteToDiskReason reason, | 
 |                                net::CacheType cache_type) { | 
 |   SIMPLE_CACHE_UMA(ENUMERATION, "IndexWriteReason", cache_type, reason, | 
 |                    SimpleIndex::INDEX_WRITE_REASON_MAX); | 
 | } | 
 |  | 
 | void UmaRecordIndexWriteReasonAtLoad(SimpleIndex::IndexWriteToDiskReason reason, | 
 |                                      net::CacheType cache_type) { | 
 |   SIMPLE_CACHE_UMA(ENUMERATION, "IndexWriteReasonAtLoad", cache_type, reason, | 
 |                    SimpleIndex::INDEX_WRITE_REASON_MAX); | 
 | } | 
 |  | 
 | void UmaRecordStaleIndexQuality(int missed_entry_count, | 
 |                                 int extra_entry_count, | 
 |                                 net::CacheType cache_type) { | 
 |   SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "StaleIndexMissedEntryCount", cache_type, | 
 |                    missed_entry_count, 1, 100, 5); | 
 |   SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "StaleIndexExtraEntryCount", cache_type, | 
 |                    extra_entry_count, 1, 100, 5); | 
 |  | 
 |   StaleIndexQuality quality; | 
 |   if (missed_entry_count > 0 && extra_entry_count > 0) | 
 |     quality = STALE_INDEX_BOTH_MISSED_AND_EXTRA_ENTRIES; | 
 |   else if (missed_entry_count > 0) | 
 |     quality = STALE_INDEX_MISSED_ENTRIES; | 
 |   else if (extra_entry_count > 0) | 
 |     quality = STALE_INDEX_EXTRA_ENTRIES; | 
 |   else | 
 |     quality = STALE_INDEX_OK; | 
 |   SIMPLE_CACHE_UMA(ENUMERATION, "StaleIndexQuality", cache_type, quality, | 
 |                    STALE_INDEX_MAX); | 
 | } | 
 |  | 
 | struct PickleHeader : public base::Pickle::Header { | 
 |   uint32_t crc; | 
 | }; | 
 |  | 
 | class SimpleIndexPickle : public base::Pickle { | 
 |  public: | 
 |   SimpleIndexPickle() : base::Pickle(sizeof(PickleHeader)) {} | 
 |   SimpleIndexPickle(const char* data, int data_len) | 
 |       : base::Pickle(data, data_len) {} | 
 |  | 
 |   bool HeaderValid() const { return header_size() == sizeof(PickleHeader); } | 
 | }; | 
 |  | 
 | bool WritePickleFile(base::Pickle* pickle, const base::FilePath& file_name) { | 
 |   base::File file(file_name, base::File::FLAG_CREATE_ALWAYS | | 
 |                                  base::File::FLAG_WRITE | | 
 |                                  base::File::FLAG_SHARE_DELETE); | 
 |   if (!file.IsValid()) | 
 |     return false; | 
 |  | 
 |   int bytes_written = | 
 |       file.Write(0, static_cast<const char*>(pickle->data()), pickle->size()); | 
 |   if (bytes_written != base::checked_cast<int>(pickle->size())) { | 
 |     simple_util::SimpleCacheDeleteFile(file_name); | 
 |     return false; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | // Called for each cache directory traversal iteration. | 
 | void ProcessEntryFile(SimpleIndex::EntrySet* entries, | 
 |                       const base::FilePath& file_path, | 
 |                       base::Time last_accessed, | 
 |                       base::Time last_modified, | 
 |                       int64_t size) { | 
 |   static const size_t kEntryFilesLength = | 
 |       kEntryFilesHashLength + kEntryFilesSuffixLength; | 
 |   // Converting to std::string is OK since we never use UTF8 wide chars in our | 
 |   // file names. | 
 |   const base::FilePath::StringType base_name = file_path.BaseName().value(); | 
 |   const std::string file_name(base_name.begin(), base_name.end()); | 
 |  | 
 |   // Cleanup any left over doomed entries. | 
 |   if (base::StartsWith(file_name, "todelete_", base::CompareCase::SENSITIVE)) { | 
 |     base::DeleteFile(file_path, false); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (file_name.size() != kEntryFilesLength) | 
 |     return; | 
 |   const base::StringPiece hash_string( | 
 |       file_name.begin(), file_name.begin() + kEntryFilesHashLength); | 
 |   uint64_t hash_key = 0; | 
 |   if (!simple_util::GetEntryHashKeyFromHexString(hash_string, &hash_key)) { | 
 |     LOG(WARNING) << "Invalid entry hash key filename while restoring index from" | 
 |                  << " disk: " << file_name; | 
 |     return; | 
 |   } | 
 |  | 
 |   base::Time last_used_time; | 
 | #if defined(OS_POSIX) | 
 |   // For POSIX systems, a last access time is available. However, it's not | 
 |   // guaranteed to be more accurate than mtime. It is no worse though. | 
 |   last_used_time = last_accessed; | 
 | #endif | 
 |   if (last_used_time.is_null()) | 
 |     last_used_time = last_modified; | 
 |  | 
 |   auto it = entries->find(hash_key); | 
 |   base::CheckedNumeric<uint32_t> total_entry_size = size; | 
 |  | 
 |   // Sometimes we see entry sizes here which are nonsense. We can't use them | 
 |   // as-is, as they simply won't fit the type. The options that come to mind | 
 |   // are: | 
 |   // 1) Ignore the file. | 
 |   // 2) Make something up. | 
 |   // 3) Delete the files for the hash. | 
 |   // ("crash the browser" isn't considered a serious alternative). | 
 |   // | 
 |   // The problem with doing (1) is that we are recovering the index here, so if | 
 |   // we don't include the info on the file here, we may completely lose track of | 
 |   // the entry and never clean the file up. | 
 |   // | 
 |   // (2) is actually mostly fine: we may trigger eviction too soon or too late, | 
 |   // but we can't really do better since we can't trust the size. If the entry | 
 |   // is never opened, it will eventually get evicted. If it is opened, we will | 
 |   // re-check the file size, and if it's nonsense delete it there, and if it's | 
 |   // fine we will fix up the index via a UpdateDataFromEntryStat to have the | 
 |   // correct size. | 
 |   // | 
 |   // (3) does the best thing except when the wrong size is some weird interim | 
 |   // thing just on directory listing (in which case it may evict an entry | 
 |   // prematurely). It's a little harder to think about since it involves | 
 |   // mutating the disk while there are other mutations going on, however, | 
 |   // while (2) is single-threaded. | 
 |   // | 
 |   // Hence this picks (2). | 
 |  | 
 |   const int kPlaceHolderSizeWhenInvalid = 32768; | 
 |   if (!total_entry_size.IsValid()) { | 
 |     LOG(WARNING) << "Invalid file size while restoring index from disk: " | 
 |                  << size << " on file:" << file_name; | 
 |   } | 
 |  | 
 |   if (it == entries->end()) { | 
 |     SimpleIndex::InsertInEntrySet( | 
 |         hash_key, | 
 |         EntryMetadata(last_used_time, total_entry_size.ValueOrDefault( | 
 |                                           kPlaceHolderSizeWhenInvalid)), | 
 |         entries); | 
 |   } else { | 
 |     // Summing up the total size of the entry through all the *_[0-1] files | 
 |     total_entry_size += it->second.GetEntrySize(); | 
 |     it->second.SetEntrySize( | 
 |         total_entry_size.ValueOrDefault(kPlaceHolderSizeWhenInvalid)); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | SimpleIndexLoadResult::SimpleIndexLoadResult() | 
 |     : did_load(false), | 
 |       index_write_reason(SimpleIndex::INDEX_WRITE_REASON_MAX), | 
 |       flush_required(false) {} | 
 |  | 
 | SimpleIndexLoadResult::~SimpleIndexLoadResult() = default; | 
 |  | 
 | void SimpleIndexLoadResult::Reset() { | 
 |   did_load = false; | 
 |   index_write_reason = SimpleIndex::INDEX_WRITE_REASON_MAX; | 
 |   flush_required = false; | 
 |   entries.clear(); | 
 | } | 
 |  | 
 | // static | 
 | const char SimpleIndexFile::kIndexFileName[] = "the-real-index"; | 
 | // static | 
 | const char SimpleIndexFile::kIndexDirectory[] = "index-dir"; | 
 | // static | 
 | const char SimpleIndexFile::kTempIndexFileName[] = "temp-index"; | 
 |  | 
 | SimpleIndexFile::IndexMetadata::IndexMetadata() | 
 |     : magic_number_(kSimpleIndexMagicNumber), | 
 |       version_(kSimpleVersion), | 
 |       reason_(SimpleIndex::INDEX_WRITE_REASON_MAX), | 
 |       entry_count_(0), | 
 |       cache_size_(0) {} | 
 |  | 
 | SimpleIndexFile::IndexMetadata::IndexMetadata( | 
 |     SimpleIndex::IndexWriteToDiskReason reason, | 
 |     uint64_t entry_count, | 
 |     uint64_t cache_size) | 
 |     : magic_number_(kSimpleIndexMagicNumber), | 
 |       version_(kSimpleVersion), | 
 |       reason_(reason), | 
 |       entry_count_(entry_count), | 
 |       cache_size_(cache_size) {} | 
 |  | 
 | void SimpleIndexFile::IndexMetadata::Serialize(base::Pickle* pickle) const { | 
 |   DCHECK(pickle); | 
 |   pickle->WriteUInt64(magic_number_); | 
 |   pickle->WriteUInt32(version_); | 
 |   pickle->WriteUInt64(entry_count_); | 
 |   pickle->WriteUInt64(cache_size_); | 
 |   pickle->WriteUInt32(static_cast<uint32_t>(reason_)); | 
 | } | 
 |  | 
 | // static | 
 | void SimpleIndexFile::SerializeFinalData(base::Time cache_modified, | 
 |                                          base::Pickle* pickle) { | 
 |   pickle->WriteInt64(cache_modified.ToInternalValue()); | 
 |   PickleHeader* header_p = pickle->headerT<PickleHeader>(); | 
 |   header_p->crc = CalculatePickleCRC(*pickle); | 
 | } | 
 |  | 
 | bool SimpleIndexFile::IndexMetadata::Deserialize(base::PickleIterator* it) { | 
 |   DCHECK(it); | 
 |  | 
 |   bool v6_format_index_read_results = | 
 |       it->ReadUInt64(&magic_number_) && it->ReadUInt32(&version_) && | 
 |       it->ReadUInt64(&entry_count_) && it->ReadUInt64(&cache_size_); | 
 |   if (!v6_format_index_read_results) | 
 |     return false; | 
 |   if (version_ >= 7) { | 
 |     uint32_t tmp_reason; | 
 |     if (!it->ReadUInt32(&tmp_reason)) | 
 |       return false; | 
 |     reason_ = static_cast<SimpleIndex::IndexWriteToDiskReason>(tmp_reason); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void SimpleIndexFile::SyncWriteToDisk(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, | 
 |                                       const base::TimeTicks& start_time, | 
 |                                       bool app_on_background) { | 
 |   DCHECK_EQ(index_filename.DirName().value(), | 
 |             temp_index_filename.DirName().value()); | 
 |   base::FilePath index_file_directory = temp_index_filename.DirName(); | 
 |   if (!base::DirectoryExists(index_file_directory) && | 
 |       !base::CreateDirectory(index_file_directory)) { | 
 |     LOG(ERROR) << "Could not create a directory to hold the index file"; | 
 |     return; | 
 |   } | 
 |  | 
 |   // There is a chance that the index containing all the necessary data about | 
 |   // newly created entries will appear to be stale. This can happen if on-disk | 
 |   // part of a Create operation does not fit into the time budget for the index | 
 |   // flush delay. This simple approach will be reconsidered if it does not allow | 
 |   // for maintaining freshness. | 
 |   base::Time cache_dir_mtime; | 
 |   if (!simple_util::GetMTime(cache_directory, &cache_dir_mtime)) { | 
 |     LOG(ERROR) << "Could obtain information about cache age"; | 
 |     return; | 
 |   } | 
 |   SerializeFinalData(cache_dir_mtime, pickle.get()); | 
 | #if defined(STARBOARD) | 
 |   const char* data = static_cast<const char*>(pickle.get()->data()); | 
 |   if (!SbFileAtomicReplace(index_filename.value().c_str(), data, pickle.get()->size())) | 
 |     return; | 
 | #else | 
 |   if (!WritePickleFile(pickle.get(), temp_index_filename)) { | 
 |     LOG(ERROR) << "Failed to write the temporary index file"; | 
 |     return; | 
 |   } | 
 |  | 
 |   // Atomically rename the temporary index file to become the real one. | 
 |   if (!base::ReplaceFile(temp_index_filename, index_filename, NULL)) | 
 |     return; | 
 | #endif | 
 |  | 
 |   if (app_on_background) { | 
 |     SIMPLE_CACHE_UMA(TIMES, | 
 |                      "IndexWriteToDiskTime.Background", cache_type, | 
 |                      (base::TimeTicks::Now() - start_time)); | 
 |   } else { | 
 |     SIMPLE_CACHE_UMA(TIMES, | 
 |                      "IndexWriteToDiskTime.Foreground", cache_type, | 
 |                      (base::TimeTicks::Now() - start_time)); | 
 |   } | 
 | } | 
 |  | 
 | bool SimpleIndexFile::IndexMetadata::CheckIndexMetadata() { | 
 |   if (entry_count_ > kMaxEntriesInIndex || | 
 |       magic_number_ != kSimpleIndexMagicNumber) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   static_assert(kSimpleVersion == 8, "index metadata reader out of date"); | 
 |   // No |reason_| is saved in the version 6 file format. | 
 |   if (version_ == 6) | 
 |     return reason_ == SimpleIndex::INDEX_WRITE_REASON_MAX; | 
 |   return (version_ == 7 || version_ == 8) && | 
 |          reason_ < SimpleIndex::INDEX_WRITE_REASON_MAX; | 
 | } | 
 |  | 
 | SimpleIndexFile::SimpleIndexFile( | 
 |     const scoped_refptr<base::SequencedTaskRunner>& cache_runner, | 
 |     const scoped_refptr<base::TaskRunner>& worker_pool, | 
 |     net::CacheType cache_type, | 
 |     const base::FilePath& cache_directory) | 
 |     : cache_runner_(cache_runner), | 
 |       worker_pool_(worker_pool), | 
 |       cache_type_(cache_type), | 
 |       cache_directory_(cache_directory), | 
 |       index_file_(cache_directory_.AppendASCII(kIndexDirectory) | 
 |                       .AppendASCII(kIndexFileName)), | 
 |       temp_index_file_(cache_directory_.AppendASCII(kIndexDirectory) | 
 |                            .AppendASCII(kTempIndexFileName)) {} | 
 |  | 
 | SimpleIndexFile::~SimpleIndexFile() = default; | 
 |  | 
 | void SimpleIndexFile::LoadIndexEntries(base::Time cache_last_modified, | 
 |                                        const base::Closure& callback, | 
 |                                        SimpleIndexLoadResult* out_result) { | 
 |   base::Closure task = base::Bind(&SimpleIndexFile::SyncLoadIndexEntries, | 
 |                                   cache_type_, | 
 |                                   cache_last_modified, cache_directory_, | 
 |                                   index_file_, out_result); | 
 |   worker_pool_->PostTaskAndReply(FROM_HERE, task, callback); | 
 | } | 
 |  | 
 | void SimpleIndexFile::WriteToDisk(SimpleIndex::IndexWriteToDiskReason reason, | 
 |                                   const SimpleIndex::EntrySet& entry_set, | 
 |                                   uint64_t cache_size, | 
 |                                   const base::TimeTicks& start, | 
 |                                   bool app_on_background, | 
 |                                   const base::Closure& callback) { | 
 |   UmaRecordIndexWriteReason(reason, cache_type_); | 
 |   IndexMetadata index_metadata(reason, entry_set.size(), cache_size); | 
 |   std::unique_ptr<base::Pickle> pickle = Serialize(index_metadata, entry_set); | 
 |   base::Closure task = | 
 |       base::Bind(&SimpleIndexFile::SyncWriteToDisk, | 
 |                  cache_type_, cache_directory_, index_file_, temp_index_file_, | 
 |                  base::Passed(&pickle), start, app_on_background); | 
 |   if (callback.is_null()) | 
 |     cache_runner_->PostTask(FROM_HERE, task); | 
 |   else | 
 |     cache_runner_->PostTaskAndReply(FROM_HERE, task, callback); | 
 | } | 
 |  | 
 | // static | 
 | void SimpleIndexFile::SyncLoadIndexEntries( | 
 |     net::CacheType cache_type, | 
 |     base::Time cache_last_modified, | 
 |     const base::FilePath& cache_directory, | 
 |     const base::FilePath& index_file_path, | 
 |     SimpleIndexLoadResult* out_result) { | 
 |   // Load the index and find its age. | 
 |   base::Time last_cache_seen_by_index; | 
 |   SyncLoadFromDisk(index_file_path, &last_cache_seen_by_index, out_result); | 
 |  | 
 |   // Consider the index loaded if it is fresh. | 
 |   const bool index_file_existed = base::PathExists(index_file_path); | 
 |   if (!out_result->did_load) { | 
 |     if (index_file_existed) | 
 |       UmaRecordIndexFileState(INDEX_STATE_CORRUPT, cache_type); | 
 |   } else { | 
 |     if (cache_last_modified <= last_cache_seen_by_index) { | 
 |       if (out_result->index_write_reason != | 
 |           SimpleIndex::INDEX_WRITE_REASON_MAX) { | 
 |         UmaRecordIndexWriteReasonAtLoad(out_result->index_write_reason, | 
 |                                         cache_type); | 
 |       } | 
 |       base::Time latest_dir_mtime; | 
 |       simple_util::GetMTime(cache_directory, &latest_dir_mtime); | 
 |       if (LegacyIsIndexFileStale(latest_dir_mtime, index_file_path)) { | 
 |         UmaRecordIndexFileState(INDEX_STATE_FRESH_CONCURRENT_UPDATES, | 
 |                                 cache_type); | 
 |       } else { | 
 |         UmaRecordIndexFileState(INDEX_STATE_FRESH, cache_type); | 
 |       } | 
 |       out_result->init_method = SimpleIndex::INITIALIZE_METHOD_LOADED; | 
 |       UmaRecordIndexInitMethod(out_result->init_method, cache_type); | 
 |       return; | 
 |     } | 
 |     UmaRecordIndexFileState(INDEX_STATE_STALE, cache_type); | 
 |   } | 
 |  | 
 |   // Reconstruct the index by scanning the disk for entries. | 
 |   SimpleIndex::EntrySet entries_from_stale_index; | 
 |   entries_from_stale_index.swap(out_result->entries); | 
 |   const base::TimeTicks start = base::TimeTicks::Now(); | 
 |   SyncRestoreFromDisk(cache_directory, index_file_path, out_result); | 
 |   SIMPLE_CACHE_UMA(MEDIUM_TIMES, "IndexRestoreTime", cache_type, | 
 |                    base::TimeTicks::Now() - start); | 
 |   SIMPLE_CACHE_UMA(COUNTS_1M, "IndexEntriesRestored", cache_type, | 
 |                    out_result->entries.size()); | 
 |   if (index_file_existed) { | 
 |     out_result->init_method = SimpleIndex::INITIALIZE_METHOD_RECOVERED; | 
 |  | 
 |     int missed_entry_count = 0; | 
 |     for (const auto& i : out_result->entries) { | 
 |       if (entries_from_stale_index.count(i.first) == 0) | 
 |         ++missed_entry_count; | 
 |     } | 
 |     int extra_entry_count = 0; | 
 |     for (const auto& i : entries_from_stale_index) { | 
 |       if (out_result->entries.count(i.first) == 0) | 
 |         ++extra_entry_count; | 
 |     } | 
 |     UmaRecordStaleIndexQuality(missed_entry_count, extra_entry_count, | 
 |                                cache_type); | 
 |   } else { | 
 |     out_result->init_method = SimpleIndex::INITIALIZE_METHOD_NEWCACHE; | 
 |     SIMPLE_CACHE_UMA(COUNTS_1M, | 
 |                      "IndexCreatedEntryCount", cache_type, | 
 |                      out_result->entries.size()); | 
 |   } | 
 |   UmaRecordIndexInitMethod(out_result->init_method, cache_type); | 
 | } | 
 |  | 
 | // static | 
 | void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename, | 
 |                                        base::Time* out_last_cache_seen_by_index, | 
 |                                        SimpleIndexLoadResult* out_result) { | 
 |   out_result->Reset(); | 
 |  | 
 |   base::File file(index_filename, base::File::FLAG_OPEN | | 
 |                                       base::File::FLAG_READ | | 
 |                                       base::File::FLAG_SHARE_DELETE | | 
 |                                       base::File::FLAG_SEQUENTIAL_SCAN); | 
 |   if (!file.IsValid()) | 
 |     return; | 
 |  | 
 |   // Sanity-check the length. We don't want to crash trying to read some corrupt | 
 |   // 10GiB file or such. | 
 |   int64_t file_length = file.GetLength(); | 
 |   if (file_length < 0 || file_length > kMaxIndexFileSizeBytes) { | 
 |     simple_util::SimpleCacheDeleteFile(index_filename); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Make sure to preallocate in one chunk, so we don't induce fragmentation | 
 |   // reallocating a growing buffer. | 
 |   auto buffer = std::make_unique<char[]>(file_length); | 
 |  | 
 |   int read = file.Read(0, buffer.get(), file_length); | 
 |   if (read < file_length) { | 
 |     simple_util::SimpleCacheDeleteFile(index_filename); | 
 |     return; | 
 |   } | 
 |  | 
 |   SimpleIndexFile::Deserialize(buffer.get(), read, out_last_cache_seen_by_index, | 
 |                                out_result); | 
 |  | 
 |   if (!out_result->did_load) | 
 |     simple_util::SimpleCacheDeleteFile(index_filename); | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<base::Pickle> SimpleIndexFile::Serialize( | 
 |     const SimpleIndexFile::IndexMetadata& index_metadata, | 
 |     const SimpleIndex::EntrySet& entries) { | 
 |   std::unique_ptr<base::Pickle> pickle = std::make_unique<SimpleIndexPickle>(); | 
 |  | 
 |   index_metadata.Serialize(pickle.get()); | 
 |   for (auto it = entries.begin(); it != entries.end(); ++it) { | 
 |     pickle->WriteUInt64(it->first); | 
 |     it->second.Serialize(pickle.get()); | 
 |   } | 
 |   return pickle; | 
 | } | 
 |  | 
 | // static | 
 | void SimpleIndexFile::Deserialize(const char* data, int data_len, | 
 |                                   base::Time* out_cache_last_modified, | 
 |                                   SimpleIndexLoadResult* out_result) { | 
 |   DCHECK(data); | 
 |  | 
 |   out_result->Reset(); | 
 |   SimpleIndex::EntrySet* entries = &out_result->entries; | 
 |  | 
 |   SimpleIndexPickle pickle(data, data_len); | 
 |   if (!pickle.data() || !pickle.HeaderValid()) { | 
 |     LOG(WARNING) << "Corrupt Simple Index File."; | 
 |     return; | 
 |   } | 
 |  | 
 |   base::PickleIterator pickle_it(pickle); | 
 |   PickleHeader* header_p = pickle.headerT<PickleHeader>(); | 
 |   const uint32_t crc_read = header_p->crc; | 
 |   const uint32_t crc_calculated = CalculatePickleCRC(pickle); | 
 |  | 
 |   if (crc_read != crc_calculated) { | 
 |     LOG(WARNING) << "Invalid CRC in Simple Index file."; | 
 |     return; | 
 |   } | 
 |  | 
 |   SimpleIndexFile::IndexMetadata index_metadata; | 
 |   if (!index_metadata.Deserialize(&pickle_it)) { | 
 |     LOG(ERROR) << "Invalid index_metadata on Simple Cache Index."; | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!index_metadata.CheckIndexMetadata()) { | 
 |     LOG(ERROR) << "Invalid index_metadata on Simple Cache Index."; | 
 |     return; | 
 |   } | 
 |  | 
 |   entries->reserve(index_metadata.entry_count() + kExtraSizeForMerge); | 
 |   while (entries->size() < index_metadata.entry_count()) { | 
 |     uint64_t hash_key; | 
 |     EntryMetadata entry_metadata; | 
 |     if (!pickle_it.ReadUInt64(&hash_key) || | 
 |         !entry_metadata.Deserialize( | 
 |             &pickle_it, index_metadata.has_entry_in_memory_data())) { | 
 |       LOG(WARNING) << "Invalid EntryMetadata in Simple Index file."; | 
 |       entries->clear(); | 
 |       return; | 
 |     } | 
 |     SimpleIndex::InsertInEntrySet(hash_key, entry_metadata, entries); | 
 |   } | 
 |  | 
 |   int64_t cache_last_modified; | 
 |   if (!pickle_it.ReadInt64(&cache_last_modified)) { | 
 |     entries->clear(); | 
 |     return; | 
 |   } | 
 |   DCHECK(out_cache_last_modified); | 
 |   *out_cache_last_modified = base::Time::FromInternalValue(cache_last_modified); | 
 |  | 
 |   out_result->index_write_reason = index_metadata.reason(); | 
 |   out_result->did_load = true; | 
 | } | 
 |  | 
 | // static | 
 | void SimpleIndexFile::SyncRestoreFromDisk( | 
 |     const base::FilePath& cache_directory, | 
 |     const base::FilePath& index_file_path, | 
 |     SimpleIndexLoadResult* out_result) { | 
 |   VLOG(1) << "Simple Cache Index is being restored from disk."; | 
 |   simple_util::SimpleCacheDeleteFile(index_file_path); | 
 |   out_result->Reset(); | 
 |   SimpleIndex::EntrySet* entries = &out_result->entries; | 
 |  | 
 |   const bool did_succeed = TraverseCacheDirectory( | 
 |       cache_directory, base::Bind(&ProcessEntryFile, entries)); | 
 |   if (!did_succeed) { | 
 |     LOG(ERROR) << "Could not reconstruct index from disk"; | 
 |     return; | 
 |   } | 
 |   out_result->did_load = true; | 
 |   // When we restore from disk we write the merged index file to disk right | 
 |   // away, this might save us from having to restore again next time. | 
 |   out_result->flush_required = true; | 
 | } | 
 |  | 
 | // static | 
 | bool SimpleIndexFile::LegacyIsIndexFileStale( | 
 |     base::Time cache_last_modified, | 
 |     const base::FilePath& index_file_path) { | 
 |   base::Time index_mtime; | 
 |   if (!simple_util::GetMTime(index_file_path, &index_mtime)) | 
 |     return true; | 
 |   return index_mtime < cache_last_modified; | 
 | } | 
 |  | 
 | }  // namespace disk_cache |