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