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