diff options
author | pasko@chromium.org <pasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-19 15:16:30 +0000 |
---|---|---|
committer | pasko@chromium.org <pasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-19 15:16:30 +0000 |
commit | fc9cffc29a4af5347f616399ff19244539c729fe (patch) | |
tree | 4322ca3cfcd5826d3af066ee18bc844c3a2920fd /net/disk_cache | |
parent | 9c8b1f97fa93386c3bd4bd86d7ac483dfdb14011 (diff) | |
download | chromium_src-fc9cffc29a4af5347f616399ff19244539c729fe.zip chromium_src-fc9cffc29a4af5347f616399ff19244539c729fe.tar.gz chromium_src-fc9cffc29a4af5347f616399ff19244539c729fe.tar.bz2 |
Simple Cache: transactional index in subdirectory
This change also introduces a minimalistic infrastructure for upgrading cache on disk during browser startup. See simple_version_upgrade.h for more details.
BUG=261618
Review URL: https://chromiumcodereview.appspot.com/22184002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@224131 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/disk_cache')
-rw-r--r-- | net/disk_cache/backend_unittest.cc | 4 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_backend_impl.cc | 56 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_backend_version.h | 27 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_entry_format.h | 2 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_entry_format_history.h | 62 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_index_file.cc | 262 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_index_file.h | 36 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_index_file_unittest.cc | 53 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_synchronous_entry.cc | 5 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_version_upgrade.cc | 203 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_version_upgrade.h | 50 | ||||
-rw-r--r-- | net/disk_cache/simple/simple_version_upgrade_unittest.cc | 146 |
12 files changed, 713 insertions, 193 deletions
diff --git a/net/disk_cache/backend_unittest.cc b/net/disk_cache/backend_unittest.cc index ab00178..999cc7f 100644 --- a/net/disk_cache/backend_unittest.cc +++ b/net/disk_cache/backend_unittest.cc @@ -3470,4 +3470,8 @@ TEST_F(DiskCacheBackendTest, SimpleCacheEnumerationCorruption) { EXPECT_TRUE(keys_to_match.empty()); } +// TODO(pasko): Add a Simple Cache test that would simulate upgrade from the +// version with the index file in the cache directory to the version with the +// index file in subdirectory. + #endif // defined(OS_POSIX) diff --git a/net/disk_cache/simple/simple_backend_impl.cc b/net/disk_cache/simple/simple_backend_impl.cc index 983b211..9e926bb 100644 --- a/net/disk_cache/simple/simple_backend_impl.cc +++ b/net/disk_cache/simple/simple_backend_impl.cc @@ -33,6 +33,7 @@ #include "net/disk_cache/simple/simple_index_file.h" #include "net/disk_cache/simple/simple_synchronous_entry.h" #include "net/disk_cache/simple/simple_util.h" +#include "net/disk_cache/simple/simple_version_upgrade.h" using base::Callback; using base::Closure; @@ -132,65 +133,12 @@ void DeleteBackendImpl(disk_cache::Backend** backend, // Detects if the files in the cache directory match the current disk cache // backend type and version. If the directory contains no cache, occupies it // with the fresh structure. -// -// There is a convention among disk cache backends: looking at the magic in the -// file "index" it should be sufficient to determine if the cache belongs to the -// currently running backend. The Simple Backend stores its index in the file -// "the-real-index" (see simple_index.cc) and the file "index" only signifies -// presence of the implementation's magic and version. There are two reasons for -// that: -// 1. Absence of the index is itself not a fatal error in the Simple Backend -// 2. The Simple Backend has pickled file format for the index making it hacky -// to have the magic in the right place. bool FileStructureConsistent(const base::FilePath& path) { if (!base::PathExists(path) && !file_util::CreateDirectory(path)) { LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName(); return false; } - const base::FilePath fake_index = path.AppendASCII("index"); - base::PlatformFileError error; - base::PlatformFile fake_index_file = base::CreatePlatformFile( - fake_index, - base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, - NULL, - &error); - if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { - base::PlatformFile file = base::CreatePlatformFile( - fake_index, - base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, - NULL, &error); - disk_cache::SimpleFileHeader file_contents; - file_contents.initial_magic_number = disk_cache::kSimpleInitialMagicNumber; - file_contents.version = disk_cache::kSimpleVersion; - int bytes_written = base::WritePlatformFile( - file, 0, reinterpret_cast<char*>(&file_contents), - sizeof(file_contents)); - if (!base::ClosePlatformFile(file) || - bytes_written != sizeof(file_contents)) { - LOG(ERROR) << "Failed to write cache structure file: " - << path.LossyDisplayName(); - return false; - } - return true; - } else if (error != base::PLATFORM_FILE_OK) { - LOG(ERROR) << "Could not open cache structure file: " - << path.LossyDisplayName(); - return false; - } else { - disk_cache::SimpleFileHeader file_header; - int bytes_read = base::ReadPlatformFile( - fake_index_file, 0, reinterpret_cast<char*>(&file_header), - sizeof(file_header)); - if (!base::ClosePlatformFile(fake_index_file) || - bytes_read != sizeof(file_header) || - file_header.initial_magic_number != - disk_cache::kSimpleInitialMagicNumber || - file_header.version != disk_cache::kSimpleVersion) { - LOG(ERROR) << "File structure does not match the disk cache backend."; - return false; - } - return true; - } + return disk_cache::UpgradeSimpleCacheOnDisk(path); } // A short bindable thunk that can call a completion callback. Intended to be diff --git a/net/disk_cache/simple/simple_backend_version.h b/net/disk_cache/simple/simple_backend_version.h new file mode 100644 index 0000000..fe350a2 --- /dev/null +++ b/net/disk_cache/simple/simple_backend_version.h @@ -0,0 +1,27 @@ +// Copyright 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. + +#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_ +#define NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_ + +namespace disk_cache { + +// Short rules helping to think about data upgrades within Simple Cache: +// * ALL changes of on-disk data format, backward-compatible or not, +// forward-compatible or not, require updating the |kSimpleVersion|. +// * All cache Upgrades are performed on backend start, must be finished +// before the new backend starts processing any incoming operations. +// * If the Upgrade is not implemented for transition from +// |kSimpleVersion - 1| then the whole cache directory will be cleared. +// * Dropping cache data on disk or some of its parts can be a valid way to +// Upgrade. +const uint32 kSimpleVersion = 6; + +// The version of the entry file(s) as written to disk. Must be updated iff the +// entry format changes with the overall backend version update. +const uint32 kSimpleEntryVersionOnDisk = 5; + +} // namespace disk_cache + +#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_ diff --git a/net/disk_cache/simple/simple_entry_format.h b/net/disk_cache/simple/simple_entry_format.h index cfd598db..8224b85 100644 --- a/net/disk_cache/simple/simple_entry_format.h +++ b/net/disk_cache/simple/simple_entry_format.h @@ -32,8 +32,6 @@ const uint64 kSimpleFinalMagicNumber = GG_UINT64_C(0xf4fa6f45970d41d8); // - the key. // - the data. // - at the end, a SimpleFileEOF record. -const uint32 kSimpleVersion = 5; - static const int kSimpleEntryFileCount = 2; static const int kSimpleEntryStreamCount = 3; diff --git a/net/disk_cache/simple/simple_entry_format_history.h b/net/disk_cache/simple/simple_entry_format_history.h new file mode 100644 index 0000000..f7b818a59 --- /dev/null +++ b/net/disk_cache/simple/simple_entry_format_history.h @@ -0,0 +1,62 @@ +// Copyright 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. + +#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_ +#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_ + +#include "base/basictypes.h" +#include "base/port.h" +#include "net/base/net_export.h" + +namespace disk_cache { + +namespace simplecache_v5 { + +const uint64 kSimpleInitialMagicNumber = GG_UINT64_C(0xfcfb6d1ba7725c30); +const uint64 kSimpleFinalMagicNumber = GG_UINT64_C(0xf4fa6f45970d41d8); + +// A file containing stream 0 and stream 1 in the Simple cache consists of: +// - a SimpleFileHeader. +// - the key. +// - the data from stream 1. +// - a SimpleFileEOF record for stream 1. +// - the data from stream 0. +// - a SimpleFileEOF record for stream 0. + +// A file containing stream 2 in the Simple cache consists of: +// - a SimpleFileHeader. +// - the key. +// - the data. +// - at the end, a SimpleFileEOF record. +static const int kSimpleEntryFileCount = 2; +static const int kSimpleEntryStreamCount = 3; + +struct NET_EXPORT_PRIVATE SimpleFileHeader { + SimpleFileHeader(); + + uint64 initial_magic_number; + uint32 version; + uint32 key_length; + uint32 key_hash; +}; + +struct NET_EXPORT_PRIVATE SimpleFileEOF { + enum Flags { + FLAG_HAS_CRC32 = (1U << 0), + }; + + SimpleFileEOF(); + + uint64 final_magic_number; + uint32 flags; + uint32 data_crc32; + // |stream_size| is only used in the EOF record for stream 0. + uint32 stream_size; +}; + +} // namespace simplecache_v5 + +} // namespace disk_cache + +#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_ diff --git a/net/disk_cache/simple/simple_index_file.cc b/net/disk_cache/simple/simple_index_file.cc index 488d645..b8ec6d7 100644 --- a/net/disk_cache/simple/simple_index_file.cc +++ b/net/disk_cache/simple/simple_index_file.cc @@ -14,6 +14,7 @@ #include "base/single_thread_task_runner.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" @@ -43,36 +44,43 @@ void DoomEntrySetReply(const net::CompletionCallback& reply_callback, reply_callback.Run(result); } -void WriteToDiskInternal(net::CacheType cache_type, - const base::FilePath& index_filename, - const base::FilePath& temp_index_filename, - scoped_ptr<Pickle> pickle, - const base::TimeTicks& start_time, - bool app_on_background) { +// 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, +}; + +void UmaRecordIndexFileState(IndexFileState state, net::CacheType cache_type) { + SIMPLE_CACHE_UMA(ENUMERATION, + "IndexFileStateOnLoad", cache_type, state, INDEX_STATE_MAX); +} + +// Used in histograms. Please only add new values at the end. +enum IndexInitMethod { + INITIALIZE_METHOD_RECOVERED = 0, + INITIALIZE_METHOD_LOADED = 1, + INITIALIZE_METHOD_NEWCACHE = 2, + INITIALIZE_METHOD_MAX = 3, +}; + +void UmaRecordIndexInitMethod(IndexInitMethod method, + net::CacheType cache_type) { + SIMPLE_CACHE_UMA(ENUMERATION, + "IndexInitializeMethod", cache_type, + method, INITIALIZE_METHOD_MAX); +} + +bool WritePickleFile(Pickle* pickle, const base::FilePath& file_name) { int bytes_written = file_util::WriteFile( - temp_index_filename, - reinterpret_cast<const char*>(pickle->data()), - pickle->size()); - DCHECK_EQ(bytes_written, implicit_cast<int>(pickle->size())); - if (bytes_written != static_cast<int>(pickle->size())) { - // TODO(felipeg): Add better error handling. - LOG(ERROR) << "Could not write Simple Cache index to temporary file: " - << temp_index_filename.value(); - base::DeleteFile(temp_index_filename, /* recursive = */ false); - } else { - // Swap temp and index_file. - bool result = base::ReplaceFile(temp_index_filename, index_filename, NULL); - DCHECK(result); - } - 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)); + file_name, static_cast<const char*>(pickle->data()), pickle->size()); + if (bytes_written != implicit_cast<int>(pickle->size())) { + base::DeleteFile(file_name, /* recursive = */ false); + return false; } + return true; } // Called for each cache directory traversal iteration. @@ -137,6 +145,13 @@ void SimpleIndexLoadResult::Reset() { 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), @@ -158,6 +173,16 @@ void SimpleIndexFile::IndexMetadata::Serialize(Pickle* pickle) const { pickle->WriteUInt64(cache_size_); } +// static +bool SimpleIndexFile::SerializeFinalData(base::Time cache_modified, + Pickle* pickle) { + if (!pickle->WriteInt64(cache_modified.ToInternalValue())) + return false; + SimpleIndexFile::PickleHeader* header_p = pickle->headerT<PickleHeader>(); + header_p->crc = CalculatePickleCRC(*pickle); + return true; +} + bool SimpleIndexFile::IndexMetadata::Deserialize(PickleIterator* it) { DCHECK(it); return it->ReadUInt64(&magic_number_) && @@ -166,10 +191,55 @@ bool SimpleIndexFile::IndexMetadata::Deserialize(PickleIterator* it) { it->ReadUInt64(&cache_size_); } +void SimpleIndexFile::SyncWriteToDisk(net::CacheType cache_type, + const base::FilePath& cache_directory, + const base::FilePath& index_filename, + const base::FilePath& temp_index_filename, + scoped_ptr<Pickle> pickle, + const base::TimeTicks& start_time, + bool app_on_background) { + // 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::PlatformFileInfo cache_dir_info; + 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 (!WritePickleFile(pickle.get(), temp_index_filename)) { + if (!file_util::CreateDirectory(temp_index_filename.DirName())) { + LOG(ERROR) << "Could not create a directory to hold the index file"; + return; + } + 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. + bool result = base::ReplaceFile(temp_index_filename, index_filename, NULL); + DCHECK(result); + + 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() { return number_of_entries_ <= kMaxEntiresInIndex && - magic_number_ == disk_cache::kSimpleIndexMagicNumber && - version_ == disk_cache::kSimpleVersion; + magic_number_ == kSimpleIndexMagicNumber && + version_ == kSimpleVersion; } SimpleIndexFile::SimpleIndexFile( @@ -181,8 +251,10 @@ SimpleIndexFile::SimpleIndexFile( worker_pool_(worker_pool), cache_type_(cache_type), cache_directory_(cache_directory), - index_file_(cache_directory_.AppendASCII(kIndexFileName)), - temp_index_file_(cache_directory_.AppendASCII(kTempIndexFileName)) { + index_file_(cache_directory_.AppendASCII(kIndexDirectory) + .AppendASCII(kIndexFileName)), + temp_index_file_(cache_directory_.AppendASCII(kIndexDirectory) + .AppendASCII(kTempIndexFileName)) { } SimpleIndexFile::~SimpleIndexFile() {} @@ -204,8 +276,9 @@ void SimpleIndexFile::WriteToDisk(const SimpleIndex::EntrySet& entry_set, IndexMetadata index_metadata(entry_set.size(), cache_size); scoped_ptr<Pickle> pickle = Serialize(index_metadata, entry_set); cache_thread_->PostTask(FROM_HERE, base::Bind( - &WriteToDiskInternal, + &SimpleIndexFile::SyncWriteToDisk, cache_type_, + cache_directory_, index_file_, temp_index_file_, base::Passed(&pickle), @@ -231,84 +304,51 @@ void SimpleIndexFile::SyncLoadIndexEntries( const base::FilePath& cache_directory, const base::FilePath& index_file_path, SimpleIndexLoadResult* out_result) { - // TODO(felipeg): probably could load a stale index and use it for something. - const SimpleIndex::EntrySet& entries = out_result->entries; - - const bool index_file_exists = base::PathExists(index_file_path); - - // Used in histograms. Please only add new values at the end. - enum { - INDEX_STATE_CORRUPT = 0, - INDEX_STATE_STALE = 1, - INDEX_STATE_FRESH = 2, - INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3, - INDEX_STATE_MAX = 4, - } index_file_state; - - // Only load if the index is not stale. - if (IsIndexFileStale(cache_last_modified, index_file_path)) { - index_file_state = INDEX_STATE_STALE; - } else { - index_file_state = INDEX_STATE_FRESH; - base::Time latest_dir_mtime; - if (simple_util::GetMTime(cache_directory, &latest_dir_mtime) && - IsIndexFileStale(latest_dir_mtime, index_file_path)) { - // A file operation has updated the directory since we last looked at it - // during backend initialization. - index_file_state = INDEX_STATE_FRESH_CONCURRENT_UPDATES; - } - - const base::TimeTicks start = base::TimeTicks::Now(); - SyncLoadFromDisk(index_file_path, out_result); - SIMPLE_CACHE_UMA(TIMES, - "IndexLoadTime", cache_type, - base::TimeTicks::Now() - start); - SIMPLE_CACHE_UMA(COUNTS, - "IndexEntriesLoaded", cache_type, - out_result->did_load ? entries.size() : 0); - if (!out_result->did_load) - index_file_state = INDEX_STATE_CORRUPT; - } - SIMPLE_CACHE_UMA(ENUMERATION, - "IndexFileStateOnLoad", cache_type, - index_file_state, INDEX_STATE_MAX); + // 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) { - 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, - "IndexEntriesRestored", cache_type, entries.size()); + if (index_file_existed) + UmaRecordIndexFileState(INDEX_STATE_CORRUPT, cache_type); + } else { + if (cache_last_modified <= last_cache_seen_by_index) { + 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); + } + UmaRecordIndexInitMethod(INITIALIZE_METHOD_LOADED, cache_type); + return; + } + UmaRecordIndexFileState(INDEX_STATE_STALE, cache_type); } - // Used in histograms. Please only add new values at the end. - enum { - INITIALIZE_METHOD_RECOVERED = 0, - INITIALIZE_METHOD_LOADED = 1, - INITIALIZE_METHOD_NEWCACHE = 2, - INITIALIZE_METHOD_MAX = 3, - }; - int initialize_method; - if (index_file_exists) { - if (out_result->flush_required) - initialize_method = INITIALIZE_METHOD_RECOVERED; - else - initialize_method = INITIALIZE_METHOD_LOADED; + // Reconstruct the index by scanning the disk for 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, "IndexEntriesRestored", cache_type, + out_result->entries.size()); + if (index_file_existed) { + UmaRecordIndexInitMethod(INITIALIZE_METHOD_RECOVERED, cache_type); } else { + UmaRecordIndexInitMethod(INITIALIZE_METHOD_NEWCACHE, cache_type); SIMPLE_CACHE_UMA(COUNTS, - "IndexCreatedEntryCount", cache_type, entries.size()); - initialize_method = INITIALIZE_METHOD_NEWCACHE; + "IndexCreatedEntryCount", cache_type, + out_result->entries.size()); } - - SIMPLE_CACHE_UMA(ENUMERATION, - "IndexInitializeMethod", cache_type, - initialize_method, INITIALIZE_METHOD_MAX); } // static void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename, + base::Time* out_last_cache_seen_by_index, SimpleIndexLoadResult* out_result) { out_result->Reset(); @@ -321,7 +361,9 @@ void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename, SimpleIndexFile::Deserialize( reinterpret_cast<const char*>(index_file_map.data()), - index_file_map.length(), out_result); + index_file_map.length(), + out_last_cache_seen_by_index, + out_result); if (!out_result->did_load) base::DeleteFile(index_filename, false); @@ -339,14 +381,12 @@ scoped_ptr<Pickle> SimpleIndexFile::Serialize( pickle->WriteUInt64(it->first); it->second.Serialize(pickle.get()); } - SimpleIndexFile::PickleHeader* header_p = - pickle->headerT<SimpleIndexFile::PickleHeader>(); - header_p->crc = CalculatePickleCRC(*pickle); return pickle.Pass(); } // static void SimpleIndexFile::Deserialize(const char* data, int data_len, + base::Time* out_cache_last_modified, SimpleIndexLoadResult* out_result) { DCHECK(data); @@ -360,7 +400,6 @@ void SimpleIndexFile::Deserialize(const char* data, int data_len, } PickleIterator pickle_it(pickle); - SimpleIndexFile::PickleHeader* header_p = pickle.headerT<SimpleIndexFile::PickleHeader>(); const uint32 crc_read = header_p->crc; @@ -398,6 +437,14 @@ void SimpleIndexFile::Deserialize(const char* data, int data_len, SimpleIndex::InsertInEntrySet(hash_key, entry_metadata, entries); } + int64 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->did_load = true; } @@ -424,8 +471,9 @@ void SimpleIndexFile::SyncRestoreFromDisk( } // static -bool SimpleIndexFile::IsIndexFileStale(base::Time cache_last_modified, - const base::FilePath& index_file_path) { +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; diff --git a/net/disk_cache/simple/simple_index_file.h b/net/disk_cache/simple/simple_index_file.h index 7c7a34e..b72ea9f 100644 --- a/net/disk_cache/simple/simple_index_file.h +++ b/net/disk_cache/simple/simple_index_file.h @@ -113,20 +113,29 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { const base::FilePath& index_file_path, SimpleIndexLoadResult* out_result); - // Load the index file from disk returning an EntrySet. Upon failure, returns - // NULL. + // Load the index file from disk returning an EntrySet. static void SyncLoadFromDisk(const base::FilePath& index_filename, + base::Time* out_last_cache_seen_by_index, SimpleIndexLoadResult* out_result); // Returns a scoped_ptr for a newly allocated Pickle containing the serialized - // data to be written to a file. + // data to be written to a file. Note: the pickle is not in a consistent state + // immediately after calling this menthod, one needs to call + // SerializeFinalData to make it ready to write to a file. static scoped_ptr<Pickle> Serialize( const SimpleIndexFile::IndexMetadata& index_metadata, const SimpleIndex::EntrySet& entries); + // Appends cache modification time data to the serialized format. This is + // performed on a thread accessing the disk. It is not combined with the main + // serialization path to avoid extra thread hops or copying the pickle to the + // worker thread. + static bool SerializeFinalData(base::Time cache_modified, Pickle* pickle); + // Given the contents of an index file |data| of length |data_len|, returns // the corresponding EntrySet. Returns NULL on error. static void Deserialize(const char* data, int data_len, + base::Time* out_cache_last_modified, SimpleIndexLoadResult* out_result); // Implemented either in simple_index_file_posix.cc or @@ -138,6 +147,15 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { const base::FilePath& cache_path, const EntryFileCallback& entry_file_callback); + // Writes the index file to disk atomically. + static void SyncWriteToDisk(net::CacheType cache_type, + const base::FilePath& cache_directory, + const base::FilePath& index_filename, + const base::FilePath& temp_index_filename, + scoped_ptr<Pickle> pickle, + const base::TimeTicks& start_time, + bool app_on_background); + // Scan the index directory for entries, returning an EntrySet of all entries // found. static void SyncRestoreFromDisk(const base::FilePath& cache_directory, @@ -145,9 +163,11 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { SimpleIndexLoadResult* out_result); // Determines if an index file is stale relative to the time of last - // modification of the cache directory. - static bool IsIndexFileStale(base::Time cache_last_modified, - const base::FilePath& index_file_path); + // modification of the cache directory. Obsolete, used only for a histogram to + // compare with the new method. + // TODO(pasko): remove this method after getting enough data. + static bool LegacyIsIndexFileStale(base::Time cache_last_modified, + const base::FilePath& index_file_path); struct PickleHeader : public Pickle::Header { uint32 crc; @@ -160,6 +180,10 @@ class NET_EXPORT_PRIVATE SimpleIndexFile { const base::FilePath index_file_; const base::FilePath temp_index_file_; + static const char kIndexDirectory[]; + static const char kIndexFileName[]; + static const char kTempIndexFileName[]; + DISALLOW_COPY_AND_ASSIGN(SimpleIndexFile); }; diff --git a/net/disk_cache/simple/simple_index_file_unittest.cc b/net/disk_cache/simple/simple_index_file_unittest.cc index 472c8ad..267c6ba 100644 --- a/net/disk_cache/simple/simple_index_file_unittest.cc +++ b/net/disk_cache/simple/simple_index_file_unittest.cc @@ -13,6 +13,7 @@ #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "net/base/cache_type.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_index.h" #include "net/disk_cache/simple/simple_index_file.h" @@ -63,8 +64,9 @@ TEST(IndexMetadataTest, Serialize) { class WrappedSimpleIndexFile : public SimpleIndexFile { public: using SimpleIndexFile::Deserialize; - using SimpleIndexFile::IsIndexFileStale; + using SimpleIndexFile::LegacyIsIndexFileStale; using SimpleIndexFile::Serialize; + using SimpleIndexFile::SerializeFinalData; explicit WrappedSimpleIndexFile(const base::FilePath& index_file_directory) : SimpleIndexFile(base::MessageLoopProxy::current().get(), @@ -77,6 +79,10 @@ class WrappedSimpleIndexFile : public SimpleIndexFile { const base::FilePath& GetIndexFilePath() const { return index_file_; } + + bool CreateIndexFileDirectory() const { + return file_util::CreateDirectory(index_file_.DirName()); + } }; class SimpleIndexFileTest : public testing::Test { @@ -124,12 +130,16 @@ TEST_F(SimpleIndexFileTest, Serialize) { scoped_ptr<Pickle> pickle = WrappedSimpleIndexFile::Serialize( index_metadata, entries); EXPECT_TRUE(pickle.get() != NULL); - + base::Time now = base::Time::Now(); + EXPECT_TRUE(WrappedSimpleIndexFile::SerializeFinalData(now, pickle.get())); + base::Time when_index_last_saw_cache; SimpleIndexLoadResult deserialize_result; WrappedSimpleIndexFile::Deserialize(static_cast<const char*>(pickle->data()), - pickle->size(), - &deserialize_result); + pickle->size(), + &when_index_last_saw_cache, + &deserialize_result); EXPECT_TRUE(deserialize_result.did_load); + EXPECT_EQ(now, when_index_last_saw_cache); const SimpleIndex::EntrySet& new_entries = deserialize_result.entries; EXPECT_EQ(entries.size(), new_entries.size()); @@ -140,7 +150,7 @@ TEST_F(SimpleIndexFileTest, Serialize) { } } -TEST_F(SimpleIndexFileTest, IsIndexFileStale) { +TEST_F(SimpleIndexFileTest, LegacyIsIndexFileStale) { base::ScopedTempDir cache_dir; ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); base::Time cache_mtime; @@ -148,31 +158,30 @@ TEST_F(SimpleIndexFileTest, IsIndexFileStale) { ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime)); WrappedSimpleIndexFile simple_index_file(cache_path); + ASSERT_TRUE(simple_index_file.CreateIndexFileDirectory()); const base::FilePath& index_path = simple_index_file.GetIndexFilePath(); - EXPECT_TRUE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); + EXPECT_TRUE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); const std::string kDummyData = "nothing to be seen here"; EXPECT_EQ(static_cast<int>(kDummyData.size()), file_util::WriteFile(index_path, kDummyData.data(), kDummyData.size())); ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime)); - EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); + EXPECT_FALSE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); const base::Time past_time = base::Time::Now() - base::TimeDelta::FromSeconds(10); EXPECT_TRUE(file_util::TouchFile(index_path, past_time, past_time)); EXPECT_TRUE(file_util::TouchFile(cache_path, past_time, past_time)); ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime)); - EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); - const base::Time even_older = - past_time - base::TimeDelta::FromSeconds(10); + EXPECT_FALSE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); + const base::Time even_older = past_time - base::TimeDelta::FromSeconds(10); EXPECT_TRUE(file_util::TouchFile(index_path, even_older, even_older)); - EXPECT_TRUE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime, - index_path)); - + EXPECT_TRUE( + WrappedSimpleIndexFile::LegacyIsIndexFileStale(cache_mtime, index_path)); } TEST_F(SimpleIndexFileTest, WriteThenLoadIndex) { @@ -223,17 +232,17 @@ TEST_F(SimpleIndexFileTest, LoadCorruptIndex) { ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); WrappedSimpleIndexFile simple_index_file(cache_dir.path()); + ASSERT_TRUE(simple_index_file.CreateIndexFileDirectory()); const base::FilePath& index_path = simple_index_file.GetIndexFilePath(); const std::string kDummyData = "nothing to be seen here"; - EXPECT_EQ(static_cast<int>(kDummyData.size()), - file_util::WriteFile(index_path, - kDummyData.data(), - kDummyData.size())); + EXPECT_EQ( + implicit_cast<int>(kDummyData.size()), + file_util::WriteFile(index_path, kDummyData.data(), kDummyData.size())); base::Time fake_cache_mtime; ASSERT_TRUE(simple_util::GetMTime(simple_index_file.GetIndexFilePath(), &fake_cache_mtime)); - EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(fake_cache_mtime, - index_path)); + EXPECT_FALSE(WrappedSimpleIndexFile::LegacyIsIndexFileStale(fake_cache_mtime, + index_path)); SimpleIndexLoadResult load_index_result; simple_index_file.LoadIndexEntries(fake_cache_mtime, diff --git a/net/disk_cache/simple/simple_synchronous_entry.cc b/net/disk_cache/simple/simple_synchronous_entry.cc index 502264d..ed0fadd 100644 --- a/net/disk_cache/simple/simple_synchronous_entry.cc +++ b/net/disk_cache/simple/simple_synchronous_entry.cc @@ -18,6 +18,7 @@ #include "base/strings/stringprintf.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" +#include "net/disk_cache/simple/simple_backend_version.h" #include "net/disk_cache/simple/simple_histogram_macros.h" #include "net/disk_cache/simple/simple_util.h" #include "third_party/zlib/zlib.h" @@ -645,7 +646,7 @@ int SimpleSynchronousEntry::InitializeForOpen( return net::ERR_FAILED; } - if (header.version != kSimpleVersion) { + if (header.version != kSimpleEntryVersionOnDisk) { DLOG(WARNING) << "Unreadable version."; RecordSyncOpenResult(cache_type_, OPEN_ENTRY_BAD_VERSION, had_index); return net::ERR_FAILED; @@ -699,7 +700,7 @@ int SimpleSynchronousEntry::InitializeForCreate( for (int i = 0; i < kSimpleEntryFileCount; ++i) { SimpleFileHeader header; header.initial_magic_number = kSimpleInitialMagicNumber; - header.version = kSimpleVersion; + header.version = kSimpleEntryVersionOnDisk; header.key_length = key_.size(); header.key_hash = base::Hash(key_); diff --git a/net/disk_cache/simple/simple_version_upgrade.cc b/net/disk_cache/simple/simple_version_upgrade.cc new file mode 100644 index 0000000..dfc6ef4 --- /dev/null +++ b/net/disk_cache/simple/simple_version_upgrade.cc @@ -0,0 +1,203 @@ +// Copyright 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_version_upgrade.h" + +#include <cstring> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "net/disk_cache/simple/simple_backend_version.h" +#include "net/disk_cache/simple/simple_entry_format_history.h" +#include "third_party/zlib/zlib.h" + +namespace { + +// It is not possible to upgrade cache structures on disk that are of version +// below this, the entire cache should be dropped for them. +const uint32 kMinVersionAbleToUpgrade = 5; + +const char kFakeIndexFileName[] = "index"; +const char kIndexFileName[] = "the-real-index"; + +void LogMessageFailedUpgradeFromVersion(int version) { + LOG(ERROR) << "Failed to upgrade Simple Cache from version: " << version; +} + +bool WriteFakeIndexFile(const base::FilePath& file_name) { + base::PlatformFileError error; + base::PlatformFile file = base::CreatePlatformFile( + file_name, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + NULL, + &error); + disk_cache::FakeIndexData file_contents; + file_contents.initial_magic_number = + disk_cache::simplecache_v5::kSimpleInitialMagicNumber; + file_contents.version = disk_cache::kSimpleVersion; + int bytes_written = base::WritePlatformFile( + file, 0, reinterpret_cast<char*>(&file_contents), sizeof(file_contents)); + if (!base::ClosePlatformFile(file) || + bytes_written != sizeof(file_contents)) { + LOG(ERROR) << "Failed to write fake index file: " + << file_name.LossyDisplayName(); + return false; + } + return true; +} + +} // namespace + +namespace disk_cache { + +FakeIndexData::FakeIndexData() { + // Make hashing repeatable: leave no padding bytes untouched. + std::memset(this, 0, sizeof(*this)); +} + +// Migrates the cache directory from version 4 to version 5. +// Returns true iff it succeeds. +// +// The V5 and V6 caches differ in the name of the index file (it moved to a +// subdirectory) and in the file format (directory last-modified time observed +// by the index writer has gotten appended to the pickled format). +// +// To keep complexity small this specific upgrade code *deletes* the old index +// file. The directory for the new index file has to be created lazily anyway, +// so it is not done in the upgrader. +// +// Below is the detailed description of index file format differences. It is for +// reference purposes. This documentation would be useful to move closer to the +// next index upgrader when the latter gets introduced. +// +// Path: +// V5: $cachedir/the-real-index +// V6: $cachedir/index-dir/the-real-index +// +// Pickled file format: +// Both formats extend Pickle::Header by 32bit value of the CRC-32 of the +// pickled data. +// <v5-index> ::= <v5-index-metadata> <entry-info>* +// <v5-index-metadata> ::= UInt64(kSimpleIndexMagicNumber) +// UInt32(4) +// UInt64(<number-of-entries>) +// UInt64(<cache-size-in-bytes>) +// <entry-info> ::= UInt64(<hash-of-the-key>) +// Int64(<entry-last-used-time>) +// UInt64(<entry-size-in-bytes>) +// <v6-index> ::= <v6-index-metadata> +// <entry-info>* +// Int64(<cache-dir-mtime>) +// <v6-index-metadata> ::= UInt64(kSimpleIndexMagicNumber) +// UInt32(5) +// UInt64(<number-of-entries>) +// UInt64(<cache-size-in-bytes>) +// Where: +// <entry-size-in-bytes> is equal the sum of all file sizes of the entry. +// <cache-dir-mtime> is the last modification time with nanosecond precision +// of the directory, where all files for entries are stored. +// <hash-of-the-key> represent the first 64 bits of a SHA-1 of the key. +bool UpgradeIndexV5V6(const base::FilePath& cache_directory) { + const base::FilePath old_index_file = + cache_directory.AppendASCII(kIndexFileName); + if (!base::DeleteFile(old_index_file, /* recursive = */ false)) + return false; + return true; +} + +// Some points about the Upgrade process are still not clear: +// 1. if the upgrade path requires dropping cache it would be faster to just +// return an initialization error here and proceed with asynchronous cache +// cleanup in CacheCreator. Should this hack be considered valid? Some smart +// tests may fail. +// 2. Because Android process management allows for killing a process at any +// time, the upgrade process may need to deal with a partially completed +// previous upgrade. For example, while upgrading A -> A + 2 we are the +// process gets killed and some parts are remaining at version A + 1. There +// are currently no generic mechanisms to resolve this situation, co the +// upgrade codes need to ensure they can continue after being stopped in the +// middle. It also means that the "fake index" must be flushed in between the +// upgrade steps. Atomicity of this is an interesting research topic. The +// intermediate fake index flushing must be added as soon as we add more +// upgrade steps. +bool UpgradeSimpleCacheOnDisk(const base::FilePath& path) { + // There is a convention among disk cache backends: looking at the magic in + // the file "index" it should be sufficient to determine if the cache belongs + // to the currently running backend. The Simple Backend stores its index in + // the file "the-real-index" (see simple_index_file.cc) and the file "index" + // only signifies presence of the implementation's magic and version. There + // are two reasons for that: + // 1. Absence of the index is itself not a fatal error in the Simple Backend + // 2. The Simple Backend has pickled file format for the index making it hacky + // to have the magic in the right place. + const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName); + base::PlatformFileError error; + base::PlatformFile fake_index_file = base::CreatePlatformFile( + fake_index, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + NULL, + &error); + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { + return WriteFakeIndexFile(fake_index); + } else if (error != base::PLATFORM_FILE_OK) { + return false; + } + FakeIndexData file_header; + int bytes_read = base::ReadPlatformFile(fake_index_file, + 0, + reinterpret_cast<char*>(&file_header), + sizeof(file_header)); + if (!base::ClosePlatformFile(fake_index_file) || + bytes_read != sizeof(file_header) || + file_header.initial_magic_number != + disk_cache::simplecache_v5::kSimpleInitialMagicNumber) { + LOG(ERROR) << "File structure does not match the disk cache backend."; + return false; + } + + uint32 version_from = file_header.version; + if (version_from < kMinVersionAbleToUpgrade || + version_from > kSimpleVersion) { + LOG(ERROR) << "Inconsistent cache version."; + return false; + } + bool upgrade_needed = (version_from != kSimpleVersion); + if (version_from == kMinVersionAbleToUpgrade) { + // Upgrade only the index for V4 -> V5 move. + if (!UpgradeIndexV5V6(path)) { + LogMessageFailedUpgradeFromVersion(file_header.version); + return false; + } + version_from++; + } + if (version_from == kSimpleVersion) { + if (!upgrade_needed) { + return true; + } else { + const base::FilePath temp_fake_index = path.AppendASCII("upgrade-index"); + if (!WriteFakeIndexFile(temp_fake_index)) { + base::DeleteFile(temp_fake_index, /* recursive = */ false); + LOG(ERROR) << "Failed to write a new fake index."; + LogMessageFailedUpgradeFromVersion(file_header.version); + return false; + } + if (!base::ReplaceFile(temp_fake_index, fake_index, NULL)) { + LOG(ERROR) << "Failed to replace the fake index."; + LogMessageFailedUpgradeFromVersion(file_header.version); + return false; + } + return true; + } + } + // Verify during the test stage that the upgraders are implemented for all + // versions. The release build would cause backend initialization failure + // which would then later lead to removing all files known to the backend. + DCHECK_EQ(kSimpleVersion, version_from); + return false; +} + +} // namespace disk_cache diff --git a/net/disk_cache/simple/simple_version_upgrade.h b/net/disk_cache/simple/simple_version_upgrade.h new file mode 100644 index 0000000..352379b --- /dev/null +++ b/net/disk_cache/simple/simple_version_upgrade.h @@ -0,0 +1,50 @@ +// Copyright 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. + +#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_VERSION_UPGRADE_H_ +#define NET_DISK_CACHE_SIMPLE_SIMPLE_VERSION_UPGRADE_H_ + +// Defines functionality to upgrade the file structure of the Simple Cache +// Backend on disk. Assumes no backend operations are running simultaneously. +// Hence must be run at cache initialization step. + +#include "base/basictypes.h" +#include "net/base/net_export.h" + +namespace base { +class FilePath; +} + +namespace disk_cache { + +// Performs all necessary disk IO to upgrade the cache structure if it is +// needed. +// +// Returns true iff no errors were found during consistency checks and all +// necessary transitions succeeded. If this function fails, there is nothing +// left to do other than dropping the whole cache directory. +NET_EXPORT_PRIVATE bool UpgradeSimpleCacheOnDisk(const base::FilePath& path); + +// The format for the fake index has mistakenly acquired two extra fields that +// do not contain any useful data. Since they were equal to zero, they are now +// mandatated to be zero. +struct NET_EXPORT_PRIVATE FakeIndexData { + FakeIndexData(); + + // Must be equal to simplecache_v4::kSimpleInitialMagicNumber. + uint64 initial_magic_number; + + // Must be equal kSimpleVersion when the cache backend is instantiated. + uint32 version; + + uint32 unused_must_be_zero1; + uint32 unused_must_be_zero2; +}; + +// Exposed for testing. +NET_EXPORT_PRIVATE bool UpgradeIndexV5V6(const base::FilePath& cache_directory); + +} // namespace disk_cache + +#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_VERSION_UPGRADE_H_ diff --git a/net/disk_cache/simple/simple_version_upgrade_unittest.cc b/net/disk_cache/simple/simple_version_upgrade_unittest.cc new file mode 100644 index 0000000..c9d42f1 --- /dev/null +++ b/net/disk_cache/simple/simple_version_upgrade_unittest.cc @@ -0,0 +1,146 @@ +// Copyright 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_version_upgrade.h" + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "net/base/net_errors.h" +#include "net/disk_cache/simple/simple_backend_version.h" +#include "net/disk_cache/simple/simple_entry_format_history.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The migration process relies on ability to rename newly created files, which +// could be problematic on Windows XP. +#if defined(OS_POSIX) + +namespace { + +// Same as |disk_cache::kSimpleInitialMagicNumber|. +const uint64 kSimpleInitialMagicNumber = GG_UINT64_C(0xfcfb6d1ba7725c30); + +// The "fake index" file that cache backends use to distinguish whether the +// cache belongs to one backend or another. +const char kFakeIndexFileName[] = "index"; + +// Same as |SimpleIndexFile::kIndexFileName|. +const char kIndexFileName[] = "the-real-index"; + +// Same as |SimpleIndexFile::kIndexDirectory|. +const char kIndexDirectory[] = "index-dir"; + +// Same as |SimpleIndexFile::kTempIndexFileName|. +const char kTempIndexFileName[] = "temp-index"; + +bool WriteFakeIndexFileV5(const base::FilePath& cache_path) { + disk_cache::FakeIndexData data; + data.version = 5; + data.initial_magic_number = kSimpleInitialMagicNumber; + data.unused_must_be_zero1 = 0; + data.unused_must_be_zero2 = 0; + const base::FilePath file_name = cache_path.AppendASCII("index"); + return sizeof(data) == + file_util::WriteFile( + file_name, reinterpret_cast<const char*>(&data), sizeof(data)); +} + +TEST(SimpleVersionUpgradeTest, FailsToMigrateBackwards) { + base::ScopedTempDir cache_dir; + ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); + const base::FilePath cache_path = cache_dir.path(); + + disk_cache::FakeIndexData data; + data.version = 100500; + data.initial_magic_number = kSimpleInitialMagicNumber; + data.unused_must_be_zero1 = 0; + data.unused_must_be_zero2 = 0; + const base::FilePath file_name = cache_path.AppendASCII(kFakeIndexFileName); + ASSERT_EQ(implicit_cast<int>(sizeof(data)), + file_util::WriteFile( + file_name, reinterpret_cast<const char*>(&data), sizeof(data))); + EXPECT_FALSE(disk_cache::UpgradeSimpleCacheOnDisk(cache_dir.path())); +} + +TEST(SimpleVersionUpgradeTest, FakeIndexVersionGetsUpdated) { + base::ScopedTempDir cache_dir; + ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); + const base::FilePath cache_path = cache_dir.path(); + + WriteFakeIndexFileV5(cache_path); + const std::string file_contents("incorrectly serialized data"); + const base::FilePath index_file = cache_path.AppendASCII(kIndexFileName); + ASSERT_EQ(implicit_cast<int>(file_contents.size()), + file_util::WriteFile( + index_file, file_contents.data(), file_contents.size())); + + // Upgrade. + ASSERT_TRUE(disk_cache::UpgradeSimpleCacheOnDisk(cache_path)); + + // Check that the version in the fake index file is updated. + std::string new_fake_index_contents; + ASSERT_TRUE(base::ReadFileToString(cache_path.AppendASCII(kFakeIndexFileName), + &new_fake_index_contents)); + const disk_cache::FakeIndexData* fake_index_header; + EXPECT_EQ(sizeof(*fake_index_header), new_fake_index_contents.size()); + fake_index_header = reinterpret_cast<const disk_cache::FakeIndexData*>( + new_fake_index_contents.data()); + EXPECT_EQ(disk_cache::kSimpleVersion, fake_index_header->version); + EXPECT_EQ(kSimpleInitialMagicNumber, fake_index_header->initial_magic_number); +} + +TEST(SimpleVersionUpgradeTest, UpgradeV5V6IndexMustDisappear) { + base::ScopedTempDir cache_dir; + ASSERT_TRUE(cache_dir.CreateUniqueTempDir()); + const base::FilePath cache_path = cache_dir.path(); + + WriteFakeIndexFileV5(cache_path); + const std::string file_contents("incorrectly serialized data"); + const base::FilePath index_file = cache_path.AppendASCII(kIndexFileName); + ASSERT_EQ(implicit_cast<int>(file_contents.size()), + file_util::WriteFile( + index_file, file_contents.data(), file_contents.size())); + + // Create a few entry-like files. + const uint64 kEntries = 5; + for (uint64 entry_hash = 0; entry_hash < kEntries; ++entry_hash) { + for (int index = 0; index < 3; ++index) { + std::string file_name = + base::StringPrintf("%016" PRIx64 "_%1d", entry_hash, index); + std::string entry_contents = + file_contents + + base::StringPrintf(" %" PRIx64, implicit_cast<uint64>(entry_hash)); + ASSERT_EQ(implicit_cast<int>(entry_contents.size()), + file_util::WriteFile(cache_path.AppendASCII(file_name), + entry_contents.data(), + entry_contents.size())); + } + } + + // Upgrade. + ASSERT_TRUE(disk_cache::UpgradeIndexV5V6(cache_path)); + + // Check that the old index disappeared but the files remain unchanged. + EXPECT_FALSE(base::PathExists(index_file)); + for (uint64 entry_hash = 0; entry_hash < kEntries; ++entry_hash) { + for (int index = 0; index < 3; ++index) { + std::string file_name = + base::StringPrintf("%016" PRIx64 "_%1d", entry_hash, index); + std::string expected_contents = + file_contents + + base::StringPrintf(" %" PRIx64, implicit_cast<uint64>(entry_hash)); + std::string real_contents; + EXPECT_TRUE(base::ReadFileToString(cache_path.AppendASCII(file_name), + &real_contents)); + EXPECT_EQ(expected_contents, real_contents); + } + } +} + +} // namespace + +#endif // defined(OS_POSIX) |