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