xref: /aosp_15_r20/external/ltp/testcases/kernel/fs/fsx-linux/fsx-linux.c (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 1991, NeXT Computer, Inc.  All Rights Reserverd.
4  *	Author:	Avadis Tevanian, Jr.
5  *
6  * Copyright (c) 1998-2001 Apple Computer, Inc. All rights reserved.
7  *	Conrad Minshall <[email protected]>
8  *	Dave Jones <[email protected]>
9  *	Zach Brown <[email protected]>
10  *	Joe Sokol, Pat Dirks, Clark Warner, Guy Harris
11  *
12  * Copyright (C) 2023 SUSE LLC Andrea Cervesato <[email protected]>
13  */
14 
15 /*\
16  * [Description]
17  *
18  * This is a complete rewrite of the old fsx-linux tool, created by
19  * NeXT Computer, Inc. and Apple Computer, Inc. between 1991 and 2001,
20  * then adapted for LTP. Test is actually a file system exerciser: we bring a
21  * file and randomly write operations like read/write/map read/map write and
22  * truncate, according with input parameters. Then we check if all of them
23  * have been completed.
24  */
25 
26 #include <stdlib.h>
27 #include "tst_test.h"
28 
29 #define FNAME "ltp-file.bin"
30 
31 enum {
32 	OP_READ = 0,
33 	OP_WRITE,
34 	OP_TRUNCATE,
35 	OP_MAPREAD,
36 	OP_MAPWRITE,
37 	/* keep counter here */
38 	OP_TOTAL,
39 };
40 
41 static char *str_file_max_size;
42 static char *str_op_max_size;
43 static char *str_op_nums;
44 static char *str_op_write_align;
45 static char *str_op_read_align;
46 static char *str_op_trunc_align;
47 
48 static int file_desc;
49 static long long file_max_size = 256 * 1024;
50 static long long op_max_size = 64 * 1024;
51 static long long file_size;
52 static int op_write_align = 1;
53 static int op_read_align = 1;
54 static int op_trunc_align = 1;
55 static int op_nums = 1000;
56 static int page_size;
57 
58 static char *file_buff;
59 static char *temp_buff;
60 
61 struct file_pos_t {
62 	long long offset;
63 	long long size;
64 };
65 
op_align_pages(struct file_pos_t * pos)66 static void op_align_pages(struct file_pos_t *pos)
67 {
68 	long long pg_offset;
69 
70 	pg_offset = pos->offset % page_size;
71 
72 	pos->offset -= pg_offset;
73 	pos->size += pg_offset;
74 }
75 
op_file_position(const long long fsize,const int align,struct file_pos_t * pos)76 static void op_file_position(
77 	const long long fsize,
78 	const int align,
79 	struct file_pos_t *pos)
80 {
81 	long long diff;
82 
83 	pos->offset = random() % fsize;
84 	pos->size = random() % (fsize - pos->offset);
85 
86 	diff = pos->offset % align;
87 
88 	if (diff) {
89 		pos->offset -= diff;
90 		pos->size += diff;
91 	}
92 
93 	if (!pos->size)
94 		pos->size = 1;
95 }
96 
update_file_size(struct file_pos_t const * pos)97 static void update_file_size(struct file_pos_t const *pos)
98 {
99 	if (pos->offset + pos->size > file_size) {
100 		file_size = pos->offset + pos->size;
101 		tst_res(TDEBUG, "File size changed: %llu", file_size);
102 	}
103 }
104 
memory_compare(const char * a,const char * b,const long long offset,const long long size)105 static int memory_compare(
106 	const char *a,
107 	const char *b,
108 	const long long offset,
109 	const long long size)
110 {
111 	int diff;
112 
113 	for (long long i = 0; i < size; i++) {
114 		diff = a[i] - b[i];
115 		if (diff) {
116 			tst_res(TDEBUG, "File memory differs at offset=%llu ('%c' != '%c')",
117 				offset + i, a[i], b[i]);
118 			break;
119 		}
120 	}
121 
122 	return diff;
123 }
124 
op_read(void)125 static int op_read(void)
126 {
127 	if (!file_size) {
128 		tst_res(TINFO, "Skipping zero size read");
129 		return 0;
130 	}
131 
132 	struct file_pos_t pos;
133 
134 	op_file_position(file_size, op_read_align, &pos);
135 
136 	tst_res(TDEBUG, "Reading at offset=%llu, size=%llu",
137 		pos.offset, pos.size);
138 
139 	memset(temp_buff, 0, file_max_size);
140 
141 	SAFE_LSEEK(file_desc, (off_t)pos.offset, SEEK_SET);
142 	SAFE_READ(0, file_desc, temp_buff, pos.size);
143 
144 	int ret = memory_compare(
145 		file_buff + pos.offset,
146 		temp_buff,
147 		pos.offset,
148 		pos.size);
149 
150 	if (ret)
151 		return -1;
152 
153 	return 1;
154 }
155 
op_write(void)156 static int op_write(void)
157 {
158 	if (file_size >= file_max_size) {
159 		tst_res(TINFO, "Skipping max size write");
160 		return 0;
161 	}
162 
163 	struct file_pos_t pos;
164 	char data;
165 
166 	op_file_position(file_max_size, op_write_align, &pos);
167 
168 	for (long long i = 0; i < pos.size; i++) {
169 		data = random() % 10 + 'a';
170 
171 		file_buff[pos.offset + i] = data;
172 		temp_buff[i] = data;
173 	}
174 
175 	tst_res(TDEBUG, "Writing at offset=%llu, size=%llu",
176 		pos.offset, pos.size);
177 
178 	SAFE_LSEEK(file_desc, (off_t)pos.offset, SEEK_SET);
179 	SAFE_WRITE(SAFE_WRITE_ALL, file_desc, temp_buff, pos.size);
180 
181 	update_file_size(&pos);
182 
183 	return 1;
184 }
185 
op_truncate(void)186 static int op_truncate(void)
187 {
188 	struct file_pos_t pos;
189 
190 	op_file_position(file_max_size, op_trunc_align, &pos);
191 	file_size = pos.offset + pos.size;
192 
193 	tst_res(TDEBUG, "Truncating to %llu", file_size);
194 
195 	SAFE_FTRUNCATE(file_desc, file_size);
196 	memset(file_buff + file_size, 0, file_max_size - file_size);
197 
198 	return 1;
199 }
200 
op_map_read(void)201 static int op_map_read(void)
202 {
203 	if (!file_size) {
204 		tst_res(TINFO, "Skipping zero size read");
205 		return 0;
206 	}
207 
208 	struct file_pos_t pos;
209 	char *addr;
210 
211 	op_file_position(file_size, op_read_align, &pos);
212 	op_align_pages(&pos);
213 
214 	tst_res(TDEBUG, "Map reading at offset=%llu, size=%llu",
215 		pos.offset, pos.size);
216 
217 	addr = SAFE_MMAP(
218 		0, pos.size,
219 		PROT_READ,
220 		MAP_FILE | MAP_SHARED,
221 		file_desc,
222 		(off_t)pos.offset);
223 
224 	memcpy(file_buff + pos.offset, addr, pos.size);
225 
226 	int ret = memory_compare(
227 		addr,
228 		file_buff + pos.offset,
229 		pos.offset,
230 		pos.size);
231 
232 	SAFE_MUNMAP(addr, pos.size);
233 	if (ret)
234 		return -1;
235 
236 	return 1;
237 }
238 
op_map_write(void)239 static int op_map_write(void)
240 {
241 	if (file_size >= file_max_size) {
242 		tst_res(TINFO, "Skipping max size write");
243 		return 0;
244 	}
245 
246 	struct file_pos_t pos;
247 	char *addr;
248 
249 	op_file_position(file_max_size, op_write_align, &pos);
250 	op_align_pages(&pos);
251 
252 	if (file_size < pos.offset + pos.size)
253 		SAFE_FTRUNCATE(file_desc, pos.offset + pos.size);
254 
255 	tst_res(TDEBUG, "Map writing at offset=%llu, size=%llu",
256 		pos.offset, pos.size);
257 
258 	for (long long i = 0; i < pos.size; i++)
259 		file_buff[pos.offset + i] = random() % 10 + 'l';
260 
261 	addr = SAFE_MMAP(
262 		0, pos.size,
263 		PROT_READ | PROT_WRITE,
264 		MAP_FILE | MAP_SHARED,
265 		file_desc,
266 		(off_t)pos.offset);
267 
268 	memcpy(addr, file_buff + pos.offset, pos.size);
269 	SAFE_MSYNC(addr, pos.size, MS_SYNC);
270 	SAFE_MUNMAP(addr, pos.size);
271 	update_file_size(&pos);
272 
273 	return 1;
274 }
275 
run(void)276 static void run(void)
277 {
278 	int op;
279 	int ret;
280 	int counter = 0;
281 
282 	file_size = 0;
283 
284 	memset(file_buff, 0, file_max_size);
285 	memset(temp_buff, 0, file_max_size);
286 
287 	SAFE_FTRUNCATE(file_desc, 0);
288 
289 	while (counter < op_nums) {
290 		op = random() % OP_TOTAL;
291 
292 		switch (op) {
293 		case OP_WRITE:
294 			ret = op_write();
295 			break;
296 		case OP_MAPREAD:
297 			ret = op_map_read();
298 			break;
299 		case OP_MAPWRITE:
300 			ret = op_map_write();
301 			break;
302 		case OP_TRUNCATE:
303 			ret = op_truncate();
304 			break;
305 		case OP_READ:
306 		default:
307 			ret = op_read();
308 			break;
309 		};
310 
311 		if (ret == -1)
312 			break;
313 
314 		counter += ret;
315 	}
316 
317 	if (counter != op_nums)
318 		tst_brk(TFAIL, "Some file operations failed");
319 	else
320 		tst_res(TPASS, "All file operations succeed");
321 }
322 
setup(void)323 static void setup(void)
324 {
325 	if (tst_parse_filesize(str_file_max_size, &file_max_size, 1, LLONG_MAX))
326 		tst_brk(TBROK, "Invalid file size '%s'", str_file_max_size);
327 
328 	if (tst_parse_filesize(str_op_max_size, &op_max_size, 1, LLONG_MAX))
329 		tst_brk(TBROK, "Invalid maximum size for single operation '%s'", str_op_max_size);
330 
331 	if (tst_parse_int(str_op_nums, &op_nums, 1, INT_MAX))
332 		tst_brk(TBROK, "Invalid number of operations '%s'", str_op_nums);
333 
334 	if (tst_parse_int(str_op_write_align, &op_write_align, 1, INT_MAX))
335 		tst_brk(TBROK, "Invalid memory write alignment factor '%s'", str_op_write_align);
336 
337 	if (tst_parse_int(str_op_read_align, &op_read_align, 1, INT_MAX))
338 		tst_brk(TBROK, "Invalid memory read alignment factor '%s'", str_op_read_align);
339 
340 	if (tst_parse_int(str_op_trunc_align, &op_trunc_align, 1, INT_MAX))
341 		tst_brk(TBROK, "Invalid memory truncate alignment factor '%s'", str_op_trunc_align);
342 
343 	page_size = (int)sysconf(_SC_PAGESIZE);
344 
345 	srandom(time(NULL));
346 
347 	file_desc = SAFE_OPEN(FNAME, O_RDWR | O_CREAT, 0666);
348 
349 	file_buff = SAFE_MALLOC(file_max_size);
350 	temp_buff = SAFE_MALLOC(file_max_size);
351 }
352 
cleanup(void)353 static void cleanup(void)
354 {
355 	if (file_buff)
356 		free(file_buff);
357 
358 	if (temp_buff)
359 		free(temp_buff);
360 
361 	if (file_desc)
362 		SAFE_CLOSE(file_desc);
363 }
364 
365 static struct tst_test test = {
366 	.needs_tmpdir = 1,
367 	.setup = setup,
368 	.cleanup = cleanup,
369 	.test_all = run,
370 	.max_runtime = 1800,
371 	.options = (struct tst_option[]) {
372 		{ "l:", &str_file_max_size, "Maximum size in MB of the test file(s) (default 262144)" },
373 		{ "o:", &str_op_max_size, "Maximum size for single operation (default 65536)" },
374 		{ "N:", &str_op_nums, "Total # operations to do (default 1000)" },
375 		{ "w:", &str_op_write_align, "Write memory page alignment (default 1)" },
376 		{ "r:", &str_op_read_align, "Read memory page alignment (default 1)" },
377 		{ "t:", &str_op_trunc_align, "Truncate memory page alignment (default 1)" },
378 		{},
379 	},
380 };
381