1 // Copyright 2012 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 "base/files/file_enumerator.h"
6 #include "base/files/file_util.h"
7 #include "build/chromeos_buildflags.h"
8 #include "net/disk_cache/blockfile/block_files.h"
9 #include "net/disk_cache/disk_cache.h"
10 #include "net/disk_cache/disk_cache_test_base.h"
11 #include "net/disk_cache/disk_cache_test_util.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13
14 using base::Time;
15
16 namespace {
17
18 // Returns the number of files in this folder.
NumberOfFiles(const base::FilePath & path)19 int NumberOfFiles(const base::FilePath& path) {
20 base::FileEnumerator iter(path, false, base::FileEnumerator::FILES);
21 int count = 0;
22 for (base::FilePath file = iter.Next(); !file.value().empty();
23 file = iter.Next()) {
24 count++;
25 }
26 return count;
27 }
28
29 } // namespace
30
31 namespace disk_cache {
32
33 #if BUILDFLAG(IS_CHROMEOS_ASH)
34 // Flaky on ChromeOS: https://crbug.com/1156795
35 #define MAYBE_BlockFiles_Grow DISABLED_BlockFiles_Grow
36 #else
37 #define MAYBE_BlockFiles_Grow BlockFiles_Grow
38 #endif
TEST_F(DiskCacheTest,MAYBE_BlockFiles_Grow)39 TEST_F(DiskCacheTest, MAYBE_BlockFiles_Grow) {
40 ASSERT_TRUE(CleanupCacheDir());
41 ASSERT_TRUE(base::CreateDirectory(cache_path_));
42
43 BlockFiles files(cache_path_);
44 ASSERT_TRUE(files.Init(true));
45
46 #if BUILDFLAG(IS_FUCHSIA)
47 // Too slow on Fuchsia: https://crbug.com/1354793
48 const int kMaxSize = 3500;
49 const int kNumberOfFiles = 4;
50 #else
51 const int kMaxSize = 35000;
52 const int kNumberOfFiles = 6;
53 #endif
54 Addr address[kMaxSize];
55
56 // Fill up the 32-byte block file (use three files).
57 for (auto& addr : address) {
58 EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &addr));
59 }
60 EXPECT_EQ(kNumberOfFiles, NumberOfFiles(cache_path_));
61
62 // Make sure we don't keep adding files.
63 for (int i = 0; i < kMaxSize * 4; i += 2) {
64 int target = i % kMaxSize;
65 files.DeleteBlock(address[target], false);
66 EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[target]));
67 }
68 EXPECT_EQ(kNumberOfFiles, NumberOfFiles(cache_path_));
69 }
70
71 // We should be able to delete empty block files.
TEST_F(DiskCacheTest,BlockFiles_Shrink)72 TEST_F(DiskCacheTest, BlockFiles_Shrink) {
73 ASSERT_TRUE(CleanupCacheDir());
74 ASSERT_TRUE(base::CreateDirectory(cache_path_));
75
76 BlockFiles files(cache_path_);
77 ASSERT_TRUE(files.Init(true));
78
79 const int kMaxSize = 35000;
80 Addr address[kMaxSize];
81
82 // Fill up the 32-byte block file (use three files).
83 for (auto& addr : address) {
84 EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &addr));
85 }
86
87 // Now delete all the blocks, so that we can delete the two extra files.
88 for (const auto& addr : address) {
89 files.DeleteBlock(addr, false);
90 }
91 EXPECT_EQ(4, NumberOfFiles(cache_path_));
92 }
93
94 // Handling of block files not properly closed.
TEST_F(DiskCacheTest,BlockFiles_Recover)95 TEST_F(DiskCacheTest, BlockFiles_Recover) {
96 ASSERT_TRUE(CleanupCacheDir());
97 ASSERT_TRUE(base::CreateDirectory(cache_path_));
98
99 BlockFiles files(cache_path_);
100 ASSERT_TRUE(files.Init(true));
101
102 const int kNumEntries = 2000;
103 CacheAddr entries[kNumEntries];
104
105 int seed = static_cast<int>(Time::Now().ToInternalValue());
106 srand(seed);
107 for (auto& entry : entries) {
108 Addr address(0);
109 int size = (rand() % 4) + 1;
110 EXPECT_TRUE(files.CreateBlock(RANKINGS, size, &address));
111 entry = address.value();
112 }
113
114 for (int i = 0; i < kNumEntries; i++) {
115 int source1 = rand() % kNumEntries;
116 int source2 = rand() % kNumEntries;
117 CacheAddr temp = entries[source1];
118 entries[source1] = entries[source2];
119 entries[source2] = temp;
120 }
121
122 for (int i = 0; i < kNumEntries / 2; i++) {
123 Addr address(entries[i]);
124 files.DeleteBlock(address, false);
125 }
126
127 // At this point, there are kNumEntries / 2 entries on the file, randomly
128 // distributed both on location and size.
129
130 Addr address(entries[kNumEntries / 2]);
131 MappedFile* file = files.GetFile(address);
132 ASSERT_TRUE(nullptr != file);
133
134 BlockFileHeader* header =
135 reinterpret_cast<BlockFileHeader*>(file->buffer());
136 ASSERT_TRUE(nullptr != header);
137
138 ASSERT_EQ(0, header->updating);
139
140 int max_entries = header->max_entries;
141 int empty_1 = header->empty[0];
142 int empty_2 = header->empty[1];
143 int empty_3 = header->empty[2];
144 int empty_4 = header->empty[3];
145
146 // Corrupt the file.
147 header->max_entries = header->empty[0] = 0;
148 header->empty[1] = header->empty[2] = header->empty[3] = 0;
149 header->updating = -1;
150
151 files.CloseFiles();
152
153 ASSERT_TRUE(files.Init(false));
154
155 // The file must have been fixed.
156 file = files.GetFile(address);
157 ASSERT_TRUE(nullptr != file);
158
159 header = reinterpret_cast<BlockFileHeader*>(file->buffer());
160 ASSERT_TRUE(nullptr != header);
161
162 ASSERT_EQ(0, header->updating);
163
164 EXPECT_EQ(max_entries, header->max_entries);
165 EXPECT_EQ(empty_1, header->empty[0]);
166 EXPECT_EQ(empty_2, header->empty[1]);
167 EXPECT_EQ(empty_3, header->empty[2]);
168 EXPECT_EQ(empty_4, header->empty[3]);
169 }
170
171 // Handling of truncated files.
TEST_F(DiskCacheTest,BlockFiles_ZeroSizeFile)172 TEST_F(DiskCacheTest, BlockFiles_ZeroSizeFile) {
173 ASSERT_TRUE(CleanupCacheDir());
174 ASSERT_TRUE(base::CreateDirectory(cache_path_));
175
176 BlockFiles files(cache_path_);
177 ASSERT_TRUE(files.Init(true));
178
179 base::FilePath filename = files.Name(0);
180 files.CloseFiles();
181 // Truncate one of the files.
182 {
183 auto file = base::MakeRefCounted<File>();
184 ASSERT_TRUE(file->Init(filename));
185 EXPECT_TRUE(file->SetLength(0));
186 }
187
188 // Initializing should fail, not crash.
189 ASSERT_FALSE(files.Init(false));
190 }
191
192 // Handling of truncated files (non empty).
TEST_F(DiskCacheTest,BlockFiles_TruncatedFile)193 TEST_F(DiskCacheTest, BlockFiles_TruncatedFile) {
194 ASSERT_TRUE(CleanupCacheDir());
195 ASSERT_TRUE(base::CreateDirectory(cache_path_));
196
197 BlockFiles files(cache_path_);
198 ASSERT_TRUE(files.Init(true));
199 Addr address;
200 EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address));
201
202 base::FilePath filename = files.Name(0);
203 files.CloseFiles();
204 // Truncate one of the files.
205 {
206 auto file = base::MakeRefCounted<File>();
207 ASSERT_TRUE(file->Init(filename));
208 EXPECT_TRUE(file->SetLength(15000));
209 }
210
211 // Initializing should fail, not crash.
212 ASSERT_FALSE(files.Init(false));
213 }
214
215 // Tests detection of out of sync counters.
TEST_F(DiskCacheTest,BlockFiles_Counters)216 TEST_F(DiskCacheTest, BlockFiles_Counters) {
217 ASSERT_TRUE(CleanupCacheDir());
218 ASSERT_TRUE(base::CreateDirectory(cache_path_));
219
220 BlockFiles files(cache_path_);
221 ASSERT_TRUE(files.Init(true));
222
223 // Create a block of size 2.
224 Addr address(0);
225 EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address));
226
227 MappedFile* file = files.GetFile(address);
228 ASSERT_TRUE(nullptr != file);
229
230 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
231 ASSERT_TRUE(nullptr != header);
232 ASSERT_EQ(0, header->updating);
233
234 // Alter the counters so that the free space doesn't add up.
235 header->empty[2] = 50; // 50 free blocks of size 3.
236 files.CloseFiles();
237
238 ASSERT_TRUE(files.Init(false));
239 file = files.GetFile(address);
240 ASSERT_TRUE(nullptr != file);
241 header = reinterpret_cast<BlockFileHeader*>(file->buffer());
242 ASSERT_TRUE(nullptr != header);
243
244 // The file must have been fixed.
245 ASSERT_EQ(0, header->empty[2]);
246
247 // Change the number of entries.
248 header->num_entries = 3;
249 header->updating = 1;
250 files.CloseFiles();
251
252 ASSERT_TRUE(files.Init(false));
253 file = files.GetFile(address);
254 ASSERT_TRUE(nullptr != file);
255 header = reinterpret_cast<BlockFileHeader*>(file->buffer());
256 ASSERT_TRUE(nullptr != header);
257
258 // The file must have been "fixed".
259 ASSERT_EQ(2, header->num_entries);
260
261 // Change the number of entries.
262 header->num_entries = -1;
263 header->updating = 1;
264 files.CloseFiles();
265
266 // Detect the error.
267 ASSERT_FALSE(files.Init(false));
268 }
269
270 // An invalid file can be detected after init.
TEST_F(DiskCacheTest,BlockFiles_InvalidFile)271 TEST_F(DiskCacheTest, BlockFiles_InvalidFile) {
272 ASSERT_TRUE(CleanupCacheDir());
273 ASSERT_TRUE(base::CreateDirectory(cache_path_));
274
275 BlockFiles files(cache_path_);
276 ASSERT_TRUE(files.Init(true));
277
278 // Let's access block 10 of file 5. (There is no file).
279 Addr addr(BLOCK_256, 1, 5, 10);
280 EXPECT_TRUE(nullptr == files.GetFile(addr));
281
282 // Let's create an invalid file.
283 base::FilePath filename(files.Name(5));
284 char header[kBlockHeaderSize];
285 memset(header, 'a', kBlockHeaderSize);
286 EXPECT_TRUE(base::WriteFile(filename, {header, kBlockHeaderSize}));
287
288 EXPECT_TRUE(nullptr == files.GetFile(addr));
289
290 // The file should not have been changed (it is still invalid).
291 EXPECT_TRUE(nullptr == files.GetFile(addr));
292 }
293
294 // Tests that we add and remove blocks correctly.
TEST_F(DiskCacheTest,AllocationMap)295 TEST_F(DiskCacheTest, AllocationMap) {
296 ASSERT_TRUE(CleanupCacheDir());
297 ASSERT_TRUE(base::CreateDirectory(cache_path_));
298
299 BlockFiles files(cache_path_);
300 ASSERT_TRUE(files.Init(true));
301
302 // Create a bunch of entries.
303 const int kSize = 100;
304 Addr address[kSize];
305 for (int i = 0; i < kSize; i++) {
306 SCOPED_TRACE(i);
307 int block_size = i % 4 + 1;
308 EXPECT_TRUE(files.CreateBlock(BLOCK_1K, block_size, &address[i]));
309 EXPECT_EQ(BLOCK_1K, address[i].file_type());
310 EXPECT_EQ(block_size, address[i].num_blocks());
311 int start = address[i].start_block();
312 EXPECT_EQ(start / 4, (start + block_size - 1) / 4);
313 }
314
315 for (int i = 0; i < kSize; i++) {
316 SCOPED_TRACE(i);
317 EXPECT_TRUE(files.IsValid(address[i]));
318 }
319
320 // The first part of the allocation map should be completely filled. We used
321 // 10 bits per each four entries, so 250 bits total.
322 BlockFileHeader* header =
323 reinterpret_cast<BlockFileHeader*>(files.GetFile(address[0])->buffer());
324 uint8_t* buffer = reinterpret_cast<uint8_t*>(&header->allocation_map);
325 for (int i =0; i < 29; i++) {
326 SCOPED_TRACE(i);
327 EXPECT_EQ(0xff, buffer[i]);
328 }
329
330 for (int i = 0; i < kSize; i++) {
331 SCOPED_TRACE(i);
332 files.DeleteBlock(address[i], false);
333 }
334
335 // The allocation map should be empty.
336 for (int i =0; i < 50; i++) {
337 SCOPED_TRACE(i);
338 EXPECT_EQ(0, buffer[i]);
339 }
340 }
341
342 } // namespace disk_cache
343