summaryrefslogtreecommitdiffstats
path: root/net/disk_cache
diff options
context:
space:
mode:
authorpasko@chromium.org <pasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-19 15:16:30 +0000
committerpasko@chromium.org <pasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-19 15:16:30 +0000
commitfc9cffc29a4af5347f616399ff19244539c729fe (patch)
tree4322ca3cfcd5826d3af066ee18bc844c3a2920fd /net/disk_cache
parent9c8b1f97fa93386c3bd4bd86d7ac483dfdb14011 (diff)
downloadchromium_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.cc4
-rw-r--r--net/disk_cache/simple/simple_backend_impl.cc56
-rw-r--r--net/disk_cache/simple/simple_backend_version.h27
-rw-r--r--net/disk_cache/simple/simple_entry_format.h2
-rw-r--r--net/disk_cache/simple/simple_entry_format_history.h62
-rw-r--r--net/disk_cache/simple/simple_index_file.cc262
-rw-r--r--net/disk_cache/simple/simple_index_file.h36
-rw-r--r--net/disk_cache/simple/simple_index_file_unittest.cc53
-rw-r--r--net/disk_cache/simple/simple_synchronous_entry.cc5
-rw-r--r--net/disk_cache/simple/simple_version_upgrade.cc203
-rw-r--r--net/disk_cache/simple/simple_version_upgrade.h50
-rw-r--r--net/disk_cache/simple/simple_version_upgrade_unittest.cc146
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)