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