1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
|
// 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/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.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_t kMinVersionAbleToUpgrade = 5;
const char kFakeIndexFileName[] = "index";
const char kIndexDirName[] = "index-dir";
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::File file(file_name, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
if (!file.IsValid())
return false;
disk_cache::FakeIndexData file_contents;
file_contents.initial_magic_number =
disk_cache::simplecache_v5::kSimpleInitialMagicNumber;
file_contents.version = disk_cache::kSimpleVersion;
file_contents.zero = 0;
file_contents.zero2 = 0;
int bytes_written = file.Write(0, reinterpret_cast<char*>(&file_contents),
sizeof(file_contents));
if (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);
return base::DeleteFile(old_index_file);
}
// 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.
SimpleCacheConsistencyResult 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::File fake_index_file(fake_index,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!fake_index_file.IsValid()) {
if (fake_index_file.error_details() == base::File::FILE_ERROR_NOT_FOUND) {
if (!WriteFakeIndexFile(fake_index)) {
base::DeleteFile(fake_index);
LOG(ERROR) << "Failed to write a new fake index.";
return SimpleCacheConsistencyResult::kWriteFakeIndexFileFailed;
}
return SimpleCacheConsistencyResult::kOK;
}
return SimpleCacheConsistencyResult::kBadFakeIndexFile;
}
FakeIndexData file_header;
int bytes_read = fake_index_file.Read(0,
reinterpret_cast<char*>(&file_header),
sizeof(file_header));
if (bytes_read != sizeof(file_header)) {
LOG(ERROR) << "Disk cache backend fake index file has wrong size.";
return SimpleCacheConsistencyResult::kBadFakeIndexReadSize;
}
if (file_header.initial_magic_number !=
disk_cache::simplecache_v5::kSimpleInitialMagicNumber) {
LOG(ERROR) << "Disk cache backend fake index file has wrong magic number.";
return SimpleCacheConsistencyResult::kBadInitialMagicNumber;
}
fake_index_file.Close();
uint32_t version_from = file_header.version;
if (version_from < kMinVersionAbleToUpgrade) {
LOG(ERROR) << "Version " << version_from << " is too old.";
return SimpleCacheConsistencyResult::kVersionTooOld;
}
if (version_from > kSimpleVersion) {
LOG(ERROR) << "Version " << version_from << " is from the future.";
return SimpleCacheConsistencyResult::kVersionFromTheFuture;
}
if (file_header.zero != 0 && file_header.zero2 != 0) {
LOG(WARNING) << "Rebuilding cache due to experiment change";
return SimpleCacheConsistencyResult::kBadZeroCheck;
}
bool new_fake_index_needed = (version_from != kSimpleVersion);
// There should be one upgrade routine here for each incremental upgrade
// starting at kMinVersionAbleToUpgrade.
static_assert(kMinVersionAbleToUpgrade == 5, "upgrade routines don't match");
DCHECK_LE(5U, version_from);
if (version_from == 5) {
// Upgrade only the index for V5 -> V6 move.
if (!UpgradeIndexV5V6(path)) {
LogMessageFailedUpgradeFromVersion(file_header.version);
return SimpleCacheConsistencyResult::kUpgradeIndexV5V6Failed;
}
version_from++;
}
DCHECK_LE(6U, version_from);
if (version_from == 6) {
// No upgrade from V6 -> V7, because the entry format has not changed and
// the V7 index reader is backwards compatible.
version_from++;
}
if (version_from == 7) {
// Likewise, V7 -> V8 is handled entirely by the index reader.
version_from++;
}
if (version_from == 8) {
// Likewise, V8 -> V9 is handled entirely by the index reader.
version_from++;
}
DCHECK_EQ(kSimpleVersion, version_from);
if (!new_fake_index_needed)
return SimpleCacheConsistencyResult::kOK;
const base::FilePath temp_fake_index = path.AppendASCII("upgrade-index");
if (!WriteFakeIndexFile(temp_fake_index)) {
base::DeleteFile(temp_fake_index);
LOG(ERROR) << "Failed to write a new fake index.";
LogMessageFailedUpgradeFromVersion(file_header.version);
return SimpleCacheConsistencyResult::kWriteFakeIndexFileFailed;
}
if (!base::ReplaceFile(temp_fake_index, fake_index, nullptr)) {
LOG(ERROR) << "Failed to replace the fake index.";
LogMessageFailedUpgradeFromVersion(file_header.version);
return SimpleCacheConsistencyResult::kReplaceFileFailed;
}
return SimpleCacheConsistencyResult::kOK;
}
bool DeleteIndexFilesIfCacheIsEmpty(const base::FilePath& path) {
const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName);
const base::FilePath index_dir = path.AppendASCII(kIndexDirName);
// The newer schema versions have the real index in the index directory.
// Older versions, however, had a real index file in the same directory.
const base::FilePath legacy_index_file = path.AppendASCII(kIndexFileName);
base::FileEnumerator e(
path, /* recursive = */ false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
if (name == fake_index || name == index_dir || name == legacy_index_file)
continue;
return false;
}
bool deleted_fake_index = base::DeleteFile(fake_index);
bool deleted_index_dir = base::DeletePathRecursively(index_dir);
bool deleted_legacy_index_file = base::DeleteFile(legacy_index_file);
return deleted_fake_index || deleted_index_dir || deleted_legacy_index_file;
}
} // namespace disk_cache
|