• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2024 SUSE LLC <mdoucha@suse.cz>
4  */
5 
6 /*\
7  * [Description]
8  *
9  * Write data into a test file using various methods and verify that file
10  * contents match what was written.
11  */
12 
13 #define _GNU_SOURCE
14 #include <fcntl.h>
15 #include <sys/statvfs.h>
16 #include "tst_test.h"
17 #include "tst_safe_prw.h"
18 
19 #define MAX_VEC 8
20 #define TEST_FILENAME "fsplough.dat"
21 
22 typedef void (*io_func)(void *buf, size_t offset, size_t size);
23 
24 static char *workdir_arg;
25 static char *directwr_flag;
26 static char *directrd_flag;
27 static char *loop_arg;
28 static int loop_count = 4096;
29 
30 static int read_fd = -1, write_fd = -1;
31 static char *writebuf, *filedata;
32 static size_t blocksize, bufsize, filesize;
33 
34 static void do_write(void *buf, size_t offset, size_t size);
35 static void do_pwrite(void *buf, size_t offset, size_t size);
36 static void do_writev(void *buf, size_t offset, size_t size);
37 static void do_pwritev(void *buf, size_t offset, size_t size);
38 static void do_read(void *buf, size_t offset, size_t size);
39 static void do_pread(void *buf, size_t offset, size_t size);
40 static void do_readv(void *buf, size_t offset, size_t size);
41 static void do_preadv(void *buf, size_t offset, size_t size);
42 
43 static const io_func write_funcs[] = {
44 	do_write,
45 	do_pwrite,
46 	do_writev,
47 	do_pwritev
48 };
49 
50 static const io_func read_funcs[] = {
51 	do_read,
52 	do_pread,
53 	do_readv,
54 	do_preadv
55 };
56 
fill_buffer(char * buf,size_t size)57 static size_t fill_buffer(char *buf, size_t size)
58 {
59 	size_t i, ret = MAX_VEC + 1 + rand() % (size - MAX_VEC);
60 
61 	/* Align buffer size to block size */
62 	if (directwr_flag || directrd_flag)
63 		ret = MAX(LTP_ALIGN(ret, blocksize), MAX_VEC * blocksize);
64 
65 	for (i = 0; i < ret; i++)
66 		buf[i] = rand();
67 
68 	return ret;
69 }
70 
vectorize_buffer(struct iovec * vec,size_t vec_size,char * buf,size_t buf_size,int align)71 static void vectorize_buffer(struct iovec *vec, size_t vec_size, char *buf,
72 	size_t buf_size, int align)
73 {
74 	size_t i, len, chunk = align ? blocksize : 1;
75 
76 	memset(vec, 0, vec_size * sizeof(struct iovec));
77 	buf_size /= chunk;
78 
79 	for (i = 0; buf_size && i < vec_size; i++) {
80 		len = 1 + rand() % (buf_size + i + 1 - vec_size);
81 		vec[i].iov_base = buf;
82 		vec[i].iov_len = len * chunk;
83 		buf += vec[i].iov_len;
84 		buf_size -= len;
85 	}
86 
87 	vec[vec_size - 1].iov_len += buf_size * chunk;
88 }
89 
update_filedata(const void * buf,size_t offset,size_t size)90 static void update_filedata(const void *buf, size_t offset, size_t size)
91 {
92 	memcpy(filedata + offset, buf, size * sizeof(char));
93 }
94 
do_write(void * buf,size_t offset,size_t size)95 static void do_write(void *buf, size_t offset, size_t size)
96 {
97 	SAFE_LSEEK(write_fd, offset, SEEK_SET);
98 	SAFE_WRITE(1, write_fd, buf, size);
99 }
100 
do_pwrite(void * buf,size_t offset,size_t size)101 static void do_pwrite(void *buf, size_t offset, size_t size)
102 {
103 	SAFE_PWRITE(1, write_fd, buf, size, offset);
104 }
105 
do_writev(void * buf,size_t offset,size_t size)106 static void do_writev(void *buf, size_t offset, size_t size)
107 {
108 	struct iovec vec[MAX_VEC] = {};
109 
110 	vectorize_buffer(vec, MAX_VEC, buf, size, !!directwr_flag);
111 	SAFE_LSEEK(write_fd, offset, SEEK_SET);
112 	SAFE_WRITEV(1, write_fd, vec, MAX_VEC);
113 }
114 
do_pwritev(void * buf,size_t offset,size_t size)115 static void do_pwritev(void *buf, size_t offset, size_t size)
116 {
117 	struct iovec vec[MAX_VEC] = {};
118 
119 	vectorize_buffer(vec, MAX_VEC, buf, size, !!directwr_flag);
120 	SAFE_PWRITEV(1, write_fd, vec, MAX_VEC, offset);
121 }
122 
do_read(void * buf,size_t offset,size_t size)123 static void do_read(void *buf, size_t offset, size_t size)
124 {
125 	SAFE_LSEEK(read_fd, offset, SEEK_SET);
126 	SAFE_READ(1, read_fd, buf, size);
127 }
128 
do_pread(void * buf,size_t offset,size_t size)129 static void do_pread(void *buf, size_t offset, size_t size)
130 {
131 	SAFE_PREAD(1, read_fd, buf, size, offset);
132 }
133 
do_readv(void * buf,size_t offset,size_t size)134 static void do_readv(void *buf, size_t offset, size_t size)
135 {
136 	struct iovec vec[MAX_VEC] = {};
137 
138 	vectorize_buffer(vec, MAX_VEC, buf, size, !!directrd_flag);
139 	SAFE_LSEEK(read_fd, offset, SEEK_SET);
140 	SAFE_READV(1, read_fd, vec, MAX_VEC);
141 }
142 
do_preadv(void * buf,size_t offset,size_t size)143 static void do_preadv(void *buf, size_t offset, size_t size)
144 {
145 	struct iovec vec[MAX_VEC] = {};
146 
147 	vectorize_buffer(vec, MAX_VEC, buf, size, !!directrd_flag);
148 	SAFE_PREADV(1, read_fd, vec, MAX_VEC, offset);
149 }
150 
open_testfile(int flags)151 static int open_testfile(int flags)
152 {
153 	if ((flags & O_WRONLY) && directwr_flag)
154 		flags |= O_DIRECT;
155 
156 	if ((flags & O_RDONLY) && directrd_flag)
157 		flags |= O_DIRECT;
158 
159 	return SAFE_OPEN(TEST_FILENAME, flags, 0644);
160 }
161 
setup(void)162 static void setup(void)
163 {
164 	struct statvfs statbuf;
165 	size_t pagesize;
166 
167 	srand(time(0));
168 	pagesize = SAFE_SYSCONF(_SC_PAGESIZE);
169 
170 	if (workdir_arg)
171 		SAFE_CHDIR(workdir_arg);
172 
173 	if (tst_parse_int(loop_arg, &loop_count, 0, INT_MAX))
174 		tst_brk(TBROK, "Invalid write loop count: %s", loop_arg);
175 
176 	write_fd = open_testfile(O_WRONLY | O_CREAT | O_TRUNC);
177 	read_fd = open_testfile(O_RDONLY);
178 	TEST(fstatvfs(write_fd, &statbuf));
179 
180 	if (TST_RET == -1)
181 		tst_brk(TBROK | TTERRNO, "fstatvfs() failed");
182 	else if (TST_RET)
183 		tst_brk(TBROK | TTERRNO, "Invalid fstatvfs() return value");
184 
185 	blocksize = statbuf.f_bsize;
186 	tst_res(TINFO, "Block size: %zu", blocksize);
187 	bufsize = 4 * MAX_VEC * MAX(pagesize, blocksize);
188 	filesize = 1024 * MAX(pagesize, blocksize);
189 	writebuf = SAFE_MMAP(NULL, bufsize, PROT_READ | PROT_WRITE,
190 		MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
191 	filedata = SAFE_MALLOC(filesize);
192 
193 	tst_set_timeout(bufsize * loop_count / (8 * 1024 * 1024));
194 }
195 
run(void)196 static void run(void)
197 {
198 	size_t start, length;
199 	int i, f, fails = 0;
200 
201 	/* Test data consistency between random writes */
202 	for (i = 0; i < loop_count; i++) {
203 		length = fill_buffer(writebuf, bufsize);
204 		start = rand() % (filesize + 1 - length);
205 
206 		/* Align offset to blocksize if needed */
207 		if (directrd_flag || directwr_flag)
208 			start = (start + blocksize / 2) & ~(blocksize - 1);
209 
210 		update_filedata(writebuf, start, length);
211 		f = rand() % ARRAY_SIZE(write_funcs);
212 		write_funcs[f](writebuf, start, length);
213 
214 		memset(writebuf, 0, length);
215 		f = rand() % ARRAY_SIZE(read_funcs);
216 		read_funcs[f](writebuf, start, length);
217 
218 		if (memcmp(writebuf, filedata + start, length)) {
219 			tst_res(TFAIL, "Partial data mismatch at [%zu:%zu]",
220 				start, start + length);
221 			fails++;
222 		}
223 	}
224 
225 	if (!fails)
226 		tst_res(TPASS, "Partial data are consistent");
227 
228 	/* Ensure that the testfile has the expected size */
229 	do_write(writebuf, filesize - blocksize, blocksize);
230 	update_filedata(writebuf, filesize - blocksize, blocksize);
231 
232 	/* Sync the testfile and clear cache */
233 	SAFE_CLOSE(read_fd);
234 	SAFE_FSYNC(write_fd);
235 	SAFE_FILE_PRINTF("/proc/sys/vm/drop_caches", "1");
236 	read_fd = open_testfile(O_RDONLY);
237 
238 	/* Check final file contents */
239 	for (start = 0; start < filesize; start += bufsize) {
240 		length = MIN(bufsize, filesize - start);
241 		SAFE_READ(1, read_fd, writebuf, length);
242 
243 		if (memcmp(writebuf, filedata + start, length)) {
244 			tst_res(TFAIL, "Final data mismatch at [%zu:%zu]",
245 				start, start + length);
246 			return;
247 		}
248 	}
249 
250 	tst_res(TPASS, "Final data are consistent");
251 }
252 
cleanup(void)253 static void cleanup(void)
254 {
255 	SAFE_MUNMAP(writebuf, bufsize);
256 	free(filedata);
257 
258 	if (read_fd >= 0)
259 		SAFE_CLOSE(read_fd);
260 
261 	if (write_fd >= 0)
262 		SAFE_CLOSE(write_fd);
263 
264 	SAFE_UNLINK(TEST_FILENAME);
265 }
266 
267 static struct tst_test test = {
268 	.test_all = run,
269 	.setup = setup,
270 	.cleanup = cleanup,
271 	.needs_tmpdir = 1,
272 	.options = (struct tst_option[]) {
273 		{"c:", &loop_arg, "Number of write loops (default: 4096)"},
274 		{"d:", &workdir_arg, "Path to working directory"},
275 		{"W", &directwr_flag, "Use direct I/O for writing"},
276 		{"R", &directrd_flag, "Use direct I/O for reading"},
277 		{}
278 	}
279 };
280