| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2019 Cyril Hrubis <chrubis@suse.cz> |
| */ |
| |
| /* |
| * This is a basic functional test for RWF_NOWAIT flag, we are attempting to |
| * force preadv2() either to return a short read or EAGAIN with three |
| * concurently running threads: |
| * |
| * nowait_reader: reads from a random offset from a random file with |
| * RWF_NOWAIT flag and expects to get EAGAIN and short |
| * read sooner or later |
| * |
| * writer_thread: rewrites random file in order to keep the underlying device |
| * busy so that pages evicted from cache cannot be faulted |
| * immediately |
| * |
| * cache_dropper: attempts to evict pages from a cache in order for reader to |
| * hit evicted page sooner or later |
| */ |
| |
| /* |
| * If test fails with EOPNOTSUPP you have likely hit a glibc bug: |
| * |
| * https://sourceware.org/bugzilla/show_bug.cgi?id=23579 |
| * |
| * Which can be worked around by calling preadv2() directly by syscall() such as: |
| * |
| * static ssize_t sys_preadv2(int fd, const struct iovec *iov, int iovcnt, |
| * off_t offset, int flags) |
| * { |
| * return syscall(SYS_preadv2, fd, iov, iovcnt, offset, offset>>32, flags); |
| * } |
| * |
| */ |
| |
| #define _GNU_SOURCE |
| #include <string.h> |
| #include <sys/uio.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <pthread.h> |
| |
| #include "tst_test.h" |
| #include "tst_safe_pthread.h" |
| #include "lapi/preadv2.h" |
| |
| #define CHUNK_SZ 4123 |
| #define CHUNKS 60 |
| #define MNTPOINT "mntpoint" |
| #define FILES 500 |
| |
| static int fds[FILES]; |
| |
| static volatile int stop; |
| |
| static void drop_caches(void) |
| { |
| SAFE_FILE_PRINTF("/proc/sys/vm/drop_caches", "3"); |
| } |
| |
| /* |
| * All files are divided in chunks each filled with the same bytes starting with |
| * '0' at offset 0 and with increasing value on each next chunk. |
| * |
| * 000....000111....111.......AAA......AAA... |
| * | chunk0 || chunk1 | ... | chunk17 | |
| */ |
| static int verify_short_read(struct iovec *iov, size_t iov_cnt, |
| off_t off, size_t size) |
| { |
| unsigned int i; |
| size_t j, checked = 0; |
| |
| for (i = 0; i < iov_cnt; i++) { |
| char *buf = iov[i].iov_base; |
| for (j = 0; j < iov[i].iov_len; j++) { |
| char exp_val = '0' + (off + checked)/CHUNK_SZ; |
| |
| if (exp_val != buf[j]) { |
| tst_res(TFAIL, |
| "Wrong value read pos %zu size %zu %c (%i) %c (%i)!", |
| checked, size, exp_val, exp_val, |
| isprint(buf[j]) ? buf[j] : ' ', buf[j]); |
| return 1; |
| } |
| |
| if (++checked >= size) |
| return 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void *nowait_reader(void *unused LTP_ATTRIBUTE_UNUSED) |
| { |
| char buf1[CHUNK_SZ/2]; |
| char buf2[CHUNK_SZ]; |
| unsigned int full_read_cnt = 0, eagain_cnt = 0; |
| unsigned int short_read_cnt = 0, zero_read_cnt = 0; |
| |
| struct iovec rd_iovec[] = { |
| {buf1, sizeof(buf1)}, |
| {buf2, sizeof(buf2)}, |
| }; |
| |
| while (!stop) { |
| if (eagain_cnt >= 100 && short_read_cnt >= 10) |
| stop = 1; |
| |
| /* Ensure short reads doesn't happen because of tripping on EOF */ |
| off_t off = random() % ((CHUNKS - 2) * CHUNK_SZ); |
| int fd = fds[random() % FILES]; |
| |
| TEST(preadv2(fd, rd_iovec, 2, off, RWF_NOWAIT)); |
| |
| if (TST_RET < 0) { |
| if (TST_ERR != EAGAIN) |
| tst_brk(TBROK | TTERRNO, "preadv2() failed"); |
| |
| eagain_cnt++; |
| continue; |
| } |
| |
| |
| if (TST_RET == 0) { |
| zero_read_cnt++; |
| continue; |
| } |
| |
| if (TST_RET != CHUNK_SZ + CHUNK_SZ/2) { |
| verify_short_read(rd_iovec, 2, off, TST_RET); |
| short_read_cnt++; |
| continue; |
| } |
| |
| full_read_cnt++; |
| } |
| |
| tst_res(TINFO, |
| "Number of full_reads %u, short reads %u, zero len reads %u, EAGAIN(s) %u", |
| full_read_cnt, short_read_cnt, zero_read_cnt, eagain_cnt); |
| |
| return (void*)(long)eagain_cnt; |
| } |
| |
| static void *writer_thread(void *unused) |
| { |
| char buf[CHUNK_SZ]; |
| unsigned int j, write_cnt = 0; |
| |
| struct iovec wr_iovec[] = { |
| {buf, sizeof(buf)}, |
| }; |
| |
| while (!stop) { |
| int fd = fds[random() % FILES]; |
| |
| for (j = 0; j < CHUNKS; j++) { |
| memset(buf, '0' + j, sizeof(buf)); |
| |
| off_t off = CHUNK_SZ * j; |
| |
| if (pwritev(fd, wr_iovec, 1, off) < 0) { |
| if (errno == EBADF) { |
| tst_res(TINFO | TERRNO, "FDs closed, exiting..."); |
| return unused; |
| } |
| |
| tst_brk(TBROK | TERRNO, "pwritev()"); |
| } |
| |
| write_cnt++; |
| } |
| } |
| |
| tst_res(TINFO, "Number of writes %u", write_cnt); |
| |
| return unused; |
| } |
| |
| static void *cache_dropper(void *unused) |
| { |
| unsigned int drop_cnt = 0; |
| |
| while (!stop) { |
| drop_caches(); |
| drop_cnt++; |
| } |
| |
| tst_res(TINFO, "Cache dropped %u times", drop_cnt); |
| |
| return unused; |
| } |
| |
| static void verify_preadv2(void) |
| { |
| pthread_t reader, dropper, writer; |
| unsigned int max_runtime = 600; |
| void *eagains; |
| |
| stop = 0; |
| |
| drop_caches(); |
| |
| SAFE_PTHREAD_CREATE(&dropper, NULL, cache_dropper, NULL); |
| SAFE_PTHREAD_CREATE(&reader, NULL, nowait_reader, NULL); |
| SAFE_PTHREAD_CREATE(&writer, NULL, writer_thread, NULL); |
| |
| while (!stop && max_runtime-- > 0) |
| usleep(100000); |
| |
| stop = 1; |
| |
| SAFE_PTHREAD_JOIN(reader, &eagains); |
| SAFE_PTHREAD_JOIN(dropper, NULL); |
| SAFE_PTHREAD_JOIN(writer, NULL); |
| |
| if (eagains) |
| tst_res(TPASS, "Got some EAGAIN"); |
| else |
| tst_res(TFAIL, "Haven't got EAGAIN"); |
| } |
| |
| static void check_preadv2_nowait(int fd) |
| { |
| char buf[1]; |
| struct iovec iovec[] = { |
| {buf, sizeof(buf)}, |
| }; |
| |
| TEST(preadv2(fd, iovec, 1, 0, RWF_NOWAIT)); |
| |
| if (TST_ERR == EOPNOTSUPP) |
| tst_brk(TCONF | TERRNO, "preadv2()"); |
| } |
| |
| static void setup(void) |
| { |
| char path[1024]; |
| char buf[CHUNK_SZ]; |
| unsigned int i; |
| char j; |
| |
| for (i = 0; i < FILES; i++) { |
| snprintf(path, sizeof(path), MNTPOINT"/file_%i", i); |
| |
| fds[i] = SAFE_OPEN(path, O_RDWR | O_CREAT, 0644); |
| |
| if (i == 0) |
| check_preadv2_nowait(fds[i]); |
| |
| for (j = 0; j < CHUNKS; j++) { |
| memset(buf, '0' + j, sizeof(buf)); |
| SAFE_WRITE(1, fds[i], buf, sizeof(buf)); |
| } |
| } |
| } |
| |
| static void do_cleanup(void) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < FILES; i++) { |
| if (fds[i] > 0) |
| SAFE_CLOSE(fds[i]); |
| } |
| } |
| |
| TST_DECLARE_ONCE_FN(cleanup, do_cleanup); |
| |
| static struct tst_test test = { |
| .setup = setup, |
| .cleanup = cleanup, |
| .test_all = verify_preadv2, |
| .mntpoint = MNTPOINT, |
| .mount_device = 1, |
| .all_filesystems = 1, |
| .needs_root = 1, |
| }; |