• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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