1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "net/disk_cache/simple/simple_version_upgrade.h" 6 7 #include <cstring> 8 9 #include "base/file_util.h" 10 #include "base/files/file_path.h" 11 #include "base/files/memory_mapped_file.h" 12 #include "base/logging.h" 13 #include "base/pickle.h" 14 #include "net/disk_cache/simple/simple_backend_version.h" 15 #include "net/disk_cache/simple/simple_entry_format_history.h" 16 #include "third_party/zlib/zlib.h" 17 18 namespace { 19 20 // It is not possible to upgrade cache structures on disk that are of version 21 // below this, the entire cache should be dropped for them. 22 const uint32 kMinVersionAbleToUpgrade = 5; 23 24 const char kFakeIndexFileName[] = "index"; 25 const char kIndexFileName[] = "the-real-index"; 26 27 void LogMessageFailedUpgradeFromVersion(int version) { 28 LOG(ERROR) << "Failed to upgrade Simple Cache from version: " << version; 29 } 30 31 bool WriteFakeIndexFile(const base::FilePath& file_name) { 32 base::PlatformFileError error; 33 base::PlatformFile file = base::CreatePlatformFile( 34 file_name, 35 base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, 36 NULL, 37 &error); 38 disk_cache::FakeIndexData file_contents; 39 file_contents.initial_magic_number = 40 disk_cache::simplecache_v5::kSimpleInitialMagicNumber; 41 file_contents.version = disk_cache::kSimpleVersion; 42 int bytes_written = base::WritePlatformFile( 43 file, 0, reinterpret_cast<char*>(&file_contents), sizeof(file_contents)); 44 if (!base::ClosePlatformFile(file) || 45 bytes_written != sizeof(file_contents)) { 46 LOG(ERROR) << "Failed to write fake index file: " 47 << file_name.LossyDisplayName(); 48 return false; 49 } 50 return true; 51 } 52 53 } // namespace 54 55 namespace disk_cache { 56 57 FakeIndexData::FakeIndexData() { 58 // Make hashing repeatable: leave no padding bytes untouched. 59 std::memset(this, 0, sizeof(*this)); 60 } 61 62 // Migrates the cache directory from version 4 to version 5. 63 // Returns true iff it succeeds. 64 // 65 // The V5 and V6 caches differ in the name of the index file (it moved to a 66 // subdirectory) and in the file format (directory last-modified time observed 67 // by the index writer has gotten appended to the pickled format). 68 // 69 // To keep complexity small this specific upgrade code *deletes* the old index 70 // file. The directory for the new index file has to be created lazily anyway, 71 // so it is not done in the upgrader. 72 // 73 // Below is the detailed description of index file format differences. It is for 74 // reference purposes. This documentation would be useful to move closer to the 75 // next index upgrader when the latter gets introduced. 76 // 77 // Path: 78 // V5: $cachedir/the-real-index 79 // V6: $cachedir/index-dir/the-real-index 80 // 81 // Pickled file format: 82 // Both formats extend Pickle::Header by 32bit value of the CRC-32 of the 83 // pickled data. 84 // <v5-index> ::= <v5-index-metadata> <entry-info>* 85 // <v5-index-metadata> ::= UInt64(kSimpleIndexMagicNumber) 86 // UInt32(4) 87 // UInt64(<number-of-entries>) 88 // UInt64(<cache-size-in-bytes>) 89 // <entry-info> ::= UInt64(<hash-of-the-key>) 90 // Int64(<entry-last-used-time>) 91 // UInt64(<entry-size-in-bytes>) 92 // <v6-index> ::= <v6-index-metadata> 93 // <entry-info>* 94 // Int64(<cache-dir-mtime>) 95 // <v6-index-metadata> ::= UInt64(kSimpleIndexMagicNumber) 96 // UInt32(5) 97 // UInt64(<number-of-entries>) 98 // UInt64(<cache-size-in-bytes>) 99 // Where: 100 // <entry-size-in-bytes> is equal the sum of all file sizes of the entry. 101 // <cache-dir-mtime> is the last modification time with nanosecond precision 102 // of the directory, where all files for entries are stored. 103 // <hash-of-the-key> represent the first 64 bits of a SHA-1 of the key. 104 bool UpgradeIndexV5V6(const base::FilePath& cache_directory) { 105 const base::FilePath old_index_file = 106 cache_directory.AppendASCII(kIndexFileName); 107 if (!base::DeleteFile(old_index_file, /* recursive = */ false)) 108 return false; 109 return true; 110 } 111 112 // Some points about the Upgrade process are still not clear: 113 // 1. if the upgrade path requires dropping cache it would be faster to just 114 // return an initialization error here and proceed with asynchronous cache 115 // cleanup in CacheCreator. Should this hack be considered valid? Some smart 116 // tests may fail. 117 // 2. Because Android process management allows for killing a process at any 118 // time, the upgrade process may need to deal with a partially completed 119 // previous upgrade. For example, while upgrading A -> A + 2 we are the 120 // process gets killed and some parts are remaining at version A + 1. There 121 // are currently no generic mechanisms to resolve this situation, co the 122 // upgrade codes need to ensure they can continue after being stopped in the 123 // middle. It also means that the "fake index" must be flushed in between the 124 // upgrade steps. Atomicity of this is an interesting research topic. The 125 // intermediate fake index flushing must be added as soon as we add more 126 // upgrade steps. 127 bool UpgradeSimpleCacheOnDisk(const base::FilePath& path) { 128 // There is a convention among disk cache backends: looking at the magic in 129 // the file "index" it should be sufficient to determine if the cache belongs 130 // to the currently running backend. The Simple Backend stores its index in 131 // the file "the-real-index" (see simple_index_file.cc) and the file "index" 132 // only signifies presence of the implementation's magic and version. There 133 // are two reasons for that: 134 // 1. Absence of the index is itself not a fatal error in the Simple Backend 135 // 2. The Simple Backend has pickled file format for the index making it hacky 136 // to have the magic in the right place. 137 const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName); 138 base::PlatformFileError error; 139 base::PlatformFile fake_index_file = base::CreatePlatformFile( 140 fake_index, 141 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, 142 NULL, 143 &error); 144 if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { 145 return WriteFakeIndexFile(fake_index); 146 } else if (error != base::PLATFORM_FILE_OK) { 147 return false; 148 } 149 FakeIndexData file_header; 150 int bytes_read = base::ReadPlatformFile(fake_index_file, 151 0, 152 reinterpret_cast<char*>(&file_header), 153 sizeof(file_header)); 154 if (!base::ClosePlatformFile(fake_index_file) || 155 bytes_read != sizeof(file_header) || 156 file_header.initial_magic_number != 157 disk_cache::simplecache_v5::kSimpleInitialMagicNumber) { 158 LOG(ERROR) << "File structure does not match the disk cache backend."; 159 return false; 160 } 161 162 uint32 version_from = file_header.version; 163 if (version_from < kMinVersionAbleToUpgrade || 164 version_from > kSimpleVersion) { 165 LOG(ERROR) << "Inconsistent cache version."; 166 return false; 167 } 168 bool upgrade_needed = (version_from != kSimpleVersion); 169 if (version_from == kMinVersionAbleToUpgrade) { 170 // Upgrade only the index for V4 -> V5 move. 171 if (!UpgradeIndexV5V6(path)) { 172 LogMessageFailedUpgradeFromVersion(file_header.version); 173 return false; 174 } 175 version_from++; 176 } 177 if (version_from == kSimpleVersion) { 178 if (!upgrade_needed) { 179 return true; 180 } else { 181 const base::FilePath temp_fake_index = path.AppendASCII("upgrade-index"); 182 if (!WriteFakeIndexFile(temp_fake_index)) { 183 base::DeleteFile(temp_fake_index, /* recursive = */ false); 184 LOG(ERROR) << "Failed to write a new fake index."; 185 LogMessageFailedUpgradeFromVersion(file_header.version); 186 return false; 187 } 188 if (!base::ReplaceFile(temp_fake_index, fake_index, NULL)) { 189 LOG(ERROR) << "Failed to replace the fake index."; 190 LogMessageFailedUpgradeFromVersion(file_header.version); 191 return false; 192 } 193 return true; 194 } 195 } 196 // Verify during the test stage that the upgraders are implemented for all 197 // versions. The release build would cause backend initialization failure 198 // which would then later lead to removing all files known to the backend. 199 DCHECK_EQ(kSimpleVersion, version_from); 200 return false; 201 } 202 203 } // namespace disk_cache 204