1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2021 The FreeBSD Foundation
5 * All rights reserved.
6 *
7 * This software was developed by Ka Ho Ng under sponsorship from
8 * the FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/param.h>
33 #include <sys/mount.h>
34 #include <sys/stat.h>
35
36 #include <atf-c.h>
37 #include <fcntl.h>
38 #include <malloc.h>
39
40 static off_t file_max_blocks = 32;
41 static const char byte_to_fill = 0x5f;
42
43 static int
fill(int fd,off_t offset,off_t len)44 fill(int fd, off_t offset, off_t len)
45 {
46 int error;
47 size_t blen;
48 char *buf;
49 struct stat statbuf;
50 blksize_t blocksize;
51
52 if (fstat(fd, &statbuf) == -1)
53 return (1);
54 blocksize = statbuf.st_blksize;
55 error = 0;
56 buf = malloc(blocksize);
57 if (buf == NULL)
58 return (1);
59
60 while (len > 0) {
61 blen = len < (off_t)blocksize ? len : blocksize;
62 memset(buf, byte_to_fill, blen);
63 if (pwrite(fd, buf, blen, offset) != (ssize_t)blen) {
64 error = 1;
65 break;
66 }
67 len -= blen;
68 offset += blen;
69 }
70
71 free(buf);
72 return (error);
73 }
74
75 static blksize_t
fd_get_blksize(void)76 fd_get_blksize(void)
77 {
78 struct statfs statfsbuf;
79
80 if (statfs(".", &statfsbuf) == -1)
81 return (-1);
82 return statfsbuf.f_iosize;
83 }
84
85 static int
check_content_dealloc(int fd,off_t hole_start,off_t hole_len,off_t file_sz)86 check_content_dealloc(int fd, off_t hole_start, off_t hole_len, off_t file_sz)
87 {
88 int error;
89 size_t blen;
90 off_t offset, resid;
91 struct stat statbuf;
92 char *buf, *sblk;
93 blksize_t blocksize;
94
95 blocksize = fd_get_blksize();
96 if (blocksize == -1)
97 return (1);
98 error = 0;
99 buf = malloc(blocksize * 2);
100 if (buf == NULL)
101 return (1);
102 sblk = buf + blocksize;
103
104 memset(sblk, 0, blocksize);
105
106 if ((uint64_t)hole_start + hole_len > (uint64_t)file_sz)
107 hole_len = file_sz - hole_start;
108
109 /*
110 * Check hole is zeroed.
111 */
112 offset = hole_start;
113 resid = hole_len;
114 while (resid > 0) {
115 blen = resid < (off_t)blocksize ? resid : blocksize;
116 if (pread(fd, buf, blen, offset) != (ssize_t)blen) {
117 error = 1;
118 break;
119 }
120 if (memcmp(buf, sblk, blen) != 0) {
121 error = 1;
122 break;
123 }
124 resid -= blen;
125 offset += blen;
126 }
127
128 memset(sblk, byte_to_fill, blocksize);
129
130 /*
131 * Check file region before hole is zeroed.
132 */
133 offset = 0;
134 resid = hole_start;
135 while (resid > 0) {
136 blen = resid < (off_t)blocksize ? resid : blocksize;
137 if (pread(fd, buf, blen, offset) != (ssize_t)blen) {
138 error = 1;
139 break;
140 }
141 if (memcmp(buf, sblk, blen) != 0) {
142 error = 1;
143 break;
144 }
145 resid -= blen;
146 offset += blen;
147 }
148
149 /*
150 * Check file region after hole is zeroed.
151 */
152 offset = hole_start + hole_len;
153 resid = file_sz - offset;
154 while (resid > 0) {
155 blen = resid < (off_t)blocksize ? resid : blocksize;
156 if (pread(fd, buf, blen, offset) != (ssize_t)blen) {
157 error = 1;
158 break;
159 }
160 if (memcmp(buf, sblk, blen) != 0) {
161 error = 1;
162 break;
163 }
164 resid -= blen;
165 offset += blen;
166 }
167
168 /*
169 * Check file size matches with expected file size.
170 */
171 if (fstat(fd, &statbuf) == -1)
172 error = -1;
173 if (statbuf.st_size != file_sz)
174 error = -1;
175
176 free(buf);
177 return (error);
178 }
179
180 /*
181 * Check aligned deallocation
182 */
183 ATF_TC_WITHOUT_HEAD(aligned_dealloc);
ATF_TC_BODY(aligned_dealloc,tc)184 ATF_TC_BODY(aligned_dealloc, tc)
185 {
186 struct spacectl_range range;
187 off_t offset, length;
188 blksize_t blocksize;
189 int fd;
190
191 ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
192 range.r_offset = offset = blocksize;
193 range.r_len = length = (file_max_blocks - 1) * blocksize -
194 range.r_offset;
195
196 ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
197 O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
198 ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
199 ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
200 ATF_CHECK(check_content_dealloc(fd, offset, length,
201 file_max_blocks * blocksize) == 0);
202 ATF_REQUIRE(close(fd) == 0);
203 }
204
205 /*
206 * Check unaligned deallocation
207 */
208 ATF_TC_WITHOUT_HEAD(unaligned_dealloc);
ATF_TC_BODY(unaligned_dealloc,tc)209 ATF_TC_BODY(unaligned_dealloc, tc)
210 {
211 struct spacectl_range range;
212 off_t offset, length;
213 blksize_t blocksize;
214 int fd;
215
216 ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
217 range.r_offset = offset = blocksize / 2;
218 range.r_len = length = (file_max_blocks - 1) * blocksize +
219 blocksize / 2 - offset;
220
221 ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
222 O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
223 ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
224 ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
225 ATF_CHECK(check_content_dealloc(fd, offset, length,
226 file_max_blocks * blocksize) == 0);
227 ATF_REQUIRE(close(fd) == 0);
228 }
229
230 /*
231 * Check aligned deallocation from certain offset to OFF_MAX
232 */
233 ATF_TC_WITHOUT_HEAD(aligned_dealloc_offmax);
ATF_TC_BODY(aligned_dealloc_offmax,tc)234 ATF_TC_BODY(aligned_dealloc_offmax, tc)
235 {
236 struct spacectl_range range;
237 off_t offset, length;
238 blksize_t blocksize;
239 int fd;
240
241 ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
242 range.r_offset = offset = blocksize;
243 range.r_len = length = OFF_MAX - offset;
244
245 ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
246 O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
247 ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
248 ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
249 ATF_CHECK(check_content_dealloc(fd, offset, length,
250 file_max_blocks * blocksize) == 0);
251 ATF_REQUIRE(close(fd) == 0);
252 }
253
254 /*
255 * Check unaligned deallocation from certain offset to OFF_MAX
256 */
257 ATF_TC_WITHOUT_HEAD(unaligned_dealloc_offmax);
ATF_TC_BODY(unaligned_dealloc_offmax,tc)258 ATF_TC_BODY(unaligned_dealloc_offmax, tc)
259 {
260 struct spacectl_range range;
261 off_t offset, length;
262 blksize_t blocksize;
263 int fd;
264
265 ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
266 range.r_offset = offset = blocksize / 2;
267 range.r_len = length = OFF_MAX - offset;
268
269 ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
270 O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
271 ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
272 ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
273 ATF_CHECK(check_content_dealloc(fd, offset, length,
274 file_max_blocks * blocksize) == 0);
275 ATF_REQUIRE(close(fd) == 0);
276 }
277
278 /*
279 * Check aligned deallocation around EOF
280 */
281 ATF_TC_WITHOUT_HEAD(aligned_dealloc_eof);
ATF_TC_BODY(aligned_dealloc_eof,tc)282 ATF_TC_BODY(aligned_dealloc_eof, tc)
283 {
284 struct spacectl_range range;
285 off_t offset, length;
286 blksize_t blocksize;
287 int fd;
288
289 ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
290 range.r_offset = offset = blocksize;
291 range.r_len = length = (file_max_blocks + 1) * blocksize -
292 range.r_offset;
293
294 ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
295 O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
296 ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
297 ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
298 ATF_CHECK(check_content_dealloc(fd, offset, length,
299 file_max_blocks * blocksize) == 0);
300 ATF_REQUIRE(close(fd) == 0);
301 }
302
303 /*
304 * Check unaligned deallocation around EOF
305 */
306 ATF_TC_WITHOUT_HEAD(unaligned_dealloc_eof);
ATF_TC_BODY(unaligned_dealloc_eof,tc)307 ATF_TC_BODY(unaligned_dealloc_eof, tc)
308 {
309 struct spacectl_range range;
310 off_t offset, length;
311 blksize_t blocksize;
312 int fd;
313
314 ATF_REQUIRE((blocksize = fd_get_blksize()) != -1);
315 range.r_offset = offset = blocksize / 2;
316 range.r_len = length = file_max_blocks * blocksize + blocksize / 2 -
317 range.r_offset;
318
319 ATF_REQUIRE((fd = open("sys_fspacectl_testfile",
320 O_CREAT | O_RDWR | O_TRUNC, 0600)) != -1);
321 ATF_REQUIRE(fill(fd, 0, file_max_blocks * blocksize) == 0);
322 ATF_CHECK(fspacectl(fd, SPACECTL_DEALLOC, &range, 0, &range) == 0);
323 ATF_CHECK(check_content_dealloc(fd, offset, length,
324 file_max_blocks * blocksize) == 0);
325 ATF_REQUIRE(close(fd) == 0);
326 }
327
ATF_TP_ADD_TCS(tp)328 ATF_TP_ADD_TCS(tp)
329 {
330 ATF_TP_ADD_TC(tp, aligned_dealloc);
331 ATF_TP_ADD_TC(tp, unaligned_dealloc);
332 ATF_TP_ADD_TC(tp, aligned_dealloc_eof);
333 ATF_TP_ADD_TC(tp, unaligned_dealloc_eof);
334 ATF_TP_ADD_TC(tp, aligned_dealloc_offmax);
335 ATF_TP_ADD_TC(tp, unaligned_dealloc_offmax);
336
337 return atf_no_error();
338 }
339