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 <assert.h>
6 #include <fcntl.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13
14 #include <zircon/syscalls.h>
15 #include <fbl/algorithm.h>
16 #include <fbl/alloc_checker.h>
17 #include <fbl/unique_ptr.h>
18
19 #include "filesystems.h"
20 #include "misc.h"
21
22 namespace {
23
check_file_contains(const char * filename,const void * data,ssize_t len)24 bool check_file_contains(const char* filename, const void* data, ssize_t len) {
25 char buf[4096];
26 struct stat st;
27
28 ASSERT_EQ(stat(filename, &st), 0);
29 ASSERT_EQ(st.st_size, len);
30 int fd = open(filename, O_RDWR, 0644);
31 ASSERT_GT(fd, 0);
32 ASSERT_STREAM_ALL(read, fd, buf, len);
33 ASSERT_EQ(memcmp(buf, data, len), 0);
34 ASSERT_EQ(close(fd), 0);
35
36 return true;
37 }
38
check_file_empty(const char * filename)39 bool check_file_empty(const char* filename) {
40 struct stat st;
41 ASSERT_EQ(stat(filename, &st), 0);
42 ASSERT_EQ(st.st_size, 0);
43
44 return true;
45 }
46
47 // Test that the really simple cases of truncate are operational
TestTruncateSmall(void)48 bool TestTruncateSmall(void) {
49 BEGIN_TEST;
50
51 const char* str = "Hello, World!\n";
52 const char* filename = "::alpha";
53
54 // Try writing a string to a file
55 int fd = open(filename, O_RDWR | O_CREAT, 0644);
56 ASSERT_GT(fd, 0);
57 ASSERT_STREAM_ALL(write, fd, str, strlen(str));
58 ASSERT_TRUE(check_file_contains(filename, str, strlen(str)));
59
60 // Check that opening a file with O_TRUNC makes it empty
61 int fd2 = open(filename, O_RDWR | O_TRUNC, 0644);
62 ASSERT_GT(fd2, 0);
63 ASSERT_TRUE(check_file_empty(filename));
64
65 // Check that we can still write to a file that has been truncated
66 ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
67 ASSERT_STREAM_ALL(write, fd, str, strlen(str));
68 ASSERT_TRUE(check_file_contains(filename, str, strlen(str)));
69
70 // Check that we can truncate the file using the "truncate" function
71 ASSERT_EQ(truncate(filename, 5), 0);
72 ASSERT_TRUE(check_file_contains(filename, str, 5));
73 ASSERT_EQ(truncate(filename, 0), 0);
74 ASSERT_TRUE(check_file_empty(filename));
75
76 // Check that truncating an already empty file does not cause problems
77 ASSERT_EQ(truncate(filename, 0), 0);
78 ASSERT_TRUE(check_file_empty(filename));
79
80 // Check that we can use truncate to extend a file
81 char empty[5] = {0, 0, 0, 0, 0};
82 ASSERT_EQ(truncate(filename, 5), 0);
83 ASSERT_TRUE(check_file_contains(filename, empty, 5));
84
85 ASSERT_EQ(close(fd), 0);
86 ASSERT_EQ(close(fd2), 0);
87 ASSERT_EQ(unlink(filename), 0);
88
89 END_TEST;
90 }
91
fill_file(int fd,uint8_t * u8,ssize_t new_len,ssize_t old_len)92 bool fill_file(int fd, uint8_t* u8, ssize_t new_len, ssize_t old_len) {
93 BEGIN_HELPER;
94 fbl::AllocChecker ac;
95 fbl::unique_ptr<uint8_t[]> readbuf(new (&ac) uint8_t[new_len]);
96 ASSERT_TRUE(ac.check());
97 if (new_len > old_len) { // Expanded the file
98 // Verify that the file is unchanged up to old_len
99 ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
100 ASSERT_STREAM_ALL(read, fd, readbuf.get(), old_len);
101 ASSERT_EQ(memcmp(readbuf.get(), u8, old_len), 0);
102 // Verify that the file is filled with zeroes from old_len to new_len
103 ASSERT_EQ(lseek(fd, old_len, SEEK_SET), old_len);
104 ASSERT_STREAM_ALL(read, fd, readbuf.get(), new_len - old_len);
105 for (ssize_t n = 0; n < (new_len - old_len); n++) {
106 ASSERT_EQ(readbuf[n], 0);
107 }
108 // Overwrite those zeroes with the contents of u8
109 ASSERT_EQ(lseek(fd, old_len, SEEK_SET), old_len);
110 ASSERT_STREAM_ALL(write, fd, u8 + old_len, new_len - old_len);
111 } else { // Shrunk the file (or kept it the same length)
112 // Verify that the file is unchanged up to new_len
113 ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
114 ASSERT_STREAM_ALL(read, fd, readbuf.get(), new_len);
115 ASSERT_EQ(memcmp(readbuf.get(), u8, new_len), 0);
116 }
117 END_HELPER;
118 }
119
120 template <bool Remount>
checked_truncate(const char * filename,uint8_t * u8,ssize_t new_len)121 bool checked_truncate(const char* filename, uint8_t* u8, ssize_t new_len) {
122 BEGIN_HELPER;
123 // Acquire the old size
124 struct stat st;
125 ASSERT_EQ(stat(filename, &st), 0);
126 ssize_t old_len = st.st_size;
127
128 // Truncate the file, verify the size gets updated
129 int fd = open(filename, O_RDWR, 0644);
130 ASSERT_GT(fd, 0);
131 ASSERT_EQ(ftruncate(fd, new_len), 0);
132 ASSERT_EQ(stat(filename, &st), 0);
133 ASSERT_EQ(st.st_size, new_len);
134
135 // Close and reopen the file; verify the inode stays updated
136 ASSERT_EQ(close(fd), 0);
137 fd = open(filename, O_RDWR, 0644);
138 ASSERT_GT(fd, 0);
139 ASSERT_EQ(stat(filename, &st), 0);
140 ASSERT_EQ(st.st_size, new_len);
141
142 if (Remount) {
143 ASSERT_EQ(close(fd), 0);
144 ASSERT_TRUE(check_remount(), "Could not remount filesystem");
145 ASSERT_EQ(stat(filename, &st), 0);
146 ASSERT_EQ(st.st_size, new_len);
147 fd = open(filename, O_RDWR, 0644);
148 }
149
150 ASSERT_TRUE(fill_file(fd, u8, new_len, old_len));
151 ASSERT_EQ(close(fd), 0);
152 END_HELPER;
153 }
154
fchecked_truncate(int fd,uint8_t * u8,ssize_t new_len)155 bool fchecked_truncate(int fd, uint8_t* u8, ssize_t new_len) {
156 BEGIN_HELPER;
157
158 // Acquire the old size
159 struct stat st;
160 ASSERT_EQ(fstat(fd, &st), 0);
161 ssize_t old_len = st.st_size;
162
163 // Truncate the file, verify the size gets updated
164 ASSERT_EQ(ftruncate(fd, new_len), 0);
165 ASSERT_EQ(fstat(fd, &st), 0);
166 ASSERT_EQ(st.st_size, new_len);
167
168 ASSERT_TRUE(fill_file(fd, u8, new_len, old_len));
169 END_HELPER;
170 }
171
172 enum TestType {
173 KeepOpen,
174 Reopen,
175 Remount,
176 };
177
178 // Test that truncate doesn't have issues dealing with larger files
179 // Repeatedly write to / truncate a file.
180 template <size_t BufSize, size_t Iterations, TestType Test>
TestTruncateLarge(void)181 bool TestTruncateLarge(void) {
182 BEGIN_TEST;
183
184 if ((Test == Remount) && !test_info->can_be_mounted) {
185 fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
186 return true;
187 }
188
189 // Fill a test buffer with data
190 fbl::AllocChecker ac;
191 fbl::unique_ptr<uint8_t[]> buf(new (&ac) uint8_t[BufSize]);
192 ASSERT_TRUE(ac.check());
193
194 unsigned seed = static_cast<unsigned>(zx_ticks_get());
195 unittest_printf("Truncate test using seed: %u\n", seed);
196 srand(seed);
197 for (unsigned n = 0; n < BufSize; n++) {
198 buf[n] = static_cast<uint8_t>(rand_r(&seed));
199 }
200
201 // Start a file filled with a buffer
202 const char* filename = "::alpha";
203 int fd = open(filename, O_RDWR | O_CREAT, 0644);
204 ASSERT_GT(fd, 0);
205 ASSERT_STREAM_ALL(write, fd, buf.get(), BufSize);
206
207 if (Test != KeepOpen) {
208 ASSERT_EQ(close(fd), 0);
209 }
210
211 // Repeatedly truncate / write to the file
212 for (size_t i = 0; i < Iterations; i++) {
213 size_t len = rand_r(&seed) % BufSize;
214 if (Test == KeepOpen) {
215 ASSERT_TRUE(fchecked_truncate(fd, buf.get(), len));
216 } else {
217 ASSERT_TRUE(checked_truncate<Test == Remount>(filename, buf.get(), len));
218 }
219 }
220 ASSERT_EQ(unlink(filename), 0);
221 if (Test == KeepOpen) {
222 ASSERT_EQ(close(fd), 0);
223 }
224
225 END_TEST;
226 }
227
228 enum SparseTestType {
229 UnlinkThenClose,
230 CloseThenUnlink,
231 };
232
233 // This test catches a particular regression in MinFS truncation, where,
234 // if a block is cut in half for truncation, it is read, filled with
235 // zeroes, and writen back out to disk.
236 //
237 // This test tries to proke at a variety of offsets of interest.
238 template <SparseTestType Test>
TestTruncatePartialBlockSparse(void)239 bool TestTruncatePartialBlockSparse(void) {
240 BEGIN_TEST;
241
242 if (strcmp(test_info->name, "minfs")) {
243 fprintf(stderr, "Test is MinFS-Exclusive; ignoring\n");
244 return true;
245 }
246
247 // TODO(smklein): Acquire these constants directly from MinFS's header
248 constexpr size_t kBlockSize = 8192;
249 constexpr size_t kDirectBlocks = 16;
250 constexpr size_t kIndirectBlocks = 31;
251 constexpr size_t kDirectPerIndirect = kBlockSize / 4;
252
253 uint8_t buf[kBlockSize];
254 memset(buf, 0xAB, sizeof(buf));
255
256 off_t write_offsets[] = {
257 kBlockSize * 5,
258 kBlockSize * kDirectBlocks,
259 kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * 1,
260 kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * 2,
261 kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks - 2 * kBlockSize,
262 kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks - kBlockSize,
263 kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks,
264 kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks + kBlockSize,
265 };
266
267 for (size_t i = 0; i < fbl::count_of(write_offsets); i++) {
268 off_t write_off = write_offsets[i];
269 int fd = open("::truncate-sparse", O_CREAT | O_RDWR);
270 ASSERT_GT(fd, 0);
271 ASSERT_EQ(lseek(fd, write_off, SEEK_SET), write_off);
272 ASSERT_EQ(write(fd, buf, sizeof(buf)), sizeof(buf));
273 ASSERT_EQ(ftruncate(fd, write_off + 2 * kBlockSize), 0);
274 ASSERT_EQ(ftruncate(fd, write_off + kBlockSize + kBlockSize / 2), 0);
275 ASSERT_EQ(ftruncate(fd, write_off + kBlockSize / 2), 0);
276 ASSERT_EQ(ftruncate(fd, write_off - kBlockSize / 2), 0);
277 if (Test == UnlinkThenClose) {
278 ASSERT_EQ(unlink("::truncate-sparse"), 0);
279 ASSERT_EQ(close(fd), 0);
280 } else {
281 ASSERT_EQ(close(fd), 0);
282 ASSERT_EQ(unlink("::truncate-sparse"), 0);
283 }
284 }
285
286 END_TEST;
287 }
288
TestTruncateErrno(void)289 bool TestTruncateErrno(void) {
290 BEGIN_TEST;
291
292 int fd = open("::truncate_errno", O_RDWR | O_CREAT | O_EXCL);
293 ASSERT_GT(fd, 0);
294
295 ASSERT_EQ(ftruncate(fd, -1), -1);
296 ASSERT_EQ(errno, EINVAL);
297 errno = 0;
298 ASSERT_EQ(ftruncate(fd, 1UL << 60), -1);
299 ASSERT_EQ(errno, EINVAL);
300
301 ASSERT_EQ(unlink("::truncate_errno"), 0);
302 ASSERT_EQ(close(fd), 0);
303 END_TEST;
304 }
305
306 const test_disk_t disk = {
307 .block_count = 3 * (1LLU << 16),
308 .block_size = 1LLU << 9,
309 .slice_size = 1LLU << 23,
310 };
311
312 } // namespace
313
314 RUN_FOR_ALL_FILESYSTEMS_SIZE(truncate_tests, disk,
315 RUN_TEST_MEDIUM(TestTruncateSmall)
316 RUN_TEST_MEDIUM((TestTruncateLarge<1 << 10, 100, KeepOpen>))
317 RUN_TEST_MEDIUM((TestTruncateLarge<1 << 10, 100, Reopen>))
318 RUN_TEST_MEDIUM((TestTruncateLarge<1 << 15, 50, KeepOpen>))
319 RUN_TEST_MEDIUM((TestTruncateLarge<1 << 15, 50, Reopen>))
320 RUN_TEST_LARGE((TestTruncateLarge<1 << 20, 50, KeepOpen>))
321 RUN_TEST_LARGE((TestTruncateLarge<1 << 20, 50, Reopen>))
322 RUN_TEST_LARGE((TestTruncateLarge<1 << 20, 50, Remount>))
323 RUN_TEST_LARGE((TestTruncateLarge<1 << 25, 50, KeepOpen>))
324 RUN_TEST_LARGE((TestTruncateLarge<1 << 25, 50, Reopen>))
325 RUN_TEST_LARGE((TestTruncateLarge<1 << 25, 50, Remount>))
326 RUN_TEST_MEDIUM((TestTruncatePartialBlockSparse<UnlinkThenClose>))
327 RUN_TEST_MEDIUM((TestTruncatePartialBlockSparse<CloseThenUnlink>))
328 RUN_TEST_MEDIUM(TestTruncateErrno)
329 )
330