[PATCH] generic: unaligned direct AIO write test

[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]



A simply reproducer from Frank Sorenson:

  ftruncate(fd, 65012224)
  io_prep_pwrite(iocbs[0], fd, buf[0], 1048576, 63963648);
  io_prep_pwrite(iocbs[1], fd, buf[1], 1048576, 65012224);

  io_prep_pwrite(iocbs[2], fd, buf[2], 1048576, 66060800);
  io_prep_pwrite(iocbs[3], fd, buf[3], 1048576, 67109376);

  io_submit(io_ctx, 1, &iocbs[0]);
  io_submit(io_ctx, 1, &iocbs[1]);
  io_submit(io_ctx, 1, &iocbs[2]);
  io_submit(io_ctx, 1, &iocbs[3]);

  io_getevents(io_ctx, 4, 4, events, NULL)

help to find an ext4 corruption:
           **************** **************** ****************
           *    page 1    * *    page 2    * *    page 3    *
           **************** **************** ****************
  existing 0000000000000000 0000000000000000 0000000000000000
  write 1    AAAAAAAAAAAAAA AA
  write 2                     BBBBBBBBBBBBBB BB

  result   00AAAAAAAAAAAAAA 00BBBBBBBBBBBBBB BB00000000000000
  desired  00AAAAAAAAAAAAAA AABBBBBBBBBBBBBB BB00000000000000

This issue remind us we might miss unaligned AIO test for long time.
We thought fsx cover this part, but looks like it's not. So this case
trys to cover unaligned direct AIO write test on file with different
initial truncate i_size.

Signed-off-by: Zorro Lang <zlang@xxxxxxxxxx>
---

Hi,

The aio-dio-write-verify.c nearly copy from aio-dio-eof-race.c, then change
a few logic. I've changed aio-dio-eof-race.c several times, to fit different
new AIO test cases. But this time I need to truncate the file, then it won't
write at EOF. This against the motive of aio-dio-eof-race.c. So I think it's
time to shift a new program to do more AIO write test.

It's only my personal opinion :)

Thanks,
Zorro

 src/aio-dio-regress/aio-dio-write-verify.c | 223 +++++++++++++++++++++
 tests/generic/999                          |  71 +++++++
 tests/generic/999.out                      |   2 +
 tests/generic/group                        |   1 +
 4 files changed, 297 insertions(+)
 create mode 100644 src/aio-dio-regress/aio-dio-write-verify.c
 create mode 100755 tests/generic/999
 create mode 100644 tests/generic/999.out

diff --git a/src/aio-dio-regress/aio-dio-write-verify.c b/src/aio-dio-regress/aio-dio-write-verify.c
new file mode 100644
index 00000000..402ddcdb
--- /dev/null
+++ b/src/aio-dio-regress/aio-dio-write-verify.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Red Hat, Inc. All Rights reserved.
+ *
+ * AIO writes from a start offset on a truncated file, verify there's not
+ * data corruption.
+ */
+#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 size_KB  = 0;
+#define IO_PATTERN	0x5a
+
+void
+usage(char *progname)
+{
+	fprintf(stderr, "usage: %s [-s datasize] [-b bufsize] [-o startoff] [-t truncsize] filename\n"
+	        "\t-s datasize: specify the minimum data size(KB), doesn't count holes\n"
+	        "\t-b bufsize: buffer size\n"
+	        "\t-o startoff: start offset to write data, 0 by default\n"
+	        "\t-t truncsize: truncate the file to a special size at first 0 by default\n",
+	        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)
+				printf("*\n");
+			new = 1;
+		}
+
+		if (!new) {
+			p += 16;
+			continue;
+		}
+
+		printf("%08llx  ", (unsigned long long)offset + i);
+		for (j = 0; j < 16 && i + j < len; j++, p++)
+			printf("%02x ", *p);
+		printf(" ");
+		for (j = 0; j < 16 && i + j < len; j++, s++) {
+			if (isalnum((int)*s))
+				printf("%c", *s);
+			else
+				printf(".");
+		}
+		printf("\n");
+
+	}
+	printf("%08llx\n", (unsigned long long)offset + i);
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_context *ctx = NULL;
+	struct io_event evs[2];
+	struct iocb iocb1, iocb2;
+	struct iocb *iocbs[] = { &iocb1, &iocb2 };
+	void *buf;
+	int fd, err = 0;
+	off_t bytes;
+	int c;
+	char *cmp_buf = NULL;
+	char *filename = NULL;
+	/* start offset to write */
+	long long startoff = 0;
+	/* truncate size */
+	off_t tsize = 0;
+
+	while ((c = getopt(argc, argv, "s:b:o:t:")) != -1) {
+		char *endp;
+
+		switch (c) {
+		case 's':	/* XXX MB size will be extended */
+			size_KB = strtol(optarg, &endp, 0);
+			break;
+		case 'b':	/* buffer size */
+			buf_size = strtol(optarg, &endp, 0);
+			break;
+		case 'o':	/* start offset */
+			startoff = strtoll(optarg, &endp, 0);
+			break;
+		case 't':	/* initial truncate size */
+			tsize = strtoul(optarg, &endp, 0);
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if (size_KB == 0)	/* default size is 64KB */
+		size_KB = 64;
+	if (buf_size < 2048)	/* default minimum buffer size is 2048 bytes */
+		buf_size = 2048;
+
+	if (optind == argc - 1)
+		filename = argv[optind];
+	else
+		usage(argv[0]);
+
+	fd = open(filename, O_DIRECT | O_CREAT | O_TRUNC | O_RDWR, 0600);
+	if (fd == -1) {
+		perror("open");
+		return 1;
+	}
+
+	/* truncate the file to a special size at first */
+	if (tsize != 0) {
+		ftruncate(fd, tsize);
+		if (fd == -1) {
+			perror("ftruncate");
+			return 1;
+		}
+	}
+
+	err = posix_memalign(&buf, getpagesize(), buf_size);
+	if (err) {
+		fprintf(stderr, "error %s during %s\n",
+			strerror(err),
+			"posix_memalign");
+		return 1;
+	}
+	cmp_buf = malloc(buf_size);
+	memset(cmp_buf, IO_PATTERN, buf_size);
+
+	err = io_setup(5, &ctx);
+	if (err) {
+		fprintf(stderr, "error %s during %s\n",
+			strerror(err),
+			"io_setup");
+		return 1;
+	}
+
+	bytes = 0;
+	/* Keep extending until size_KB */
+	while (bytes < size_KB * 1024) {
+		ssize_t sret;
+		int i;
+
+		memset(buf, IO_PATTERN, buf_size);
+
+		io_prep_pwrite(&iocb1, fd, buf, buf_size/2, \
+		               startoff + bytes + 0*buf_size/2);
+		io_prep_pwrite(&iocb2, fd, buf, buf_size/2, \
+		               startoff + bytes + 1*buf_size/2);
+
+		err = io_submit(ctx, 2, iocbs);
+		if (err != 2) {
+			fprintf(stderr, "error %s during %s\n",
+				strerror(err),
+				"io_submit");
+			return 1;
+		}
+
+		err = io_getevents(ctx, 2, 2, evs, NULL);
+		if (err != 2) {
+			fprintf(stderr, "error %s during %s\n",
+				strerror(err),
+				"io_getevents");
+			return 1;
+		}
+
+		for (i = 0; i < err; i++) {
+			/*
+			 * res is unsigned for some reason, so this is the best
+			 * way to detect that it contains a negative errno.
+			 */
+			if (evs[i].res > buf_size / 2) {
+				fprintf(stderr, "pwrite: %s\n",
+					strerror(-evs[i].res));
+				return 1;
+			}
+		}
+		fsync(fd);
+
+		/*
+		 * And then read it back, then compare with original content.
+		 */
+		sret = pread(fd, buf, buf_size, startoff + bytes);
+		if (sret == -1) {
+			perror("pread");
+			return 1;
+		} else if (sret != buf_size) {
+			fprintf(stderr, "short read %zd was less than %zu\n",
+				sret, buf_size);
+			return 1;
+		}
+		if (memcmp(buf, cmp_buf, buf_size)) {
+			printf("Find corruption\n");
+			dump_buffer(buf, 0, buf_size);
+			return 1;
+		}
+
+		/* Calculate how many bytes we've written after pread */
+		bytes += buf_size;
+	}
+
+	return 0;
+}
diff --git a/tests/generic/999 b/tests/generic/999
new file mode 100755
index 00000000..6fcc593a
--- /dev/null
+++ b/tests/generic/999
@@ -0,0 +1,71 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2019 Red Hat, Inc.  All Rights Reserved.
+#
+# FS QA Test No. 999
+#
+# Non-page-aligned direct AIO write test. AIO write from unalinged offset
+# on a file with different initial truncate i_size.
+#
+# Uncover "ext4: Fix data corruption caused by unaligned direct AIO"
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+_supported_fs generic
+_supported_os Linux
+_require_test
+_require_aiodio aio-dio-write-verify
+_require_test_program "feature"
+
+localfile=$TEST_DIR/tst-aio-dio-testfile
+diosize=`_min_dio_alignment $TEST_DEV`
+pagesize=`src/feature -s`
+bufsize=$((pagesize * 2))
+filesize=$((bufsize * 3 / 1024))
+
+# Need smaller logical block size to do non-page-aligned test
+if [ $diosize -ge $pagesize ];then
+	_notrun "Need device logical block size($diosize) < page size($pagesize)"
+fi
+
+rm -rf $localfile 2>/dev/null
+# page-aligned aiodio write verification at first
+$AIO_TEST -s $((bufsize * 10)) -b $bufsize $localfile
+
+# non-page-aligned aiodio write verification
+i=0
+while [ $((diosize * i)) -lt $bufsize ];do
+	truncsize=$((diosize * i++))
+	# non-page-aligned AIO write on different i_size file
+	$AIO_TEST -s $filesize -b $bufsize -o $diosize -t $truncsize $localfile
+	if [ $? -ne 0 ];then
+		echo "[FAIL] $AIO_TEST -s $filesize -b $bufsize -o $diosize -t $truncsize $localfile"
+	fi
+done
+
+echo "Silence is golden"
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/999.out b/tests/generic/999.out
new file mode 100644
index 00000000..3b276ca8
--- /dev/null
+++ b/tests/generic/999.out
@@ -0,0 +1,2 @@
+QA output created by 999
+Silence is golden
diff --git a/tests/generic/group b/tests/generic/group
index 15227b67..b1f93d99 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -534,3 +534,4 @@
 529 auto quick attr
 530 auto quick unlink
 531 auto quick unlink
+999 auto quick aio
-- 
2.17.2




[Index of Archives]     [Linux Filesystems Development]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux