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