1 // Copyright 2016 The Fuchsia 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 <fcntl.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
11 
12 #include <minfs/format.h>
13 #include <zircon/syscalls.h>
14 #include <fbl/alloc_checker.h>
15 #include <fbl/unique_fd.h>
16 #include <fbl/unique_ptr.h>
17 #include <unittest/unittest.h>
18 
19 #include "filesystems.h"
20 
21 template <size_t WriteOffset, size_t ReadOffset, size_t WriteSize>
test_sparse(void)22 bool test_sparse(void) {
23     BEGIN_TEST;
24 
25     int fd = open("::my_file", O_RDWR | O_CREAT, 0644);
26     ASSERT_GT(fd, 0);
27 
28     // Create a random write buffer of data
29     fbl::AllocChecker ac;
30     fbl::unique_ptr<uint8_t[]> wbuf(new (&ac) uint8_t[WriteSize]);
31     ASSERT_EQ(ac.check(), true);
32     unsigned int seed = static_cast<unsigned int>(zx_ticks_get());
33     unittest_printf("Sparse test using seed: %u\n", seed);
34     for (size_t i = 0; i < WriteSize; i++) {
35         wbuf[i] = (uint8_t) rand_r(&seed);
36     }
37 
38     // Dump write buffer to file
39     ASSERT_EQ(pwrite(fd, &wbuf[0], WriteSize, WriteOffset), WriteSize);
40 
41     // Reopen file
42     ASSERT_EQ(close(fd), 0);
43     fd = open("::my_file", O_RDWR, 0644);
44     ASSERT_GT(fd, 0);
45 
46     // Access read buffer from file
47     constexpr size_t kFileSize = WriteOffset + WriteSize;
48     constexpr size_t kBytesToRead = (kFileSize - ReadOffset) > WriteSize ?
49                                      WriteSize : (kFileSize - ReadOffset);
50     static_assert(kBytesToRead > 0, "We want to test writing AND reading");
51     fbl::unique_ptr<uint8_t[]> rbuf(new (&ac) uint8_t[kBytesToRead]);
52     ASSERT_EQ(ac.check(), true);
53     ASSERT_EQ(pread(fd, &rbuf[0], kBytesToRead, ReadOffset), kBytesToRead);
54 
55     constexpr size_t kSparseLength = (ReadOffset < WriteOffset) ?
56                                       WriteOffset - ReadOffset : 0;
57 
58     if (kSparseLength > 0) {
59         for (size_t i = 0; i < kSparseLength; i++) {
60             ASSERT_EQ(rbuf[i], 0, "This portion of file should be sparse; but isn't");
61         }
62     }
63 
64     constexpr size_t kWbufOffset = (ReadOffset < WriteOffset) ?
65                                     0 : ReadOffset - WriteOffset;
66     constexpr size_t kValidLength = kBytesToRead - kSparseLength;
67 
68     if (kValidLength > 0) {
69         for (size_t i = 0; i < kValidLength; i++) {
70             ASSERT_EQ(rbuf[kSparseLength + i], wbuf[kWbufOffset + i]);
71         }
72     }
73 
74     // Clean up
75     ASSERT_EQ(close(fd), 0);
76     ASSERT_EQ(unlink("::my_file"), 0);
77     END_TEST;
78 }
79 
TestSparseAllocation()80 bool TestSparseAllocation() {
81     BEGIN_TEST;
82     fbl::unique_fd sparse_fd(open("::sparse_file", O_RDWR | O_CREAT, 0644));
83     ASSERT_TRUE(sparse_fd);
84 
85     char data[minfs::kMinfsBlockSize];
86     memset(data, 0xaa, sizeof(data));
87 
88     // Create a file that owns blocks in |kBitmapBlocks| different bitmap blocks.
89     constexpr uint32_t kBitmapBlocks = 4;
90     for (uint32_t j = 0; j < kBitmapBlocks; j++) {
91         // Write one block to the "sparse" file.
92         ASSERT_EQ(sizeof(data), write(sparse_fd.get(), data, sizeof(data)));
93 
94         char filename[128];
95         snprintf(filename, sizeof(filename), "::file_%u", j);
96         fbl::unique_fd fd(open(filename, O_RDWR | O_CREAT, 0644));
97         ASSERT_TRUE(fd);
98 
99         // Write enough blocks to another file to use up the remainder of a bitmap block.
100         for (size_t i = 0; i < minfs::kMinfsBlockBits; i++) {
101             ASSERT_EQ(sizeof(data), write(fd.get(), data, sizeof(data)));
102         }
103     }
104 
105     ASSERT_EQ(close(sparse_fd.release()), 0);
106     ASSERT_EQ(unlink("::sparse_file"), 0);
107 
108     END_TEST;
109 }
110 
111 constexpr size_t kBlockSize = 8192;
112 constexpr size_t kDirectBlocks = 16;
113 
114 const test_disk_t disk = {
115     .block_count = 1LLU << 24,
116     .block_size = 1LLU << 9,
117     .slice_size = 1LLU << 23,
118 };
119 
120 RUN_FOR_ALL_FILESYSTEMS_SIZE(sparse_tests, disk,
121     RUN_TEST_MEDIUM((test_sparse<0, 0, kBlockSize>))
122     RUN_TEST_MEDIUM((test_sparse<kBlockSize / 2, 0, kBlockSize>))
123     RUN_TEST_MEDIUM((test_sparse<kBlockSize / 2, kBlockSize, kBlockSize>))
124     RUN_TEST_MEDIUM((test_sparse<kBlockSize, 0, kBlockSize>))
125     RUN_TEST_MEDIUM((test_sparse<kBlockSize, kBlockSize / 2, kBlockSize>))
126 
127     RUN_TEST_MEDIUM((test_sparse<kBlockSize * kDirectBlocks,
128                                  kBlockSize * kDirectBlocks - kBlockSize,
129                                  kBlockSize * 2>))
130     RUN_TEST_MEDIUM((test_sparse<kBlockSize * kDirectBlocks,
131                                  kBlockSize * kDirectBlocks - kBlockSize,
132                                  kBlockSize * 32>))
133     RUN_TEST_MEDIUM((test_sparse<kBlockSize * kDirectBlocks + kBlockSize,
134                                  kBlockSize * kDirectBlocks - kBlockSize,
135                                  kBlockSize * 32>))
136     RUN_TEST_MEDIUM((test_sparse<kBlockSize * kDirectBlocks + kBlockSize,
137                                  kBlockSize * kDirectBlocks + 2 * kBlockSize,
138                                  kBlockSize * 32>))
139     RUN_TEST_LARGE(TestSparseAllocation)
140 )
141