xref: /aosp_15_r20/external/ltp/testcases/kernel/syscalls/fallocate/fallocate06.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
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