• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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