1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2019 SUSE LLC <[email protected]>
4 */
5
6 /*\
7 * [Description]
8 *
9 * Tests misaligned fallocate()
10 *
11 * Test scenario:
12 *
13 * 1. write() several blocks worth of data
14 * 2. fallocate() some more space (not aligned to FS blocks)
15 * 3. try to write() into the allocated space
16 * 4. deallocate misaligned part of file range written in step 1
17 * 5. read() the deallocated range and check that it was zeroed
18 *
19 * Subtests:
20 *
21 * - fill filesystem between step 2 and 3
22 * - disable copy-on-write on test file
23 * - combinations of above subtests
24 */
25
26 /*
27 * This is also regression test for:
28 * e093c4be760e ("xfs: Fix tail rounding in xfs_alloc_file_space()")
29 * 6d4572a9d71d ("Allow btrfs_truncate_block() to fallback to nocow for data
30 * space reservation")
31 */
32
33 #define _GNU_SOURCE
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <fcntl.h>
39 #include <sys/ioctl.h>
40 #include <linux/fs.h>
41 #include "tst_test.h"
42 #include "lapi/fallocate.h"
43
44 #define MNTPOINT "mntpoint"
45 #define TEMPFILE MNTPOINT "/test_file"
46 #define WRITE_BLOCKS 8
47 #define FALLOCATE_BLOCKS 2
48 #define DEALLOCATE_BLOCKS 3
49 #define TESTED_FLAGS "fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)"
50
51 const struct test_case {
52 int no_cow, fill_fs;
53 } testcase_list[] = {
54 {1, 0},
55 {1, 1},
56 {0, 0},
57 {0, 1}
58 };
59
60 static int cow_support;
61 static char *wbuf, *rbuf;
62 static blksize_t blocksize;
63 static long wbuf_size, rbuf_size, block_offset;
64
toggle_cow(int fd,int enable)65 static int toggle_cow(int fd, int enable)
66 {
67 int ret, attr;
68
69 ret = ioctl(fd, FS_IOC_GETFLAGS, &attr);
70
71 if (ret)
72 return ret;
73
74 if (enable)
75 attr &= ~FS_NOCOW_FL;
76 else
77 attr |= FS_NOCOW_FL;
78
79 return ioctl(fd, FS_IOC_SETFLAGS, &attr);
80 }
81
setup(void)82 static void setup(void)
83 {
84 unsigned char ch;
85 long i;
86 int fd;
87 struct stat statbuf;
88
89 fd = SAFE_OPEN(TEMPFILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
90
91 /*
92 * Set FS_NOCOW_FL flag on the temp file. Non-CoW filesystems will
93 * return error.
94 */
95 TEST(toggle_cow(fd, 0));
96 SAFE_FSTAT(fd, &statbuf);
97 blocksize = statbuf.st_blksize;
98 block_offset = MIN(blocksize / 2, (blksize_t)512);
99 wbuf_size = MAX(WRITE_BLOCKS, FALLOCATE_BLOCKS) * blocksize;
100 rbuf_size = (DEALLOCATE_BLOCKS + 1) * blocksize;
101 SAFE_CLOSE(fd);
102 SAFE_UNLINK(TEMPFILE);
103
104 if (blocksize < 2)
105 tst_brk(TCONF, "Block size %ld too small for test", blocksize);
106
107 if (!TST_RET) {
108 cow_support = 1;
109 } else {
110 switch (TST_ERR) {
111 case ENOTSUP:
112 case ENOTTY:
113 case EINVAL:
114 case ENOSYS:
115 cow_support = 0;
116 break;
117
118 default:
119 tst_brk(TBROK|TTERRNO,
120 "Error checking copy-on-write support");
121 break;
122 }
123 }
124
125 tst_res(TINFO, "Copy-on-write is%s supported",
126 cow_support ? "" : " not");
127 wbuf = SAFE_MALLOC(wbuf_size);
128 rbuf = SAFE_MALLOC(rbuf_size);
129
130 /* Fill the buffer with known values */
131 for (i = 0, ch = 1; i < wbuf_size; i++, ch++)
132 wbuf[i] = ch;
133 }
134
check_result(const struct test_case * tc,const char * func,long exp)135 static int check_result(const struct test_case *tc, const char *func, long exp)
136 {
137 if (tc->fill_fs && !tc->no_cow && TST_RET < 0) {
138 if (TST_RET != -1) {
139 tst_res(TFAIL, "%s returned unexpected value %ld",
140 func, TST_RET);
141 return 0;
142 }
143
144 if (TST_ERR != ENOSPC) {
145 tst_res(TFAIL | TTERRNO, "%s should fail with ENOSPC",
146 func);
147 return 0;
148 }
149
150 tst_res(TPASS | TTERRNO, "%s on full FS with CoW", func);
151 return 1;
152 }
153
154 if (TST_RET < 0) {
155 tst_res(TFAIL | TTERRNO, "%s failed unexpectedly", func);
156 return 0;
157 }
158
159 if (TST_RET != exp) {
160 tst_res(TFAIL,
161 "Unexpected return value from %s: %ld (expected %ld)",
162 func, TST_RET, exp);
163 return 0;
164 }
165
166 tst_res(TPASS, "%s successful", func);
167 return 1;
168 }
169
run(unsigned int n)170 static void run(unsigned int n)
171 {
172 int fd, i, err;
173 long offset, size;
174 const struct test_case *tc = testcase_list + n;
175
176 tst_res(TINFO, "Case %u. Fill FS: %s; Use copy on write: %s", n+1,
177 tc->fill_fs ? "yes" : "no", tc->no_cow ? "no" : "yes");
178 fd = SAFE_OPEN(TEMPFILE, O_RDWR | O_CREAT | O_TRUNC, 0644);
179
180 if (cow_support)
181 toggle_cow(fd, !tc->no_cow);
182 else if (!tc->no_cow)
183 tst_brk(TCONF, "File system does not support copy-on-write");
184
185 /* Prepare test data for deallocation test */
186 size = WRITE_BLOCKS * blocksize;
187 SAFE_WRITE(SAFE_WRITE_ALL, fd, wbuf, size);
188
189 /* Allocation test */
190 offset = size + block_offset;
191 size = FALLOCATE_BLOCKS * blocksize;
192 TEST(fallocate(fd, 0, offset, size));
193
194 if (TST_RET) {
195 SAFE_CLOSE(fd);
196
197 if (TST_ERR == ENOTSUP)
198 tst_brk(TCONF | TTERRNO, "fallocate() not supported");
199
200 tst_brk(TBROK | TTERRNO, "fallocate(fd, 0, %ld, %ld)", offset,
201 size);
202 }
203
204 if (tc->fill_fs)
205 tst_fill_fs(MNTPOINT, 1, TST_FILL_RANDOM);
206
207 SAFE_LSEEK(fd, offset, SEEK_SET);
208 TEST(write(fd, wbuf, size));
209 if (check_result(tc, "write()", size))
210 tst_res(TPASS, "Misaligned allocation works as expected");
211
212 /* Deallocation test */
213 size = DEALLOCATE_BLOCKS * blocksize;
214 offset = block_offset;
215 TEST(fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset,
216 size));
217
218 if (TST_RET == -1 && TST_ERR == ENOTSUP) {
219 tst_res(TCONF | TTERRNO, TESTED_FLAGS);
220 goto end;
221 }
222
223 if (!check_result(tc, TESTED_FLAGS, 0) || TST_RET)
224 goto end;
225
226 /* Validate that fallocate() cleared the correct file range */
227 SAFE_LSEEK(fd, 0, SEEK_SET);
228 SAFE_READ(1, fd, rbuf, rbuf_size);
229
230 for (err = 0, i = offset; i < offset + size; i++) {
231 if (rbuf[i]) {
232 err = 1;
233 break;
234 }
235 }
236
237 err = err || memcmp(rbuf, wbuf, offset);
238 offset += size;
239 size = rbuf_size - offset;
240 err = err || memcmp(rbuf + offset, wbuf + offset, size);
241
242 if (err)
243 tst_res(TFAIL, TESTED_FLAGS
244 " did not clear the correct file range.");
245 else
246 tst_res(TPASS, TESTED_FLAGS " cleared the correct file range");
247
248 end:
249 SAFE_CLOSE(fd);
250 tst_purge_dir(MNTPOINT);
251 }
252
cleanup(void)253 static void cleanup(void)
254 {
255 free(wbuf);
256 free(rbuf);
257 }
258
259 static struct tst_test test = {
260 .test = run,
261 .tcnt = ARRAY_SIZE(testcase_list),
262 .needs_root = 1,
263 .dev_min_size = 1024,
264 .max_runtime = 120,
265 .mount_device = 1,
266 .mntpoint = MNTPOINT,
267 .all_filesystems = 1,
268 .setup = setup,
269 .cleanup = cleanup,
270 .tags = (const struct tst_tag[]) {
271 {"linux-git", "e093c4be760e"},
272 {"linux-git", "6d4572a9d71d"},
273 {}
274 }
275 };
276