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