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