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 <errno.h>
6 #include <fcntl.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12 
13 #include <fbl/algorithm.h>
14 #include <zircon/syscalls.h>
15 
16 #include "filesystems.h"
17 #include "misc.h"
18 
19 #define MB (1 << 20)
20 #define PRINT_SIZE (MB * 100)
21 
22 namespace {
23 
24 enum MountType {
25     DoRemount,
26     DontRemount,
27 };
28 
29 // Test writing as much as we can to a file until we run
30 // out of space
31 template <MountType mt>
test_maxfile(void)32 bool test_maxfile(void) {
33     BEGIN_TEST;
34 
35     if (!test_info->can_be_mounted && mt == DoRemount) {
36         fprintf(stderr, "Filesystem cannot be mounted; cannot remount\n");
37         return true;
38     }
39 
40     // TODO(ZX-1735): We avoid making files that consume more than half
41     // of physical memory. When we can page out files, this restriction
42     // should be removed.
43     const size_t physmem = zx_system_get_physmem();
44     const size_t max_cap = physmem / 2;
45 
46     int fd = open("::bigfile", O_CREAT | O_RDWR, 0644);
47     ASSERT_GT(fd, 0);
48     char data_a[8192];
49     char data_b[8192];
50     char data_c[8192];
51     memset(data_a, 0xaa, sizeof(data_a));
52     memset(data_b, 0xbb, sizeof(data_b));
53     memset(data_c, 0xcc, sizeof(data_c));
54     size_t sz = 0;
55     ssize_t r;
56 
57     auto rotate = [&](const char* data) {
58         if (data == data_a) {
59             return data_b;
60         } else if (data == data_b) {
61             return data_c;
62         } else {
63             return data_a;
64         }
65     };
66 
67     const char* data = data_a;
68     for (;;) {
69         if (sz >= max_cap) {
70             fprintf(stderr, "Approaching physical memory capacity: %zd bytes\n", sz);
71             r = 0;
72             break;
73         }
74 
75         if ((r = write(fd, data, sizeof(data_a))) < 0) {
76             fprintf(stderr, "bigfile received error: %s\n", strerror(errno));
77             if ((errno == EFBIG) || (errno == ENOSPC)) {
78                 // Either the file should be too big (EFBIG) or the file should
79                 // consume the whole volume (ENOSPC).
80                 fprintf(stderr, "(This was an expected error)\n");
81                 r = 0;
82             }
83             break;
84         }
85         if ((sz + r) % PRINT_SIZE < (sz % PRINT_SIZE)) {
86             fprintf(stderr, "wrote %lu MB\n", (sz + r) / MB);
87         }
88         sz += r;
89         ASSERT_EQ(r, sizeof(data_a));
90 
91         // Rotate which data buffer we use
92         data = rotate(data);
93     }
94     ASSERT_EQ(r, 0, "Saw an unexpected error from write");
95     fprintf(stderr, "wrote %lu bytes\n", sz);
96 
97     struct stat buf;
98     ASSERT_EQ(fstat(fd, &buf), 0, "Couldn't stat max file");
99     ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz), "Unexpected max file size");
100 
101     // Try closing, re-opening, and verifying the file
102     ASSERT_EQ(close(fd), 0);
103     if (mt == DoRemount) {
104         ASSERT_TRUE(check_remount(), "Could not remount filesystem");
105     }
106     fd = open("::bigfile", O_RDWR, 0644);
107     ASSERT_GT(fd, 0);
108     ASSERT_EQ(fstat(fd, &buf), 0, "Couldn't stat max file");
109     ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz), "Unexpected max file size");
110     char readbuf[8192];
111     size_t bytes_read = 0;
112     data = data_a;
113     while (bytes_read < sz) {
114         r = read(fd, readbuf, sizeof(readbuf));
115         ASSERT_EQ(r, static_cast<ssize_t>(fbl::min(sz - bytes_read, sizeof(readbuf))));
116         ASSERT_EQ(memcmp(readbuf, data, r), 0, "File failed to verify");
117         data = rotate(data);
118         bytes_read += r;
119     }
120 
121     ASSERT_EQ(bytes_read, sz);
122 
123     ASSERT_EQ(unlink("::bigfile"), 0);
124     ASSERT_EQ(close(fd), 0);
125     END_TEST;
126 }
127 
128 // Test writing to two files, in alternation, until we run out
129 // of space. For trivial (sequential) block allocation policies,
130 // this will create two large files with non-contiguous block
131 // allocations.
132 template <MountType mt>
TestZippedMaxfiles(void)133 bool TestZippedMaxfiles(void) {
134     BEGIN_TEST;
135 
136     if (!test_info->can_be_mounted && mt == DoRemount) {
137         fprintf(stderr, "Filesystem cannot be mounted; cannot remount\n");
138         return true;
139     }
140 
141     // TODO(ZX-1735): We avoid making files that consume more than half
142     // of physical memory. When we can page out files, this restriction
143     // should be removed.
144     const size_t physmem = zx_system_get_physmem();
145     const size_t max_cap = physmem / 4;
146 
147     int fda = open("::bigfile-A", O_CREAT | O_RDWR, 0644);
148     int fdb = open("::bigfile-B", O_CREAT | O_RDWR, 0644);
149     ASSERT_GT(fda, 0);
150     ASSERT_GT(fdb, 0);
151     char data_a[8192];
152     char data_b[8192];
153     memset(data_a, 0xaa, sizeof(data_a));
154     memset(data_b, 0xbb, sizeof(data_b));
155     size_t sz_a = 0;
156     size_t sz_b = 0;
157     ssize_t r;
158 
159     size_t* sz = &sz_a;
160     int fd = fda;
161     const char* data = data_a;
162     for (;;) {
163         if (*sz >= max_cap) {
164             fprintf(stderr, "Approaching physical memory capacity: %zd bytes\n", *sz);
165             r = 0;
166             break;
167         }
168 
169         if ((r = write(fd, data, sizeof(data_a))) <= 0) {
170             fprintf(stderr, "bigfile received error: %s\n", strerror(errno));
171             // Either the file should be too big (EFBIG) or the file should
172             // consume the whole volume (ENOSPC).
173             ASSERT_TRUE(errno == EFBIG || errno == ENOSPC);
174             fprintf(stderr, "(This was an expected error)\n");
175             break;
176         }
177         if ((*sz + r) % PRINT_SIZE < (*sz % PRINT_SIZE)) {
178             fprintf(stderr, "wrote %lu MB\n", (*sz + r) / MB);
179         }
180         *sz += r;
181         ASSERT_EQ(r, sizeof(data_a));
182 
183         fd = (fd == fda) ? fdb : fda;
184         data = (data == data_a) ? data_b : data_a;
185         sz = (sz == &sz_a) ? &sz_b : &sz_a;
186     }
187     fprintf(stderr, "wrote %lu bytes (to A)\n", sz_a);
188     fprintf(stderr, "wrote %lu bytes (to B)\n", sz_b);
189 
190     struct stat buf;
191     ASSERT_EQ(fstat(fda, &buf), 0, "Couldn't stat max file");
192     ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz_a), "Unexpected max file size");
193     ASSERT_EQ(fstat(fdb, &buf), 0, "Couldn't stat max file");
194     ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz_b), "Unexpected max file size");
195 
196     // Try closing, re-opening, and verifying the file
197     ASSERT_EQ(close(fda), 0);
198     ASSERT_EQ(close(fdb), 0);
199     if (mt == DoRemount) {
200         ASSERT_TRUE(check_remount(), "Could not remount filesystem");
201     }
202     fda = open("::bigfile-A", O_RDWR, 0644);
203     fdb = open("::bigfile-B", O_RDWR, 0644);
204     ASSERT_GT(fda, 0);
205     ASSERT_GT(fdb, 0);
206 
207     char readbuf[8192];
208     size_t bytes_read_a = 0;
209     size_t bytes_read_b = 0;
210 
211     fd = fda;
212     data = data_a;
213     sz = &sz_a;
214     size_t* bytes_read = &bytes_read_a;
215     while (*bytes_read < *sz) {
216         r = read(fd, readbuf, sizeof(readbuf));
217         ASSERT_EQ(r, static_cast<ssize_t>(fbl::min(*sz - *bytes_read, sizeof(readbuf))));
218         ASSERT_EQ(memcmp(readbuf, data, r), 0, "File failed to verify");
219         *bytes_read += r;
220 
221         fd = (fd == fda) ? fdb : fda;
222         data = (data == data_a) ? data_b : data_a;
223         sz = (sz == &sz_a) ? &sz_b : &sz_a;
224         bytes_read = (bytes_read == &bytes_read_a) ? &bytes_read_b : &bytes_read_a;
225     }
226 
227     ASSERT_EQ(bytes_read_a, sz_a);
228     ASSERT_EQ(bytes_read_b, sz_b);
229 
230     ASSERT_EQ(unlink("::bigfile-A"), 0);
231     ASSERT_EQ(unlink("::bigfile-B"), 0);
232     ASSERT_EQ(close(fda), 0);
233     ASSERT_EQ(close(fdb), 0);
234 
235     END_TEST;
236 }
237 
238 const test_disk_t disk = {
239     .block_count = 1LLU << 17,
240     .block_size = 1LLU << 9,
241     .slice_size = 1LLU << 23,
242 };
243 
244 }  // namespace
245 
246 RUN_FOR_ALL_FILESYSTEMS_SIZE(maxfile_tests, disk,
247     RUN_TEST_LARGE((test_maxfile<DontRemount>))
248     RUN_TEST_LARGE((test_maxfile<DoRemount>))
249     RUN_TEST_LARGE((TestZippedMaxfiles<DontRemount>))
250     RUN_TEST_LARGE((TestZippedMaxfiles<DoRemount>))
251 )
252