summaryrefslogtreecommitdiff
path: root/net/disk_cache/simple/simple_version_upgrade.cc
blob: 09d80c5af8722655a907eee41928d295d4f1254d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/disk_cache/simple/simple_version_upgrade.h"

#include <cstring>

#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/pickle.h"
#include "net/disk_cache/disk_cache.h"
#include "net/disk_cache/simple/simple_backend_version.h"
#include "net/disk_cache/simple/simple_entry_format_history.h"
#include "third_party/zlib/zlib.h"

namespace {

// It is not possible to upgrade cache structures on disk that are of version
// below this, the entire cache should be dropped for them.
const uint32_t kMinVersionAbleToUpgrade = 5;

const char kFakeIndexFileName[] = "index";
const char kIndexDirName[] = "index-dir";
const char kIndexFileName[] = "the-real-index";

void LogMessageFailedUpgradeFromVersion(int version) {
  LOG(ERROR) << "Failed to upgrade Simple Cache from version: " << version;
}

bool WriteFakeIndexFile(disk_cache::BackendFileOperations* file_operations,
                        const base::FilePath& file_name) {
  base::File file = file_operations->OpenFile(
      file_name, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  if (!file.IsValid())
    return false;

  disk_cache::FakeIndexData file_contents;
  file_contents.initial_magic_number =
      disk_cache::simplecache_v5::kSimpleInitialMagicNumber;
  file_contents.version = disk_cache::kSimpleVersion;
  file_contents.zero = 0;
  file_contents.zero2 = 0;

  int bytes_written = file.Write(0, reinterpret_cast<char*>(&file_contents),
                                 sizeof(file_contents));
  if (bytes_written != sizeof(file_contents)) {
    LOG(ERROR) << "Failed to write fake index file: "
               << file_name.LossyDisplayName();
    return false;
  }
  return true;
}

}  // namespace

namespace disk_cache {

FakeIndexData::FakeIndexData() {
  // Make hashing repeatable: leave no padding bytes untouched.
  std::memset(this, 0, sizeof(*this));
}

// Migrates the cache directory from version 4 to version 5.
// Returns true iff it succeeds.
//
// The V5 and V6 caches differ in the name of the index file (it moved to a
// subdirectory) and in the file format (directory last-modified time observed
// by the index writer has gotten appended to the pickled format).
//
// To keep complexity small this specific upgrade code *deletes* the old index
// file. The directory for the new index file has to be created lazily anyway,
// so it is not done in the upgrader.
//
// Below is the detailed description of index file format differences. It is for
// reference purposes. This documentation would be useful to move closer to the
// next index upgrader when the latter gets introduced.
//
// Path:
//   V5: $cachedir/the-real-index
//   V6: $cachedir/index-dir/the-real-index
//
// Pickled file format:
//   Both formats extend Pickle::Header by 32bit value of the CRC-32 of the
//   pickled data.
//   <v5-index> ::= <v5-index-metadata> <entry-info>*
//   <v5-index-metadata> ::= UInt64(kSimpleIndexMagicNumber)
//                           UInt32(4)
//                           UInt64(<number-of-entries>)
//                           UInt64(<cache-size-in-bytes>)
//   <entry-info> ::= UInt64(<hash-of-the-key>)
//                    Int64(<entry-last-used-time>)
//                    UInt64(<entry-size-in-bytes>)
//   <v6-index> ::= <v6-index-metadata>
//                  <entry-info>*
//                  Int64(<cache-dir-mtime>)
//   <v6-index-metadata> ::= UInt64(kSimpleIndexMagicNumber)
//                           UInt32(5)
//                           UInt64(<number-of-entries>)
//                           UInt64(<cache-size-in-bytes>)
//   Where:
//     <entry-size-in-bytes> is equal the sum of all file sizes of the entry.
//     <cache-dir-mtime> is the last modification time with nanosecond precision
//       of the directory, where all files for entries are stored.
//     <hash-of-the-key> represent the first 64 bits of a SHA-1 of the key.
bool UpgradeIndexV5V6(BackendFileOperations* file_operations,
                      const base::FilePath& cache_directory) {
  const base::FilePath old_index_file =
      cache_directory.AppendASCII(kIndexFileName);
  return file_operations->DeleteFile(old_index_file);
}

// Some points about the Upgrade process are still not clear:
// 1. if the upgrade path requires dropping cache it would be faster to just
//    return an initialization error here and proceed with asynchronous cache
//    cleanup in CacheCreator. Should this hack be considered valid? Some smart
//    tests may fail.
// 2. Because Android process management allows for killing a process at any
//    time, the upgrade process may need to deal with a partially completed
//    previous upgrade. For example, while upgrading A -> A + 2 we are the
//    process gets killed and some parts are remaining at version A + 1. There
//    are currently no generic mechanisms to resolve this situation, co the
//    upgrade codes need to ensure they can continue after being stopped in the
//    middle. It also means that the "fake index" must be flushed in between the
//    upgrade steps. Atomicity of this is an interesting research topic. The
//    intermediate fake index flushing must be added as soon as we add more
//    upgrade steps.
SimpleCacheConsistencyResult UpgradeSimpleCacheOnDisk(
    BackendFileOperations* file_operations,
    const base::FilePath& path) {
  // There is a convention among disk cache backends: looking at the magic in
  // the file "index" it should be sufficient to determine if the cache belongs
  // to the currently running backend. The Simple Backend stores its index in
  // the file "the-real-index" (see simple_index_file.cc) and the file "index"
  // only signifies presence of the implementation's magic and version. There
  // are two reasons for that:
  // 1. Absence of the index is itself not a fatal error in the Simple Backend
  // 2. The Simple Backend has pickled file format for the index making it hacky
  //    to have the magic in the right place.
  const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName);
  base::File fake_index_file = file_operations->OpenFile(
      fake_index, base::File::FLAG_OPEN | base::File::FLAG_READ);

  if (!fake_index_file.IsValid()) {
    if (fake_index_file.error_details() == base::File::FILE_ERROR_NOT_FOUND) {
      if (!WriteFakeIndexFile(file_operations, fake_index)) {
        file_operations->DeleteFile(fake_index);
        LOG(ERROR) << "Failed to write a new fake index.";
        return SimpleCacheConsistencyResult::kWriteFakeIndexFileFailed;
      }
      return SimpleCacheConsistencyResult::kOK;
    }
    return SimpleCacheConsistencyResult::kBadFakeIndexFile;
  }

  FakeIndexData file_header;
  int bytes_read = fake_index_file.Read(0,
                                        reinterpret_cast<char*>(&file_header),
                                        sizeof(file_header));
  if (bytes_read != sizeof(file_header)) {
    LOG(ERROR) << "Disk cache backend fake index file has wrong size.";
    return SimpleCacheConsistencyResult::kBadFakeIndexReadSize;
  }
  if (file_header.initial_magic_number !=
      disk_cache::simplecache_v5::kSimpleInitialMagicNumber) {
    LOG(ERROR) << "Disk cache backend fake index file has wrong magic number.";
    return SimpleCacheConsistencyResult::kBadInitialMagicNumber;
  }
  fake_index_file.Close();

  uint32_t version_from = file_header.version;
  if (version_from < kMinVersionAbleToUpgrade) {
    LOG(ERROR) << "Version " << version_from << " is too old.";
    return SimpleCacheConsistencyResult::kVersionTooOld;
  }

  if (version_from > kSimpleVersion) {
    LOG(ERROR) << "Version " << version_from << " is from the future.";
    return SimpleCacheConsistencyResult::kVersionFromTheFuture;
  }

  if (file_header.zero != 0 && file_header.zero2 != 0) {
    LOG(WARNING) << "Rebuilding cache due to experiment change";
    return SimpleCacheConsistencyResult::kBadZeroCheck;
  }

  bool new_fake_index_needed = (version_from != kSimpleVersion);

  // There should be one upgrade routine here for each incremental upgrade
  // starting at kMinVersionAbleToUpgrade.
  static_assert(kMinVersionAbleToUpgrade == 5, "upgrade routines don't match");
  DCHECK_LE(5U, version_from);
  if (version_from == 5) {
    // Upgrade only the index for V5 -> V6 move.
    if (!UpgradeIndexV5V6(file_operations, path)) {
      LogMessageFailedUpgradeFromVersion(file_header.version);
      return SimpleCacheConsistencyResult::kUpgradeIndexV5V6Failed;
    }
    version_from++;
  }
  DCHECK_LE(6U, version_from);
  if (version_from == 6) {
    // No upgrade from V6 -> V7, because the entry format has not changed and
    // the V7 index reader is backwards compatible.
    version_from++;
  }

  if (version_from == 7) {
    // Likewise, V7 -> V8 is handled entirely by the index reader.
    version_from++;
  }

  if (version_from == 8) {
    // Likewise, V8 -> V9 is handled entirely by the index reader.
    version_from++;
  }

  DCHECK_EQ(kSimpleVersion, version_from);

  if (!new_fake_index_needed)
    return SimpleCacheConsistencyResult::kOK;

  const base::FilePath temp_fake_index = path.AppendASCII("upgrade-index");
  if (!WriteFakeIndexFile(file_operations, temp_fake_index)) {
    file_operations->DeleteFile(temp_fake_index);
    LOG(ERROR) << "Failed to write a new fake index.";
    LogMessageFailedUpgradeFromVersion(file_header.version);
    return SimpleCacheConsistencyResult::kWriteFakeIndexFileFailed;
  }
  if (!file_operations->ReplaceFile(temp_fake_index, fake_index, nullptr)) {
    LOG(ERROR) << "Failed to replace the fake index.";
    LogMessageFailedUpgradeFromVersion(file_header.version);
    return SimpleCacheConsistencyResult::kReplaceFileFailed;
  }
  return SimpleCacheConsistencyResult::kOK;
}

bool DeleteIndexFilesIfCacheIsEmpty(const base::FilePath& path) {
  const base::FilePath fake_index = path.AppendASCII(kFakeIndexFileName);
  const base::FilePath index_dir = path.AppendASCII(kIndexDirName);
  // The newer schema versions have the real index in the index directory.
  // Older versions, however, had a real index file in the same directory.
  const base::FilePath legacy_index_file = path.AppendASCII(kIndexFileName);
  base::FileEnumerator e(
      path, /* recursive = */ false,
      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
  for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
    if (name == fake_index || name == index_dir || name == legacy_index_file)
      continue;
    return false;
  }
  bool deleted_fake_index = base::DeleteFile(fake_index);
  bool deleted_index_dir = base::DeletePathRecursively(index_dir);
  bool deleted_legacy_index_file = base::DeleteFile(legacy_index_file);
  return deleted_fake_index || deleted_index_dir || deleted_legacy_index_file;
}

}  // namespace disk_cache