1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2015 Oracle and/or its affiliates. All Rights Reserved.
4 * Author: Alexey Kodanev <alexey.kodanev@oracle.com>
5 *
6 * Test allocates a file with specified size then tests the following modes:
7 * FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE and FALLOC_FL_COLLAPSE_RANGE.
8 */
9
10 #define _GNU_SOURCE
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <errno.h>
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <unistd.h>
18
19 #include "tst_test.h"
20 #include "lapi/fallocate.h"
21
22 static int fd;
23 static size_t block_size;
24 static size_t buf_size;
25
26 #define MNTPOINT "fallocate"
27 #define FNAME MNTPOINT "/fallocate.txt"
28 #define NUM_OF_BLOCKS 3
29
30 static char *verbose;
31
get_blocksize(void)32 static void get_blocksize(void)
33 {
34 struct stat file_stat;
35
36 SAFE_FSTAT(fd, &file_stat);
37
38 block_size = file_stat.st_blksize;
39 buf_size = NUM_OF_BLOCKS * block_size;
40 }
41
get_allocsize(void)42 static size_t get_allocsize(void)
43 {
44 struct stat file_stat;
45
46 fsync(fd);
47
48 SAFE_FSTAT(fd, &file_stat);
49
50 return file_stat.st_blocks * 512;
51 }
52
fill_tst_buf(char buf[])53 static void fill_tst_buf(char buf[])
54 {
55 /* fill the buffer with a, b, c, ... letters on each block */
56 int i;
57
58 for (i = 0; i < NUM_OF_BLOCKS; ++i)
59 memset(buf + i * block_size, 'a' + i, block_size);
60 }
61
check_file_data(const char exp_buf[],size_t size)62 static void check_file_data(const char exp_buf[], size_t size)
63 {
64 char rbuf[size];
65
66 tst_res(TINFO, "reading the file, compare with expected buffer");
67
68 SAFE_LSEEK(fd, 0, SEEK_SET);
69 SAFE_READ(1, fd, rbuf, size);
70
71 if (memcmp(exp_buf, rbuf, size)) {
72 if (verbose) {
73 tst_res_hexd(TINFO, exp_buf, size, "expected:");
74 tst_res_hexd(TINFO, rbuf, size, "but read:");
75 }
76 tst_brk(TFAIL, "not expected file data");
77 }
78 }
79
test01(void)80 static void test01(void)
81 {
82 tst_res(TINFO, "allocate '%zu' bytes", buf_size);
83
84 if (fallocate(fd, 0, 0, buf_size) == -1) {
85 if (errno == ENOSYS || errno == EOPNOTSUPP)
86 tst_brk(TCONF, "fallocate() not supported");
87 tst_brk(TFAIL | TERRNO, "fallocate() failed");
88 }
89
90 char buf[buf_size];
91
92 fill_tst_buf(buf);
93
94 SAFE_WRITE(SAFE_WRITE_ALL, fd, buf, buf_size);
95
96 tst_res(TPASS, "test-case succeeded");
97 }
98
test02(void)99 static void test02(void)
100 {
101 size_t alloc_size0 = get_allocsize();
102
103 tst_res(TINFO, "read allocated file size '%zu'", alloc_size0);
104 tst_res(TINFO, "make a hole with FALLOC_FL_PUNCH_HOLE");
105
106 if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
107 block_size, block_size) == -1) {
108 if (errno == EOPNOTSUPP) {
109 tst_brk(TCONF,
110 "FALLOC_FL_PUNCH_HOLE not supported");
111 }
112 tst_brk(TFAIL | TERRNO, "fallocate() failed");
113 }
114
115 tst_res(TINFO, "check that file has a hole with lseek(,,SEEK_HOLE)");
116 off_t ret = lseek(fd, 0, SEEK_HOLE);
117
118 if (ret != (ssize_t)block_size) {
119 /* exclude error when kernel doesn't have SEEK_HOLE support */
120 if (errno != EINVAL) {
121 tst_brk(TFAIL | TERRNO,
122 "fallocate() or lseek() failed");
123 }
124 tst_brk(TBROK | TERRNO,
125 "lseek() doesn't support SEEK_HOLE");
126 } else {
127 tst_res(TINFO, "found a hole at '%ld' offset", ret);
128 }
129
130 size_t alloc_size1 = get_allocsize();
131
132 tst_res(TINFO, "allocated file size before '%zu' and after '%zu'",
133 alloc_size0, alloc_size1);
134 if ((alloc_size0 - block_size) != alloc_size1)
135 tst_brk(TFAIL, "not expected allocated size");
136
137 char exp_buf[buf_size];
138
139 fill_tst_buf(exp_buf);
140 memset(exp_buf + block_size, 0, block_size);
141
142 check_file_data(exp_buf, buf_size);
143
144 tst_res(TPASS, "test-case succeeded");
145 }
146
test03(void)147 static void test03(void)
148 {
149 tst_res(TINFO, "zeroing file space with FALLOC_FL_ZERO_RANGE");
150
151 if (tst_kvercmp(3, 15, 0) < 0) {
152 tst_brk(TCONF,
153 "FALLOC_FL_ZERO_RANGE needs Linux 3.15 or newer");
154 }
155
156 size_t alloc_size0 = get_allocsize();
157
158 tst_res(TINFO, "read current allocated file size '%zu'", alloc_size0);
159
160 if (fallocate(fd, FALLOC_FL_ZERO_RANGE, block_size - 1,
161 block_size + 2) == -1) {
162 if (errno == EOPNOTSUPP) {
163 tst_brk(TCONF,
164 "FALLOC_FL_ZERO_RANGE not supported");
165 }
166 tst_brk(TFAIL | TERRNO, "fallocate failed");
167 }
168
169 /* The file hole in the specified range must be allocated and
170 * filled with zeros. Check it.
171 */
172 size_t alloc_size1 = get_allocsize();
173
174 tst_res(TINFO, "allocated file size before '%zu' and after '%zu'",
175 alloc_size0, alloc_size1);
176 if ((alloc_size0 + block_size) != alloc_size1)
177 tst_brk(TFAIL, "not expected allocated size");
178
179 char exp_buf[buf_size];
180
181 fill_tst_buf(exp_buf);
182 memset(exp_buf + block_size - 1, 0, block_size + 2);
183
184 check_file_data(exp_buf, buf_size);
185
186 tst_res(TPASS, "test-case succeeded");
187 }
188
test04(void)189 static void test04(void)
190 {
191 tst_res(TINFO, "collapsing file space with FALLOC_FL_COLLAPSE_RANGE");
192
193 size_t alloc_size0 = get_allocsize();
194
195 tst_res(TINFO, "read current allocated file size '%zu'", alloc_size0);
196
197 if (fallocate(fd, FALLOC_FL_COLLAPSE_RANGE, block_size,
198 block_size) == -1) {
199 if (errno == EOPNOTSUPP) {
200 tst_brk(TCONF,
201 "FALLOC_FL_COLLAPSE_RANGE not supported");
202 }
203 tst_brk(TFAIL | TERRNO, "fallocate failed");
204 }
205
206 size_t alloc_size1 = get_allocsize();
207
208 tst_res(TINFO, "allocated file size before '%zu' and after '%zu'",
209 alloc_size0, alloc_size1);
210 if ((alloc_size0 - block_size) != alloc_size1)
211 tst_brk(TFAIL, "not expected allocated size");
212
213 size_t size = buf_size - block_size;
214 char tmp_buf[buf_size];
215 char exp_buf[size];
216
217 fill_tst_buf(tmp_buf);
218
219 memcpy(exp_buf, tmp_buf, block_size);
220 memcpy(exp_buf + block_size, tmp_buf + 2 * block_size,
221 buf_size - block_size * 2);
222
223 exp_buf[block_size - 1] = exp_buf[block_size] = '\0';
224 check_file_data(exp_buf, size);
225
226 tst_res(TPASS, "test-case succeeded");
227 }
228
test05(void)229 static void test05(void)
230 {
231 tst_res(TINFO, "inserting space with FALLOC_FL_INSERT_RANGE");
232
233 size_t alloc_size0 = get_allocsize();
234
235 tst_res(TINFO, "read current allocated file size '%zu'", alloc_size0);
236
237 if (fallocate(fd, FALLOC_FL_INSERT_RANGE, block_size,
238 block_size) == -1) {
239 if (errno == EOPNOTSUPP) {
240 tst_brk(TCONF,
241 "FALLOC_FL_INSERT_RANGE not supported");
242 }
243 tst_brk(TFAIL | TERRNO, "fallocate failed");
244 }
245
246 /* allocate space and ensure that it filled with zeroes */
247 if (fallocate(fd, FALLOC_FL_ZERO_RANGE, block_size, block_size) == -1)
248 tst_brk(TFAIL | TERRNO, "fallocate failed");
249
250 size_t alloc_size1 = get_allocsize();
251
252 tst_res(TINFO, "allocated file size before '%zu' and after '%zu'",
253 alloc_size0, alloc_size1);
254 if ((alloc_size0 + block_size) != alloc_size1)
255 tst_brk(TFAIL, "not expected allocated size");
256
257 char exp_buf[buf_size];
258
259 fill_tst_buf(exp_buf);
260 memset(exp_buf + block_size - 1, 0, block_size + 2);
261
262 check_file_data(exp_buf, buf_size);
263
264 tst_res(TPASS, "test-case succeeded");
265 }
266
267 static void (*tcases[])(void) = {
268 test01, test02, test03, test04, test05
269 };
270
run(unsigned int i)271 static void run(unsigned int i)
272 {
273 tcases[i]();
274 }
275
setup(void)276 static void setup(void)
277 {
278 fd = SAFE_OPEN(FNAME, O_RDWR | O_CREAT, 0700);
279
280 get_blocksize();
281 }
282
cleanup(void)283 static void cleanup(void)
284 {
285 if (fd > 0)
286 SAFE_CLOSE(fd);
287 }
288
289 static struct tst_test test = {
290 .options = (struct tst_option[]) {
291 {"v", &verbose, "Turns on verbose mode"},
292 {}
293 },
294 .cleanup = cleanup,
295 .setup = setup,
296 .test = run,
297 .tcnt = ARRAY_SIZE(tcases),
298 .mount_device = 1,
299 .mntpoint = MNTPOINT,
300 .all_filesystems = 1,
301 .needs_root = 1,
302 };
303