| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 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. | |
| 4 | |
| 5 #include "net/disk_cache/simple/simple_index_file.h" | |
| 6 | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/files/file.h" | |
| 10 #include "base/files/file_util.h" | |
| 11 #include "base/files/memory_mapped_file.h" | |
| 12 #include "base/hash.h" | |
| 13 #include "base/logging.h" | |
| 14 #include "base/pickle.h" | |
| 15 #include "base/single_thread_task_runner.h" | |
| 16 #include "base/task_runner_util.h" | |
| 17 #include "base/threading/thread_restrictions.h" | |
| 18 #include "net/disk_cache/simple/simple_backend_version.h" | |
| 19 #include "net/disk_cache/simple/simple_entry_format.h" | |
| 20 #include "net/disk_cache/simple/simple_histogram_macros.h" | |
| 21 #include "net/disk_cache/simple/simple_index.h" | |
| 22 #include "net/disk_cache/simple/simple_synchronous_entry.h" | |
| 23 #include "net/disk_cache/simple/simple_util.h" | |
| 24 #include "third_party/zlib/zlib.h" | |
| 25 | |
| 26 using base::File; | |
| 27 | |
| 28 namespace disk_cache { | |
| 29 namespace { | |
| 30 | |
| 31 const int kEntryFilesHashLength = 16; | |
| 32 const int kEntryFilesSuffixLength = 2; | |
| 33 | |
| 34 const uint64 kMaxEntiresInIndex = 100000000; | |
| 35 | |
| 36 uint32 CalculatePickleCRC(const Pickle& pickle) { | |
| 37 return crc32(crc32(0, Z_NULL, 0), | |
| 38 reinterpret_cast<const Bytef*>(pickle.payload()), | |
| 39 pickle.payload_size()); | |
| 40 } | |
| 41 | |
| 42 // Used in histograms. Please only add new values at the end. | |
| 43 enum IndexFileState { | |
| 44 INDEX_STATE_CORRUPT = 0, | |
| 45 INDEX_STATE_STALE = 1, | |
| 46 INDEX_STATE_FRESH = 2, | |
| 47 INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3, | |
| 48 INDEX_STATE_MAX = 4, | |
| 49 }; | |
| 50 | |
| 51 void UmaRecordIndexFileState(IndexFileState state, net::CacheType cache_type) { | |
| 52 SIMPLE_CACHE_UMA(ENUMERATION, | |
| 53 "IndexFileStateOnLoad", cache_type, state, INDEX_STATE_MAX); | |
| 54 } | |
| 55 | |
| 56 // Used in histograms. Please only add new values at the end. | |
| 57 enum IndexInitMethod { | |
| 58 INITIALIZE_METHOD_RECOVERED = 0, | |
| 59 INITIALIZE_METHOD_LOADED = 1, | |
| 60 INITIALIZE_METHOD_NEWCACHE = 2, | |
| 61 INITIALIZE_METHOD_MAX = 3, | |
| 62 }; | |
| 63 | |
| 64 void UmaRecordIndexInitMethod(IndexInitMethod method, | |
| 65 net::CacheType cache_type) { | |
| 66 SIMPLE_CACHE_UMA(ENUMERATION, | |
| 67 "IndexInitializeMethod", cache_type, | |
| 68 method, INITIALIZE_METHOD_MAX); | |
| 69 } | |
| 70 | |
| 71 bool WritePickleFile(Pickle* pickle, const base::FilePath& file_name) { | |
| 72 File file( | |
| 73 file_name, | |
| 74 File::FLAG_CREATE | File::FLAG_WRITE | File::FLAG_SHARE_DELETE); | |
| 75 if (!file.IsValid()) | |
| 76 return false; | |
| 77 | |
| 78 int bytes_written = | |
| 79 file.Write(0, static_cast<const char*>(pickle->data()), pickle->size()); | |
| 80 if (bytes_written != implicit_cast<int>(pickle->size())) { | |
| 81 simple_util::SimpleCacheDeleteFile(file_name); | |
| 82 return false; | |
| 83 } | |
| 84 return true; | |
| 85 } | |
| 86 | |
| 87 // Called for each cache directory traversal iteration. | |
| 88 void ProcessEntryFile(SimpleIndex::EntrySet* entries, | |
| 89 const base::FilePath& file_path) { | |
| 90 static const size_t kEntryFilesLength = | |
| 91 kEntryFilesHashLength + kEntryFilesSuffixLength; | |
| 92 // Converting to std::string is OK since we never use UTF8 wide chars in our | |
| 93 // file names. | |
| 94 const base::FilePath::StringType base_name = file_path.BaseName().value(); | |
| 95 const std::string file_name(base_name.begin(), base_name.end()); | |
| 96 if (file_name.size() != kEntryFilesLength) | |
| 97 return; | |
| 98 const base::StringPiece hash_string( | |
| 99 file_name.begin(), file_name.begin() + kEntryFilesHashLength); | |
| 100 uint64 hash_key = 0; | |
| 101 if (!simple_util::GetEntryHashKeyFromHexString(hash_string, &hash_key)) { | |
| 102 LOG(WARNING) << "Invalid entry hash key filename while restoring index from" | |
| 103 << " disk: " << file_name; | |
| 104 return; | |
| 105 } | |
| 106 | |
| 107 File::Info file_info; | |
| 108 if (!base::GetFileInfo(file_path, &file_info)) { | |
| 109 LOG(ERROR) << "Could not get file info for " << file_path.value(); | |
| 110 return; | |
| 111 } | |
| 112 base::Time last_used_time; | |
| 113 #if defined(OS_POSIX) | |
| 114 // For POSIX systems, a last access time is available. However, it's not | |
| 115 // guaranteed to be more accurate than mtime. It is no worse though. | |
| 116 last_used_time = file_info.last_accessed; | |
| 117 #endif | |
| 118 if (last_used_time.is_null()) | |
| 119 last_used_time = file_info.last_modified; | |
| 120 | |
| 121 int64 file_size = file_info.size; | |
| 122 SimpleIndex::EntrySet::iterator it = entries->find(hash_key); | |
| 123 if (it == entries->end()) { | |
| 124 SimpleIndex::InsertInEntrySet( | |
| 125 hash_key, | |
| 126 EntryMetadata(last_used_time, file_size), | |
| 127 entries); | |
| 128 } else { | |
| 129 // Summing up the total size of the entry through all the *_[0-1] files | |
| 130 it->second.SetEntrySize(it->second.GetEntrySize() + file_size); | |
| 131 } | |
| 132 } | |
| 133 | |
| 134 } // namespace | |
| 135 | |
| 136 SimpleIndexLoadResult::SimpleIndexLoadResult() : did_load(false), | |
| 137 flush_required(false) { | |
| 138 } | |
| 139 | |
| 140 SimpleIndexLoadResult::~SimpleIndexLoadResult() { | |
| 141 } | |
| 142 | |
| 143 void SimpleIndexLoadResult::Reset() { | |
| 144 did_load = false; | |
| 145 flush_required = false; | |
| 146 entries.clear(); | |
| 147 } | |
| 148 | |
| 149 // static | |
| 150 const char SimpleIndexFile::kIndexFileName[] = "the-real-index"; | |
| 151 // static | |
| 152 const char SimpleIndexFile::kIndexDirectory[] = "index-dir"; | |
| 153 // static | |
| 154 const char SimpleIndexFile::kTempIndexFileName[] = "temp-index"; | |
| 155 | |
| 156 SimpleIndexFile::IndexMetadata::IndexMetadata() | |
| 157 : magic_number_(kSimpleIndexMagicNumber), | |
| 158 version_(kSimpleVersion), | |
| 159 number_of_entries_(0), | |
| 160 cache_size_(0) {} | |
| 161 | |
| 162 SimpleIndexFile::IndexMetadata::IndexMetadata( | |
| 163 uint64 number_of_entries, uint64 cache_size) | |
| 164 : magic_number_(kSimpleIndexMagicNumber), | |
| 165 version_(kSimpleVersion), | |
| 166 number_of_entries_(number_of_entries), | |
| 167 cache_size_(cache_size) {} | |
| 168 | |
| 169 void SimpleIndexFile::IndexMetadata::Serialize(Pickle* pickle) const { | |
| 170 DCHECK(pickle); | |
| 171 pickle->WriteUInt64(magic_number_); | |
| 172 pickle->WriteUInt32(version_); | |
| 173 pickle->WriteUInt64(number_of_entries_); | |
| 174 pickle->WriteUInt64(cache_size_); | |
| 175 } | |
| 176 | |
| 177 // static | |
| 178 bool SimpleIndexFile::SerializeFinalData(base::Time cache_modified, | |
| 179 Pickle* pickle) { | |
| 180 if (!pickle->WriteInt64(cache_modified.ToInternalValue())) | |
| 181 return false; | |
| 182 SimpleIndexFile::PickleHeader* header_p = pickle->headerT<PickleHeader>(); | |
| 183 header_p->crc = CalculatePickleCRC(*pickle); | |
| 184 return true; | |
| 185 } | |
| 186 | |
| 187 bool SimpleIndexFile::IndexMetadata::Deserialize(PickleIterator* it) { | |
| 188 DCHECK(it); | |
| 189 return it->ReadUInt64(&magic_number_) && | |
| 190 it->ReadUInt32(&version_) && | |
| 191 it->ReadUInt64(&number_of_entries_)&& | |
| 192 it->ReadUInt64(&cache_size_); | |
| 193 } | |
| 194 | |
| 195 void SimpleIndexFile::SyncWriteToDisk(net::CacheType cache_type, | |
| 196 const base::FilePath& cache_directory, | |
| 197 const base::FilePath& index_filename, | |
| 198 const base::FilePath& temp_index_filename, | |
| 199 scoped_ptr<Pickle> pickle, | |
| 200 const base::TimeTicks& start_time, | |
| 201 bool app_on_background) { | |
| 202 DCHECK_EQ(index_filename.DirName().value(), | |
| 203 temp_index_filename.DirName().value()); | |
| 204 base::FilePath index_file_directory = temp_index_filename.DirName(); | |
| 205 if (!base::DirectoryExists(index_file_directory) && | |
| 206 !base::CreateDirectory(index_file_directory)) { | |
| 207 LOG(ERROR) << "Could not create a directory to hold the index file"; | |
| 208 return; | |
| 209 } | |
| 210 | |
| 211 // There is a chance that the index containing all the necessary data about | |
| 212 // newly created entries will appear to be stale. This can happen if on-disk | |
| 213 // part of a Create operation does not fit into the time budget for the index | |
| 214 // flush delay. This simple approach will be reconsidered if it does not allow | |
| 215 // for maintaining freshness. | |
| 216 base::Time cache_dir_mtime; | |
| 217 if (!simple_util::GetMTime(cache_directory, &cache_dir_mtime)) { | |
| 218 LOG(ERROR) << "Could obtain information about cache age"; | |
| 219 return; | |
| 220 } | |
| 221 SerializeFinalData(cache_dir_mtime, pickle.get()); | |
| 222 if (!WritePickleFile(pickle.get(), temp_index_filename)) { | |
| 223 LOG(ERROR) << "Failed to write the temporary index file"; | |
| 224 return; | |
| 225 } | |
| 226 | |
| 227 // Atomically rename the temporary index file to become the real one. | |
| 228 // TODO(gavinp): DCHECK when not shutting down, since that is very strange. | |
| 229 // The rename failing during shutdown is legal because it's legal to begin | |
| 230 // erasing a cache as soon as the destructor has been called. | |
| 231 if (!base::ReplaceFile(temp_index_filename, index_filename, NULL)) | |
| 232 return; | |
| 233 | |
| 234 if (app_on_background) { | |
| 235 SIMPLE_CACHE_UMA(TIMES, | |
| 236 "IndexWriteToDiskTime.Background", cache_type, | |
| 237 (base::TimeTicks::Now() - start_time)); | |
| 238 } else { | |
| 239 SIMPLE_CACHE_UMA(TIMES, | |
| 240 "IndexWriteToDiskTime.Foreground", cache_type, | |
| 241 (base::TimeTicks::Now() - start_time)); | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 bool SimpleIndexFile::IndexMetadata::CheckIndexMetadata() { | |
| 246 return number_of_entries_ <= kMaxEntiresInIndex && | |
| 247 magic_number_ == kSimpleIndexMagicNumber && | |
| 248 version_ == kSimpleVersion; | |
| 249 } | |
| 250 | |
| 251 SimpleIndexFile::SimpleIndexFile( | |
| 252 const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread, | |
| 253 const scoped_refptr<base::TaskRunner>& worker_pool, | |
| 254 net::CacheType cache_type, | |
| 255 const base::FilePath& cache_directory) | |
| 256 : cache_thread_(cache_thread), | |
| 257 worker_pool_(worker_pool), | |
| 258 cache_type_(cache_type), | |
| 259 cache_directory_(cache_directory), | |
| 260 index_file_(cache_directory_.AppendASCII(kIndexDirectory) | |
| 261 .AppendASCII(kIndexFileName)), | |
| 262 temp_index_file_(cache_directory_.AppendASCII(kIndexDirectory) | |
| 263 .AppendASCII(kTempIndexFileName)) { | |
| 264 } | |
| 265 | |
| 266 SimpleIndexFile::~SimpleIndexFile() {} | |
| 267 | |
| 268 void SimpleIndexFile::LoadIndexEntries(base::Time cache_last_modified, | |
| 269 const base::Closure& callback, | |
| 270 SimpleIndexLoadResult* out_result) { | |
| 271 base::Closure task = base::Bind(&SimpleIndexFile::SyncLoadIndexEntries, | |
| 272 cache_type_, | |
| 273 cache_last_modified, cache_directory_, | |
| 274 index_file_, out_result); | |
| 275 worker_pool_->PostTaskAndReply(FROM_HERE, task, callback); | |
| 276 } | |
| 277 | |
| 278 void SimpleIndexFile::WriteToDisk(const SimpleIndex::EntrySet& entry_set, | |
| 279 uint64 cache_size, | |
| 280 const base::TimeTicks& start, | |
| 281 bool app_on_background, | |
| 282 const base::Closure& callback) { | |
| 283 IndexMetadata index_metadata(entry_set.size(), cache_size); | |
| 284 scoped_ptr<Pickle> pickle = Serialize(index_metadata, entry_set); | |
| 285 base::Closure task = | |
| 286 base::Bind(&SimpleIndexFile::SyncWriteToDisk, | |
| 287 cache_type_, cache_directory_, index_file_, temp_index_file_, | |
| 288 base::Passed(&pickle), start, app_on_background); | |
| 289 if (callback.is_null()) | |
| 290 cache_thread_->PostTask(FROM_HERE, task); | |
| 291 else | |
| 292 cache_thread_->PostTaskAndReply(FROM_HERE, task, callback); | |
| 293 } | |
| 294 | |
| 295 // static | |
| 296 void SimpleIndexFile::SyncLoadIndexEntries( | |
| 297 net::CacheType cache_type, | |
| 298 base::Time cache_last_modified, | |
| 299 const base::FilePath& cache_directory, | |
| 300 const base::FilePath& index_file_path, | |
| 301 SimpleIndexLoadResult* out_result) { | |
| 302 // Load the index and find its age. | |
| 303 base::Time last_cache_seen_by_index; | |
| 304 SyncLoadFromDisk(index_file_path, &last_cache_seen_by_index, out_result); | |
| 305 | |
| 306 // Consider the index loaded if it is fresh. | |
| 307 const bool index_file_existed = base::PathExists(index_file_path); | |
| 308 if (!out_result->did_load) { | |
| 309 if (index_file_existed) | |
| 310 UmaRecordIndexFileState(INDEX_STATE_CORRUPT, cache_type); | |
| 311 } else { | |
| 312 if (cache_last_modified <= last_cache_seen_by_index) { | |
| 313 base::Time latest_dir_mtime; | |
| 314 simple_util::GetMTime(cache_directory, &latest_dir_mtime); | |
| 315 if (LegacyIsIndexFileStale(latest_dir_mtime, index_file_path)) { | |
| 316 UmaRecordIndexFileState(INDEX_STATE_FRESH_CONCURRENT_UPDATES, | |
| 317 cache_type); | |
| 318 } else { | |
| 319 UmaRecordIndexFileState(INDEX_STATE_FRESH, cache_type); | |
| 320 } | |
| 321 UmaRecordIndexInitMethod(INITIALIZE_METHOD_LOADED, cache_type); | |
| 322 return; | |
| 323 } | |
| 324 UmaRecordIndexFileState(INDEX_STATE_STALE, cache_type); | |
| 325 } | |
| 326 | |
| 327 // Reconstruct the index by scanning the disk for entries. | |
| 328 const base::TimeTicks start = base::TimeTicks::Now(); | |
| 329 SyncRestoreFromDisk(cache_directory, index_file_path, out_result); | |
| 330 SIMPLE_CACHE_UMA(MEDIUM_TIMES, "IndexRestoreTime", cache_type, | |
| 331 base::TimeTicks::Now() - start); | |
| 332 SIMPLE_CACHE_UMA(COUNTS, "IndexEntriesRestored", cache_type, | |
| 333 out_result->entries.size()); | |
| 334 if (index_file_existed) { | |
| 335 UmaRecordIndexInitMethod(INITIALIZE_METHOD_RECOVERED, cache_type); | |
| 336 } else { | |
| 337 UmaRecordIndexInitMethod(INITIALIZE_METHOD_NEWCACHE, cache_type); | |
| 338 SIMPLE_CACHE_UMA(COUNTS, | |
| 339 "IndexCreatedEntryCount", cache_type, | |
| 340 out_result->entries.size()); | |
| 341 } | |
| 342 } | |
| 343 | |
| 344 // static | |
| 345 void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename, | |
| 346 base::Time* out_last_cache_seen_by_index, | |
| 347 SimpleIndexLoadResult* out_result) { | |
| 348 out_result->Reset(); | |
| 349 | |
| 350 File file(index_filename, | |
| 351 File::FLAG_OPEN | File::FLAG_READ | File::FLAG_SHARE_DELETE); | |
| 352 if (!file.IsValid()) | |
| 353 return; | |
| 354 | |
| 355 base::MemoryMappedFile index_file_map; | |
| 356 if (!index_file_map.Initialize(file.Pass())) { | |
| 357 simple_util::SimpleCacheDeleteFile(index_filename); | |
| 358 return; | |
| 359 } | |
| 360 | |
| 361 SimpleIndexFile::Deserialize( | |
| 362 reinterpret_cast<const char*>(index_file_map.data()), | |
| 363 index_file_map.length(), | |
| 364 out_last_cache_seen_by_index, | |
| 365 out_result); | |
| 366 | |
| 367 if (!out_result->did_load) | |
| 368 simple_util::SimpleCacheDeleteFile(index_filename); | |
| 369 } | |
| 370 | |
| 371 // static | |
| 372 scoped_ptr<Pickle> SimpleIndexFile::Serialize( | |
| 373 const SimpleIndexFile::IndexMetadata& index_metadata, | |
| 374 const SimpleIndex::EntrySet& entries) { | |
| 375 scoped_ptr<Pickle> pickle(new Pickle(sizeof(SimpleIndexFile::PickleHeader))); | |
| 376 | |
| 377 index_metadata.Serialize(pickle.get()); | |
| 378 for (SimpleIndex::EntrySet::const_iterator it = entries.begin(); | |
| 379 it != entries.end(); ++it) { | |
| 380 pickle->WriteUInt64(it->first); | |
| 381 it->second.Serialize(pickle.get()); | |
| 382 } | |
| 383 return pickle.Pass(); | |
| 384 } | |
| 385 | |
| 386 // static | |
| 387 void SimpleIndexFile::Deserialize(const char* data, int data_len, | |
| 388 base::Time* out_cache_last_modified, | |
| 389 SimpleIndexLoadResult* out_result) { | |
| 390 DCHECK(data); | |
| 391 | |
| 392 out_result->Reset(); | |
| 393 SimpleIndex::EntrySet* entries = &out_result->entries; | |
| 394 | |
| 395 Pickle pickle(data, data_len); | |
| 396 if (!pickle.data()) { | |
| 397 LOG(WARNING) << "Corrupt Simple Index File."; | |
| 398 return; | |
| 399 } | |
| 400 | |
| 401 PickleIterator pickle_it(pickle); | |
| 402 SimpleIndexFile::PickleHeader* header_p = | |
| 403 pickle.headerT<SimpleIndexFile::PickleHeader>(); | |
| 404 const uint32 crc_read = header_p->crc; | |
| 405 const uint32 crc_calculated = CalculatePickleCRC(pickle); | |
| 406 | |
| 407 if (crc_read != crc_calculated) { | |
| 408 LOG(WARNING) << "Invalid CRC in Simple Index file."; | |
| 409 return; | |
| 410 } | |
| 411 | |
| 412 SimpleIndexFile::IndexMetadata index_metadata; | |
| 413 if (!index_metadata.Deserialize(&pickle_it)) { | |
| 414 LOG(ERROR) << "Invalid index_metadata on Simple Cache Index."; | |
| 415 return; | |
| 416 } | |
| 417 | |
| 418 if (!index_metadata.CheckIndexMetadata()) { | |
| 419 LOG(ERROR) << "Invalid index_metadata on Simple Cache Index."; | |
| 420 return; | |
| 421 } | |
| 422 | |
| 423 #if !defined(OS_WIN) | |
| 424 // TODO(gavinp): Consider using std::unordered_map. | |
| 425 entries->resize(index_metadata.GetNumberOfEntries() + kExtraSizeForMerge); | |
| 426 #endif | |
| 427 while (entries->size() < index_metadata.GetNumberOfEntries()) { | |
| 428 uint64 hash_key; | |
| 429 EntryMetadata entry_metadata; | |
| 430 if (!pickle_it.ReadUInt64(&hash_key) || | |
| 431 !entry_metadata.Deserialize(&pickle_it)) { | |
| 432 LOG(WARNING) << "Invalid EntryMetadata in Simple Index file."; | |
| 433 entries->clear(); | |
| 434 return; | |
| 435 } | |
| 436 SimpleIndex::InsertInEntrySet(hash_key, entry_metadata, entries); | |
| 437 } | |
| 438 | |
| 439 int64 cache_last_modified; | |
| 440 if (!pickle_it.ReadInt64(&cache_last_modified)) { | |
| 441 entries->clear(); | |
| 442 return; | |
| 443 } | |
| 444 DCHECK(out_cache_last_modified); | |
| 445 *out_cache_last_modified = base::Time::FromInternalValue(cache_last_modified); | |
| 446 | |
| 447 out_result->did_load = true; | |
| 448 } | |
| 449 | |
| 450 // static | |
| 451 void SimpleIndexFile::SyncRestoreFromDisk( | |
| 452 const base::FilePath& cache_directory, | |
| 453 const base::FilePath& index_file_path, | |
| 454 SimpleIndexLoadResult* out_result) { | |
| 455 VLOG(1) << "Simple Cache Index is being restored from disk."; | |
| 456 simple_util::SimpleCacheDeleteFile(index_file_path); | |
| 457 out_result->Reset(); | |
| 458 SimpleIndex::EntrySet* entries = &out_result->entries; | |
| 459 | |
| 460 const bool did_succeed = TraverseCacheDirectory( | |
| 461 cache_directory, base::Bind(&ProcessEntryFile, entries)); | |
| 462 if (!did_succeed) { | |
| 463 LOG(ERROR) << "Could not reconstruct index from disk"; | |
| 464 return; | |
| 465 } | |
| 466 out_result->did_load = true; | |
| 467 // When we restore from disk we write the merged index file to disk right | |
| 468 // away, this might save us from having to restore again next time. | |
| 469 out_result->flush_required = true; | |
| 470 } | |
| 471 | |
| 472 // static | |
| 473 bool SimpleIndexFile::LegacyIsIndexFileStale( | |
| 474 base::Time cache_last_modified, | |
| 475 const base::FilePath& index_file_path) { | |
| 476 base::Time index_mtime; | |
| 477 if (!simple_util::GetMTime(index_file_path, &index_mtime)) | |
| 478 return true; | |
| 479 return index_mtime < cache_last_modified; | |
| 480 } | |
| 481 | |
| 482 } // namespace disk_cache | |
| OLD | NEW |