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