// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2021 SUSE LLC Andrea Cervesato */ /*\ * [Description] * * Create a file using buffered writes while other processes are doing * O_DIRECT reads and check if the buffer reads always see zero. */ #define _GNU_SOURCE #include #include #include #include #include #include "tst_test.h" #include "common.h" static char *str_numchildren; static char *str_writesize; static char *str_readsize; static char *str_filesize; static char *filename = "file.bin"; static int numchildren = 8; static long long writesize = 32 * 1024 * 1024; static long long readsize = 32 * 1024 * 1024; static long long filesize = 128 * 1024 * 1024; static int *children_completed; static char *iobuf; static int fd; static void do_buffered_writes(int fd, char *bufptr, long long fsize, long long wsize, int pattern) { long long offset; long long w; memset(bufptr, pattern, wsize); tst_res(TINFO, "child %i writing file", getpid()); for (offset = 0; offset + wsize <= fsize; offset += wsize) { w = pwrite(fd, bufptr, wsize, offset); if (w < 0) tst_brk(TBROK, "pwrite: %s", tst_strerrno(-w)); if (w != wsize) tst_brk(TBROK, "pwrite: wrote %lld bytes out of %lld", w, wsize); SAFE_FSYNC(fd); if (!tst_remaining_runtime()) return; } } static int do_direct_reads(char *filename, char *bufptr, long long fsize, long long rsize) { int fd; long long offset; long long w; int fail = 0; int iter = 1; fd = SAFE_OPEN(filename, O_RDONLY | O_DIRECT, 0666); while (1) { for (offset = 0; offset + rsize < fsize; offset += rsize) { char *bufoff; if (*children_completed >= numchildren) { tst_res(TINFO, "Writers finshed, exiting reader (iteration %i)", iter); goto exit; } if (!tst_remaining_runtime()) { tst_res(TINFO, "Test out of runtime, exiting"); goto exit; } w = pread(fd, bufptr, rsize, offset); if (w < 0) tst_brk(TBROK, "pread: %s", tst_strerrno(-w)); if (w != rsize) tst_brk(TBROK, "pread: read %lld bytes out of %lld", w, rsize); bufoff = check_zero(bufptr, rsize); if (bufoff) { fail = 1; goto exit; } iter++; } } exit: SAFE_CLOSE(fd); return fail; } static void setup(void) { struct stat sb; long long buffsize; long long alignment; if (tst_parse_int(str_numchildren, &numchildren, 1, INT_MAX)) tst_brk(TBROK, "Invalid number of children '%s'", str_numchildren); if (tst_parse_filesize(str_filesize, &filesize, 1, LLONG_MAX)) tst_brk(TBROK, "Invalid file size '%s'", str_filesize); if (tst_parse_filesize(str_writesize, &writesize, 1, filesize)) tst_brk(TBROK, "Invalid write blocks size '%s'", str_writesize); if (tst_parse_filesize(str_readsize, &readsize, 1, filesize)) tst_brk(TBROK, "Invalid read blocks size '%s'", str_readsize); SAFE_STAT(".", &sb); alignment = sb.st_blksize; buffsize = readsize > writesize ? readsize : writesize; iobuf = SAFE_MEMALIGN(alignment, buffsize); children_completed = SAFE_MMAP(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); fd = SAFE_OPEN(filename, O_CREAT | O_TRUNC | O_RDWR, 0666); } static void cleanup(void) { SAFE_CLOSE(fd); } static void run(void) { int i; int fail; // Fill the file with a known pattern so that the blocks // on disk can be detected if they become exposed do_buffered_writes(fd, iobuf, filesize, writesize, 1); SAFE_FSYNC(fd); SAFE_FTRUNCATE(fd, 0); SAFE_FSYNC(fd); SAFE_FTRUNCATE(fd, filesize); *children_completed = 0; for (i = 0; i < numchildren; i++) { if (!SAFE_FORK()) { do_buffered_writes(fd, iobuf, filesize, writesize, 0); tst_atomic_add_return(1, children_completed); return; } } fail = do_direct_reads(filename, iobuf, filesize, readsize); if (fail) tst_res(TFAIL, "Non zero bytes read"); else tst_res(TPASS, "All bytes read were zeroed"); } static struct tst_test test = { .test_all = run, .setup = setup, .cleanup = cleanup, .needs_tmpdir = 1, .forks_child = 1, .max_runtime = 1800, .options = (struct tst_option[]) { {"n:", &str_numchildren, "Number of threads (default 8)"}, {"w:", &str_writesize, "Size of writing blocks (default 32M)"}, {"r:", &str_readsize, "Size of reading blocks (default 32M)"}, {"s:", &str_filesize, "File size (default 128M)"}, {} }, .skip_filesystems = (const char *[]) { "tmpfs", NULL }, };