1 // Copyright 2013 The Chromium Authors
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/containers/span.h"
10 #include "base/files/file.h"
11 #include "base/files/file_enumerator.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/files/memory_mapped_file.h"
15 #include "base/logging.h"
16 #include "base/pickle.h"
17 #include "net/disk_cache/disk_cache.h"
18 #include "net/disk_cache/simple/simple_backend_version.h"
19 #include "net/disk_cache/simple/simple_entry_format_history.h"
20 #include "third_party/zlib/zlib.h"
21
22 namespace {
23
24 // It is not possible to upgrade cache structures on disk that are of version
25 // below this, the entire cache should be dropped for them.
26 const uint32_t kMinVersionAbleToUpgrade = 5;
27
28 const char kFakeIndexFileName[] = "index";
29 const char kIndexDirName[] = "index-dir";
30 const char kIndexFileName[] = "the-real-index";
31
LogMessageFailedUpgradeFromVersion(int version)32 void LogMessageFailedUpgradeFromVersion(int version) {
33 LOG(ERROR) << "Failed to upgrade Simple Cache from version: " << version;
34 }
35
WriteFakeIndexFile(disk_cache::BackendFileOperations * file_operations,const base::FilePath & file_name)36 bool WriteFakeIndexFile(disk_cache::BackendFileOperations* file_operations,
37 const base::FilePath& file_name) {
38 base::File file = file_operations->OpenFile(
39 file_name, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
40 if (!file.IsValid())
41 return false;
42
43 disk_cache::FakeIndexData file_contents;
44 file_contents.initial_magic_number =
45 disk_cache::simplecache_v5::kSimpleInitialMagicNumber;
46 file_contents.version = disk_cache::kSimpleVersion;
47 file_contents.zero = 0;
48 file_contents.zero2 = 0;
49
50 if (!file.WriteAndCheck(0, base::byte_span_from_ref(file_contents))) {
51 LOG(ERROR) << "Failed to write fake index file: "
52 << file_name.LossyDisplayName();
53 return false;
54 }
55 return true;
56 }
57
58 } // namespace
59
60 namespace disk_cache {
61
FakeIndexData()62 FakeIndexData::FakeIndexData() {
63 // Make hashing repeatable: leave no padding bytes untouched.
64 std::memset(this, 0, sizeof(*this));
65 }
66
67 // Migrates the cache directory from version 4 to version 5.
68 // Returns true iff it succeeds.
69 //
70 // The V5 and V6 caches differ in the name of the index file (it moved to a
71 // subdirectory) and in the file format (directory last-modified time observed
72 // by the index writer has gotten appended to the pickled format).
73 //
74 // To keep complexity small this specific upgrade code *deletes* the old index
75 // file. The directory for the new index file has to be created lazily anyway,
76 // so it is not done in the upgrader.
77 //
78 // Below is the detailed description of index file format differences. It is for
79 // reference purposes. This documentation would be useful to move closer to the
80 // next index upgrader when the latter gets introduced.
81 //
82 // Path:
83 // V5: $cachedir/the-real-index
84 // V6: $cachedir/index-dir/the-real-index
85 //
86 // Pickled file format:
87 // Both formats extend Pickle::Header by 32bit value of the CRC-32 of the
88 // pickled data.
89 // <v5-index> ::= <v5-index-metadata> <entry-info>*
90 // <v5-index-metadata> ::= UInt64(kSimpleIndexMagicNumber)
91 // UInt32(4)
92 // UInt64(<number-of-entries>)
93 // UInt64(<cache-size-in-bytes>)
94 // <entry-info> ::= UInt64(<hash-of-the-key>)
95 // Int64(<entry-last-used-time>)
96 // UInt64(<entry-size-in-bytes>)
97 // <v6-index> ::= <v6-index-metadata>
98 // <entry-info>*
99 // Int64(<cache-dir-mtime>)
100 // <v6-index-metadata> ::= UInt64(kSimpleIndexMagicNumber)
101 // UInt32(5)
102 // UInt64(<number-of-entries>)
103 // UInt64(<cache-size-in-bytes>)
104 // Where:
105 // <entry-size-in-bytes> is equal the sum of all file sizes of the entry.
106 // <cache-dir-mtime> is the last modification time with nanosecond precision
107 // of the directory, where all files for entries are stored.
108 // <hash-of-the-key> represent the first 64 bits of a SHA-1 of the key.
UpgradeIndexV5V6(BackendFileOperations * file_operations,const base::FilePath & cache_directory)109 bool UpgradeIndexV5V6(BackendFileOperations* file_operations,
110 const base::FilePath& cache_directory) {
111 const base::FilePath old_index_file =
112 cache_directory.AppendASCII(kIndexFileName);
113 return file_operations->DeleteFile(old_index_file);
114 }
115
116 // Some points about the Upgrade process are still not clear:
117 // 1. if the upgrade path requires dropping cache it would be faster to just
118 // return an initialization error here and proceed with asynchronous cache
119 // cleanup in CacheCreator. Should this hack be considered valid? Some smart
120 // tests may fail.
121 // 2. Because Android process management allows for killing a process at any
122 // time, the upgrade process may need to deal with a partially completed
123 // previous upgrade. For example, while upgrading A -> A + 2 we are the
124 // process gets killed and some parts are remaining at version A + 1. There
125 // are currently no generic mechanisms to resolve this situation, co the
126 // upgrade codes need to ensure they can continue after being stopped in the
127 // middle. It also means that the "fake index" must be flushed in between the
128 // upgrade steps. Atomicity of this is an interesting research topic. The
129 // intermediate fake index flushing must be added as soon as we add more
130 // upgrade steps.
UpgradeSimpleCacheOnDisk(BackendFileOperations * file_operations,const base::FilePath & path)131 SimpleCacheConsistencyResult UpgradeSimpleCacheOnDisk(
132 BackendFileOperations* file_operations,
133 const base::FilePath& path) {
134 // There is a convention among disk cache backends: looking at the magic in
135 // the file "index" it should be sufficient to determine if the cache belongs
136 // to the currently running backend. The Simple Backend stores its index in
137 // the file "the-real-index" (see simple_index_file.cc) and the file "index"
138 // only signifies presence of the implementation's magic and version. There
139 // are two reasons for that:
140 // 1. Absence of the index is itself not a fatal error in the Simple Backend
141 // 2. The Simple Backend has pickled file format for the index making it hacky
142 // to have the magic in the right place.
143 const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName);
144 base::File fake_index_file = file_operations->OpenFile(
145 fake_index, base::File::FLAG_OPEN | base::File::FLAG_READ);
146
147 if (!fake_index_file.IsValid()) {
148 if (fake_index_file.error_details() == base::File::FILE_ERROR_NOT_FOUND) {
149 if (!WriteFakeIndexFile(file_operations, fake_index)) {
150 file_operations->DeleteFile(fake_index);
151 LOG(ERROR) << "Failed to write a new fake index.";
152 return SimpleCacheConsistencyResult::kWriteFakeIndexFileFailed;
153 }
154 return SimpleCacheConsistencyResult::kOK;
155 }
156 return SimpleCacheConsistencyResult::kBadFakeIndexFile;
157 }
158
159 FakeIndexData file_header;
160 if (!fake_index_file.ReadAndCheck(0, base::byte_span_from_ref(file_header))) {
161 LOG(ERROR) << "Disk cache backend fake index file has wrong size.";
162 return SimpleCacheConsistencyResult::kBadFakeIndexReadSize;
163 }
164 if (file_header.initial_magic_number !=
165 disk_cache::simplecache_v5::kSimpleInitialMagicNumber) {
166 LOG(ERROR) << "Disk cache backend fake index file has wrong magic number.";
167 return SimpleCacheConsistencyResult::kBadInitialMagicNumber;
168 }
169 fake_index_file.Close();
170
171 uint32_t version_from = file_header.version;
172 if (version_from < kMinVersionAbleToUpgrade) {
173 LOG(ERROR) << "Version " << version_from << " is too old.";
174 return SimpleCacheConsistencyResult::kVersionTooOld;
175 }
176
177 if (version_from > kSimpleVersion) {
178 LOG(ERROR) << "Version " << version_from << " is from the future.";
179 return SimpleCacheConsistencyResult::kVersionFromTheFuture;
180 }
181
182 if (file_header.zero != 0 && file_header.zero2 != 0) {
183 LOG(WARNING) << "Rebuilding cache due to experiment change";
184 return SimpleCacheConsistencyResult::kBadZeroCheck;
185 }
186
187 bool new_fake_index_needed = (version_from != kSimpleVersion);
188
189 // There should be one upgrade routine here for each incremental upgrade
190 // starting at kMinVersionAbleToUpgrade.
191 static_assert(kMinVersionAbleToUpgrade == 5, "upgrade routines don't match");
192 DCHECK_LE(5U, version_from);
193 if (version_from == 5) {
194 // Upgrade only the index for V5 -> V6 move.
195 if (!UpgradeIndexV5V6(file_operations, path)) {
196 LogMessageFailedUpgradeFromVersion(file_header.version);
197 return SimpleCacheConsistencyResult::kUpgradeIndexV5V6Failed;
198 }
199 version_from++;
200 }
201 DCHECK_LE(6U, version_from);
202 if (version_from == 6) {
203 // No upgrade from V6 -> V7, because the entry format has not changed and
204 // the V7 index reader is backwards compatible.
205 version_from++;
206 }
207
208 if (version_from == 7) {
209 // Likewise, V7 -> V8 is handled entirely by the index reader.
210 version_from++;
211 }
212
213 if (version_from == 8) {
214 // Likewise, V8 -> V9 is handled entirely by the index reader.
215 version_from++;
216 }
217
218 DCHECK_EQ(kSimpleVersion, version_from);
219
220 if (!new_fake_index_needed)
221 return SimpleCacheConsistencyResult::kOK;
222
223 const base::FilePath temp_fake_index = path.AppendASCII("upgrade-index");
224 if (!WriteFakeIndexFile(file_operations, temp_fake_index)) {
225 file_operations->DeleteFile(temp_fake_index);
226 LOG(ERROR) << "Failed to write a new fake index.";
227 LogMessageFailedUpgradeFromVersion(file_header.version);
228 return SimpleCacheConsistencyResult::kWriteFakeIndexFileFailed;
229 }
230 if (!file_operations->ReplaceFile(temp_fake_index, fake_index, nullptr)) {
231 LOG(ERROR) << "Failed to replace the fake index.";
232 LogMessageFailedUpgradeFromVersion(file_header.version);
233 return SimpleCacheConsistencyResult::kReplaceFileFailed;
234 }
235 return SimpleCacheConsistencyResult::kOK;
236 }
237
DeleteIndexFilesIfCacheIsEmpty(const base::FilePath & path)238 bool DeleteIndexFilesIfCacheIsEmpty(const base::FilePath& path) {
239 const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName);
240 const base::FilePath index_dir = path.AppendASCII(kIndexDirName);
241 // The newer schema versions have the real index in the index directory.
242 // Older versions, however, had a real index file in the same directory.
243 const base::FilePath legacy_index_file = path.AppendASCII(kIndexFileName);
244 base::FileEnumerator e(
245 path, /* recursive = */ false,
246 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
247 for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
248 if (name == fake_index || name == index_dir || name == legacy_index_file)
249 continue;
250 return false;
251 }
252 bool deleted_fake_index = base::DeleteFile(fake_index);
253 bool deleted_index_dir = base::DeletePathRecursively(index_dir);
254 bool deleted_legacy_index_file = base::DeleteFile(legacy_index_file);
255 return deleted_fake_index || deleted_index_dir || deleted_legacy_index_file;
256 }
257
258 } // namespace disk_cache
259