1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2019 Cyril Hrubis <chrubis@suse.cz>
4 */
5
6 /*
7 * This is a basic functional test for RWF_NOWAIT flag, we are attempting to
8 * force preadv2() either to return a short read or EAGAIN with three
9 * concurently running threads:
10 *
11 * nowait_reader: reads from a random offset from a random file with
12 * RWF_NOWAIT flag and expects to get EAGAIN and short
13 * read sooner or later
14 *
15 * writer_thread: rewrites random file in order to keep the underlying device
16 * busy so that pages evicted from cache cannot be faulted
17 * immediately
18 *
19 * cache_dropper: attempts to evict pages from a cache in order for reader to
20 * hit evicted page sooner or later
21 */
22
23 /*
24 * If test fails with EOPNOTSUPP you have likely hit a glibc bug:
25 *
26 * https://sourceware.org/bugzilla/show_bug.cgi?id=23579
27 *
28 * Which can be worked around by calling preadv2() directly by syscall() such as:
29 *
30 * static ssize_t sys_preadv2(int fd, const struct iovec *iov, int iovcnt,
31 * off_t offset, int flags)
32 * {
33 * return syscall(SYS_preadv2, fd, iov, iovcnt, offset, offset>>32, flags);
34 * }
35 *
36 */
37
38 #define _GNU_SOURCE
39 #include <string.h>
40 #include <sys/uio.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <ctype.h>
44 #include <pthread.h>
45
46 #include "tst_test.h"
47 #include "tst_safe_pthread.h"
48 #include "lapi/preadv2.h"
49
50 #define CHUNK_SZ 4123
51 #define CHUNKS 60
52 #define MNTPOINT "mntpoint"
53 #define FILES 500
54
55 static int fds[FILES];
56
57 static volatile int stop;
58
drop_caches(void)59 static void drop_caches(void)
60 {
61 SAFE_FILE_PRINTF("/proc/sys/vm/drop_caches", "3");
62 }
63
64 /*
65 * All files are divided in chunks each filled with the same bytes starting with
66 * '0' at offset 0 and with increasing value on each next chunk.
67 *
68 * 000....000111....111.......AAA......AAA...
69 * | chunk0 || chunk1 | ... | chunk17 |
70 */
verify_short_read(struct iovec * iov,size_t iov_cnt,off_t off,size_t size)71 static int verify_short_read(struct iovec *iov, size_t iov_cnt,
72 off_t off, size_t size)
73 {
74 unsigned int i;
75 size_t j, checked = 0;
76
77 for (i = 0; i < iov_cnt; i++) {
78 char *buf = iov[i].iov_base;
79 for (j = 0; j < iov[i].iov_len; j++) {
80 char exp_val = '0' + (off + checked)/CHUNK_SZ;
81
82 if (exp_val != buf[j]) {
83 tst_res(TFAIL,
84 "Wrong value read pos %zu size %zu %c (%i) %c (%i)!",
85 checked, size, exp_val, exp_val,
86 isprint(buf[j]) ? buf[j] : ' ', buf[j]);
87 return 1;
88 }
89
90 if (++checked >= size)
91 return 0;
92 }
93 }
94
95 return 0;
96 }
97
nowait_reader(void * unused LTP_ATTRIBUTE_UNUSED)98 static void *nowait_reader(void *unused LTP_ATTRIBUTE_UNUSED)
99 {
100 char buf1[CHUNK_SZ/2];
101 char buf2[CHUNK_SZ];
102 unsigned int full_read_cnt = 0, eagain_cnt = 0;
103 unsigned int short_read_cnt = 0, zero_read_cnt = 0;
104
105 struct iovec rd_iovec[] = {
106 {buf1, sizeof(buf1)},
107 {buf2, sizeof(buf2)},
108 };
109
110 while (!stop) {
111 if (eagain_cnt >= 100 && short_read_cnt >= 10)
112 stop = 1;
113
114 /* Ensure short reads doesn't happen because of tripping on EOF */
115 off_t off = random() % ((CHUNKS - 2) * CHUNK_SZ);
116 int fd = fds[random() % FILES];
117
118 TEST(preadv2(fd, rd_iovec, 2, off, RWF_NOWAIT));
119
120 if (TST_RET < 0) {
121 if (TST_ERR != EAGAIN)
122 tst_brk(TBROK | TTERRNO, "preadv2() failed");
123
124 eagain_cnt++;
125 continue;
126 }
127
128
129 if (TST_RET == 0) {
130 zero_read_cnt++;
131 continue;
132 }
133
134 if (TST_RET != CHUNK_SZ + CHUNK_SZ/2) {
135 verify_short_read(rd_iovec, 2, off, TST_RET);
136 short_read_cnt++;
137 continue;
138 }
139
140 full_read_cnt++;
141 }
142
143 tst_res(TINFO,
144 "Number of full_reads %u, short reads %u, zero len reads %u, EAGAIN(s) %u",
145 full_read_cnt, short_read_cnt, zero_read_cnt, eagain_cnt);
146
147 return (void*)(long)eagain_cnt;
148 }
149
writer_thread(void * unused)150 static void *writer_thread(void *unused)
151 {
152 char buf[CHUNK_SZ];
153 unsigned int j, write_cnt = 0;
154
155 struct iovec wr_iovec[] = {
156 {buf, sizeof(buf)},
157 };
158
159 while (!stop) {
160 int fd = fds[random() % FILES];
161
162 for (j = 0; j < CHUNKS; j++) {
163 memset(buf, '0' + j, sizeof(buf));
164
165 off_t off = CHUNK_SZ * j;
166
167 if (pwritev(fd, wr_iovec, 1, off) < 0) {
168 if (errno == EBADF) {
169 tst_res(TINFO | TERRNO, "FDs closed, exiting...");
170 return unused;
171 }
172
173 tst_brk(TBROK | TERRNO, "pwritev()");
174 }
175
176 write_cnt++;
177 }
178 }
179
180 tst_res(TINFO, "Number of writes %u", write_cnt);
181
182 return unused;
183 }
184
cache_dropper(void * unused)185 static void *cache_dropper(void *unused)
186 {
187 unsigned int drop_cnt = 0;
188
189 while (!stop) {
190 drop_caches();
191 drop_cnt++;
192 }
193
194 tst_res(TINFO, "Cache dropped %u times", drop_cnt);
195
196 return unused;
197 }
198
verify_preadv2(void)199 static void verify_preadv2(void)
200 {
201 pthread_t reader, dropper, writer;
202 unsigned int max_runtime = 600;
203 void *eagains;
204
205 stop = 0;
206
207 drop_caches();
208
209 SAFE_PTHREAD_CREATE(&dropper, NULL, cache_dropper, NULL);
210 SAFE_PTHREAD_CREATE(&reader, NULL, nowait_reader, NULL);
211 SAFE_PTHREAD_CREATE(&writer, NULL, writer_thread, NULL);
212
213 while (!stop && max_runtime-- > 0)
214 usleep(100000);
215
216 stop = 1;
217
218 SAFE_PTHREAD_JOIN(reader, &eagains);
219 SAFE_PTHREAD_JOIN(dropper, NULL);
220 SAFE_PTHREAD_JOIN(writer, NULL);
221
222 if (eagains)
223 tst_res(TPASS, "Got some EAGAIN");
224 else
225 tst_res(TFAIL, "Haven't got EAGAIN");
226 }
227
check_preadv2_nowait(int fd)228 static void check_preadv2_nowait(int fd)
229 {
230 char buf[1];
231 struct iovec iovec[] = {
232 {buf, sizeof(buf)},
233 };
234
235 TEST(preadv2(fd, iovec, 1, 0, RWF_NOWAIT));
236
237 if (TST_ERR == EOPNOTSUPP)
238 tst_brk(TCONF | TERRNO, "preadv2()");
239 }
240
setup(void)241 static void setup(void)
242 {
243 char path[1024];
244 char buf[CHUNK_SZ];
245 unsigned int i;
246 char j;
247
248 for (i = 0; i < FILES; i++) {
249 snprintf(path, sizeof(path), MNTPOINT"/file_%i", i);
250
251 fds[i] = SAFE_OPEN(path, O_RDWR | O_CREAT, 0644);
252
253 if (i == 0)
254 check_preadv2_nowait(fds[i]);
255
256 for (j = 0; j < CHUNKS; j++) {
257 memset(buf, '0' + j, sizeof(buf));
258 SAFE_WRITE(1, fds[i], buf, sizeof(buf));
259 }
260 }
261 }
262
do_cleanup(void)263 static void do_cleanup(void)
264 {
265 unsigned int i;
266
267 for (i = 0; i < FILES; i++) {
268 if (fds[i] > 0)
269 SAFE_CLOSE(fds[i]);
270 }
271 }
272
273 TST_DECLARE_ONCE_FN(cleanup, do_cleanup);
274
275 static struct tst_test test = {
276 .setup = setup,
277 .cleanup = cleanup,
278 .test_all = verify_preadv2,
279 .mntpoint = MNTPOINT,
280 .mount_device = 1,
281 .all_filesystems = 1,
282 .needs_root = 1,
283 };
284