| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2017 Red Hat, Inc. All Rights reserved. |
| * |
| * Directly AIO re-write a file with different content again and again. |
| * And check the data integrity. |
| */ |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| |
| #include <libaio.h> |
| |
| unsigned long buf_size = 0; |
| unsigned long loop_count = 0; |
| void *test_buf[2]; |
| void *cmp_buf; |
| #define IO_PATTERN1 0x55 |
| #define IO_PATTERN2 0xaa |
| |
| void usage(char *progname) |
| { |
| fprintf(stderr, "usage: %s [-c loop_count] [-b bufsize] filename\n" |
| "\t-c loopcount: specify how many times to test" |
| "\t-b bufsize: keep writing from offset 0 to this size", |
| progname); |
| exit(1); |
| } |
| |
| void dump_buffer(void *buf, off64_t offset, ssize_t len) |
| { |
| int i, j; |
| char *p; |
| int new; |
| |
| for (i = 0, p = (char *)buf; i < len; i += 16) { |
| char *s = p; |
| |
| if (i && !memcmp(p, p - 16, 16)) { |
| new = 0; |
| } else { |
| if (i) |
| fprintf(stderr, "*\n"); |
| new = 1; |
| } |
| |
| if (!new) { |
| p += 16; |
| continue; |
| } |
| |
| fprintf(stderr, "%08llx ", (unsigned long long)offset + i); |
| for (j = 0; j < 16 && i + j < len; j++, p++) |
| fprintf(stderr, "%02x ", *p); |
| fprintf(stderr, " "); |
| for (j = 0; j < 16 && i + j < len; j++, s++) { |
| if (isalnum((int)*s)) |
| fprintf(stderr, "%c", *s); |
| else |
| fprintf(stderr, "."); |
| } |
| fprintf(stderr, "\n"); |
| } |
| fprintf(stderr, "%08llx\n", (unsigned long long)offset + i); |
| } |
| |
| int init_test(char *filename) |
| { |
| int fd; |
| int err = 0; |
| |
| fd = open(filename, O_DIRECT | O_CREAT | O_RDWR, 0600); |
| if (fd == -1) { |
| perror("open"); |
| exit(1); |
| } |
| |
| ftruncate(fd, buf_size); |
| |
| /* fill test_buf[0] with IO_PATTERN1 */ |
| err = posix_memalign(&(test_buf[0]), getpagesize(), buf_size); |
| if (err) { |
| fprintf(stderr, "error %s during posix_memalign\n", |
| strerror(err)); |
| exit(1); |
| } |
| memset(test_buf[0], IO_PATTERN1, buf_size); |
| |
| /* fill test_buf[1] with IO_PATTERN2 */ |
| err = posix_memalign(&(test_buf[1]), getpagesize(), buf_size); |
| if (err) { |
| fprintf(stderr, "error %s during posix_memalign\n", |
| strerror(err)); |
| exit(1); |
| } |
| memset(test_buf[1], IO_PATTERN2, buf_size); |
| |
| /* fill test file with IO_PATTERN1 */ |
| if (pwrite(fd, test_buf[0], buf_size, 0) != buf_size) { |
| perror("pwrite"); |
| exit(1); |
| } |
| |
| /* cmp_buf is used to compare with test_buf */ |
| err = posix_memalign(&cmp_buf, getpagesize(), buf_size); |
| if (err) { |
| fprintf(stderr, "error %s during posix_memalign\n", |
| strerror(err)); |
| exit(1); |
| } |
| memset(cmp_buf, 0, buf_size); |
| |
| fsync(fd); |
| close(fd); |
| |
| return 0; |
| } |
| |
| /* |
| * Read file content back, then compare with the 'buf' which |
| * point to the original buffer was written to file. |
| */ |
| int file_check(char *filename, void *buf) |
| { |
| int fd; |
| int rc = 0; |
| |
| /* check cached data with buffer read */ |
| fd = open(filename, O_RDONLY, 0600); |
| if (fd == -1) { |
| perror("open"); |
| exit(1); |
| } |
| |
| if (pread(fd, cmp_buf, buf_size, 0) != buf_size) { |
| perror("pread"); |
| exit(1); |
| } |
| |
| if (memcmp(buf, cmp_buf, buf_size)) { |
| fprintf(stderr, "get stale data from buffer read\n"); |
| dump_buffer(cmp_buf, 0, buf_size); |
| rc++; |
| } |
| close(fd); |
| |
| /* |
| * check on-disk data with direct-IO read |
| */ |
| fd = open(filename, O_DIRECT|O_RDONLY, 0600); |
| if (fd < 0) { |
| perror("open with direct-IO"); |
| exit(1); |
| } |
| |
| if (pread(fd, cmp_buf, buf_size, 0) != buf_size) { |
| perror("pread"); |
| exit(1); |
| } |
| |
| if (memcmp(buf, cmp_buf, buf_size)) { |
| fprintf(stderr, "get stale data from DIO read\n"); |
| dump_buffer(cmp_buf, 0, buf_size); |
| rc++; |
| } |
| |
| close(fd); |
| return rc; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct io_context *ctx = NULL; |
| struct io_event evs[1]; |
| struct iocb iocb1; |
| struct iocb *iocbs[] = { &iocb1 }; |
| int fd, err = 0; |
| int i; |
| int c; |
| char *filename = NULL; |
| |
| while ((c = getopt(argc, argv, "c:b:")) != -1) { |
| char *endp; |
| |
| switch (c) { |
| case 'c': /* the number of testing cycles */ |
| loop_count = strtol(optarg, &endp, 0); |
| break; |
| case 'b': /* buffer size */ |
| buf_size = strtol(optarg, &endp, 0); |
| break; |
| default: |
| usage(argv[0]); |
| } |
| } |
| |
| if (loop_count == 0) |
| loop_count = 1600; |
| if (buf_size == 0) /* default minimum buffer size is 65536 bytes */ |
| buf_size = 65536; |
| |
| if (optind == argc - 1) |
| filename = argv[optind]; |
| else |
| usage(argv[0]); |
| |
| init_test(filename); |
| |
| err = io_setup(1, &ctx); |
| if (err) { |
| fprintf(stderr, "error %s during io_setup\n", |
| strerror(err)); |
| return 1; |
| } |
| |
| i = 0; |
| /* |
| * Keep running until loop_count times, fill the file with IO_PATTERN1 |
| * or IO_PATTERN2 one by one, then read the file data back to check if |
| * there's stale data. |
| */ |
| while (loop_count--) { |
| i++; |
| i %= 2; |
| fd = open(filename, O_DIRECT | O_CREAT | O_RDWR, 0600); |
| if (fd == -1) { |
| perror("open"); |
| return 1; |
| } |
| |
| io_prep_pwrite(&iocb1, fd, test_buf[i], buf_size, 0); |
| err = io_submit(ctx, 1, iocbs); |
| if (err != 1) { |
| fprintf(stderr, "error %s during io_submit\n", |
| strerror(err)); |
| return 1; |
| } |
| err = io_getevents(ctx, 1, 1, evs, NULL); |
| if (err != 1) { |
| fprintf(stderr, "error %s during io_getevents\n", |
| strerror(err)); |
| return 1; |
| } |
| close(fd); |
| |
| err = file_check(filename, test_buf[i]); |
| if (err != 0) |
| break; |
| } |
| |
| return 0; |
| } |