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
LogMessageFailedUpgradeFromVersion(int version)27 void LogMessageFailedUpgradeFromVersion(int version) {
28 LOG(ERROR) << "Failed to upgrade Simple Cache from version: " << version;
29 }
30
WriteFakeIndexFile(const base::FilePath & file_name)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
FakeIndexData()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.
UpgradeIndexV5V6(const base::FilePath & cache_directory)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.
UpgradeSimpleCacheOnDisk(const base::FilePath & path)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