diff mbox

xfstests 255: add a seek_data/seek_hole tester

Message ID 1309197745-14107-5-git-send-email-josef@redhat.com (mailing list archive)
State Not Applicable
Headers show

Commit Message

Josef Bacik June 27, 2011, 6:02 p.m. UTC
This is a test to make sure seek_data/seek_hole is acting like it does on
Solaris.  It will check to see if the fs supports finding a hole or not and will
adjust as necessary.

Cc: xfs@oss.sgi.com
Signed-off-by: Josef Bacik <josef@redhat.com>
---
 255               |   71 ++++++++
 255.out           |    2 +
 src/seek-tester.c |  473 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 546 insertions(+), 0 deletions(-)
 create mode 100755 255
 create mode 100644 255.out
 create mode 100644 src/seek-tester.c

Comments

Andreas Dilger June 27, 2011, 6:32 p.m. UTC | #1
On 2011-06-27, at 12:02 PM, Josef Bacik wrote:
> This is a test to make sure seek_data/seek_hole is acting like it does on
> Solaris.  It will check to see if the fs supports finding a hole or not and will
> adjust as necessary.
> 
> diff --git a/src/seek-tester.c b/src/seek-tester.c
> new file mode 100644
> index 0000000..2b8c957
> --- /dev/null
> +++ b/src/seek-tester.c
> @@ -0,0 +1,473 @@
> +/*
> + * Copyright (C) 2011 Oracle.  All rights reserved.
> + * Copyright (C) 2011 Red Hat.  All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public
> + * License v2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public
> + * License along with this program; if not, write to the
> + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
> + * Boston, MA 021110-1307, USA.
> + */
> +
> +#define _XOPEN_SOURCE 500
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +
> +#define SEEK_DATA	3
> +#define SEEK_HOLE	4

These should probably be "#ifndef SEEK_DATA" so that gcc doesn't complain
in the future when these are added to a standard header.

> +#define FS_NO_HOLES	(1 << 0)
> +#define QUIET		(1 << 1)
> +
> +static blksize_t alloc_size;
> +static unsigned flags = 0;
> +
> +static int get_io_sizes(int fd)
> +{
> +	struct stat buf;
> +	int ret;
> +
> +	ret = fstat(fd, &buf);
> +	if (ret)
> +		fprintf(stderr, "  ERROR %d: Failed to find io blocksize\n",
> +			errno);
> +
> +	/* st_blksize is typically also the allocation size */
> +	alloc_size = buf.st_blksize;
> +
> +	if (!(flags & QUIET))
> +		printf("Allocation size: %ld\n", alloc_size);
> +
> +	return ret;
> +}
> +
> +#define do_free(x)	do { if(x) free(x); } while(0);
> +
> +static void *do_malloc(size_t size)
> +{
> +	void *buf;
> +
> +	buf = malloc(size);
> +	if (!buf)
> +		fprintf(stderr, "  ERROR: Unable to allocate %ld bytes\n",
> +			(long)size);
> +
> +	return buf;
> +}
> +
> +static int do_truncate(int fd, off_t length)
> +{
> +	int ret;
> +
> +	ret = ftruncate(fd, length);
> +	if (ret)
> +		fprintf(stderr, "  ERROR %d: Failed to extend file "
> +			"to %ld bytes\n", errno, (long)length);
> +	return ret;
> +}
> +
> +static ssize_t do_pwrite(int fd, const void *buf, size_t count, off_t offset)
> +{
> +	ssize_t ret, written = 0;
> +
> +	while (count > written) {
> +		ret = pwrite(fd, buf + written, count - written, offset + written);
> +		if (ret < 0) {
> +			fprintf(stderr, "  ERROR %d: Failed to write %ld "
> +				"bytes\n", errno, (long)count);
> +			return ret;
> +		}
> +		written += ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int do_lseek(int testnum, int subtest, int fd, int origin, off_t set,
> +		    off_t exp)
> +{
> +	off_t pos;
> +	int ret = -1;
> +
> +	pos = lseek(fd, set, origin);
> +
> +	if (pos != exp) {
> +		fprintf(stderr, "  ERROR in Test %d.%d: POS expected %ld, "
> +			"got %ld\n", testnum, subtest, (long)exp, (long)pos);
> +		goto out;
> +	}
> +
> +	if (pos == -1 && errno != ENXIO) {
> +		fprintf(stderr, "  ERROR in Test %d.%d: ERRNO expected %d, "
> +			"got %d\n", testnum, subtest, ENXIO, errno);
> +		goto out;
> +	}
> +
> +	ret = 0;
> +
> +out:
> +	return ret;
> +}
> +
> +static int get_flags(int fd)
> +{
> +	const char *buf = "ABCDEF";
> +	ssize_t written;
> +	off_t pos;
> +	int ret;
> +
> +	ret = do_truncate(fd, alloc_size * 2);
> +	if (ret)
> +		return ret;
> +
> +	written = do_pwrite(fd, buf, strlen(buf), 0);
> +	if (written)
> +		return -1;
> +
> +	pos = lseek(fd, 0, SEEK_HOLE);
> +	if (pos == alloc_size * 2) {
> +		if (!(flags & QUIET))
> +			printf("File system does not recognize holes, the only "
> +			       "hole found will be at the end.\n");
> +		flags |= FS_NO_HOLES;

This is a question that I've also had about compatibility with older
(well, every) Linux kernel that does not support SEEK_{HOLE,DATA}
today.

My reading of the existing generic_file_llseek() and default_llseek()
code, along with most filesystem-specific llseek() implementations is
that they will happily ignore the @whence parameter if it is not
known, and pretend like it is 0 (SEEK_SET), so they will just set the
position to the @offset parameter and return this value.  In that
case, the above "SEEK_HOLE" test would incorrectly fail on every
Linux kernel in existence today because the returned pos == 0.

Should applications call both SEEK_HOLE and SEEK_DATA with @offset=0,
and if they return the same values (which is normally impossible,
decide that the kernel does not support this SEEK_* functionality?

> +	} else if (pos == (off_t)-1) {
> +		fprintf(stderr, "SEEK_HOLE is not supported\n");
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +/* test hole data hole data */
> +static int test06(int fd, int testnum)
> +{
> +	int ret = 0;
> +	char *buf = NULL;
> +	int bufsz = alloc_size;
> +	int filsz = bufsz * 4;
> +	int off;
> +
> +	if (flags & FS_NO_HOLES)
> +		return 1;
> +
> +	/* HOLE - DATA - HOLE - DATA */
> +	/* Each unit is bufsz */
> +
> +	buf = do_malloc(bufsz);
> +	if (!buf)
> +		goto out;
> +	memset(buf, 'a', bufsz);
> +
> +	ret = do_pwrite(fd, buf, bufsz, bufsz);
> +	if (!ret)
> +		do_pwrite(fd, buf, bufsz, bufsz * 3);
> +	if (ret)
> +		goto out;
> +
> +	/* offset at the beginning */
> +	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, 0);
> +	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, 1);
> +	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, bufsz);
> +	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, bufsz);
> +
> +	/* offset around first hole-data boundary */
> +	off = bufsz;
> +	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, off - 1, off - 1);
> +	ret += do_lseek(testnum,  6, fd, SEEK_DATA, off - 1, off);
> +	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, off,     bufsz * 2);
> +	ret += do_lseek(testnum,  8, fd, SEEK_DATA, off,     off);
> +	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, off + 1, bufsz * 2);
> +	ret += do_lseek(testnum, 10, fd, SEEK_DATA, off + 1, off + 1);
> +
> +	/* offset around data-hole boundary */
> +	off = bufsz * 2;
> +	ret += do_lseek(testnum, 11, fd, SEEK_HOLE, off - 1, off);
> +	ret += do_lseek(testnum, 12, fd, SEEK_DATA, off - 1, off - 1);
> +	ret += do_lseek(testnum, 13, fd, SEEK_HOLE, off,     off);
> +	ret += do_lseek(testnum, 14, fd, SEEK_DATA, off,     bufsz * 3);
> +	ret += do_lseek(testnum, 15, fd, SEEK_HOLE, off + 1, off + 1);
> +	ret += do_lseek(testnum, 16, fd, SEEK_DATA, off + 1, bufsz * 3);
> +
> +	/* offset around second hole-data boundary */
> +	off = bufsz * 3;
> +	ret += do_lseek(testnum, 17, fd, SEEK_HOLE, off - 1, off - 1);
> +	ret += do_lseek(testnum, 18, fd, SEEK_DATA, off - 1, off);
> +	ret += do_lseek(testnum, 19, fd, SEEK_HOLE, off,     filsz);
> +	ret += do_lseek(testnum, 20, fd, SEEK_DATA, off,     off);
> +	ret += do_lseek(testnum, 21, fd, SEEK_HOLE, off + 1, filsz);
> +	ret += do_lseek(testnum, 22, fd, SEEK_DATA, off + 1, off + 1);
> +
> +	/* offset around the end of file */
> +	off = filsz;
> +	ret += do_lseek(testnum, 23, fd, SEEK_HOLE, off - 1, filsz);
> +	ret += do_lseek(testnum, 24, fd, SEEK_DATA, off - 1, filsz - 1);
> +	ret += do_lseek(testnum, 25, fd, SEEK_HOLE, off, -1);
> +	ret += do_lseek(testnum, 26, fd, SEEK_DATA, off, -1);
> +	ret += do_lseek(testnum, 27, fd, SEEK_HOLE, off + 1, -1);
> +	ret += do_lseek(testnum, 28, fd, SEEK_DATA, off + 1, -1);
> +
> +out:
> +	do_free(buf);
> +	return ret;
> +}
> +
> +/* test file with data at the beginning and a hole at the end */
> +static int test05(int fd, int testnum)
> +{
> +	int ret = -1;
> +	char *buf = NULL;
> +	int bufsz = alloc_size;
> +	int filsz = bufsz * 4;
> +
> +	if (flags & FS_NO_HOLES)
> +		return 1;
> +
> +	/* DATA - HOLE */
> +	/* Each unit is bufsz */
> +
> +	buf = do_malloc(bufsz);
> +	if (!buf)
> +		goto out;
> +	memset(buf, 'a', bufsz);
> +
> +	ret = do_truncate(fd, filsz);
> +	if (!ret)
> +		ret = do_pwrite(fd, buf, bufsz, 0);
> +	if (ret)
> +		goto out;
> +
> +	/* offset at the beginning */
> +	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, bufsz);
> +	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, bufsz);
> +	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, 0);
> +	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, 1);
> +
> +	/* offset around data-hole boundary */
> +	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, bufsz - 1, bufsz);
> +	ret += do_lseek(testnum,  6, fd, SEEK_DATA, bufsz - 1, bufsz - 1);
> +	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, bufsz,     bufsz);
> +	ret += do_lseek(testnum,  8, fd, SEEK_DATA, bufsz,     -1);
> +	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, bufsz + 1, bufsz + 1);
> +	ret += do_lseek(testnum, 10, fd, SEEK_DATA, bufsz + 1, -1);
> +
> +	/* offset around eof */
> +	ret += do_lseek(testnum, 11, fd, SEEK_HOLE, filsz - 1, filsz - 1);
> +	ret += do_lseek(testnum, 12, fd, SEEK_DATA, filsz - 1, -1);
> +	ret += do_lseek(testnum, 13, fd, SEEK_HOLE, filsz,     -1);
> +	ret += do_lseek(testnum, 14, fd, SEEK_DATA, filsz,     -1);
> +	ret += do_lseek(testnum, 15, fd, SEEK_HOLE, filsz + 1, -1);
> +	ret += do_lseek(testnum, 16, fd, SEEK_DATA, filsz + 1, -1);
> +
> +out:
> +	do_free(buf);
> +	return ret;
> +}
> +
> +/* test hole begin and data end */
> +static int test04(int fd, int testnum)
> +{
> +	int ret;
> +	char *buf = "ABCDEFGH";
> +	int bufsz = sizeof(buf);
> +	int holsz = alloc_size * 2;
> +	int filsz = holsz + bufsz;
> +
> +	if (flags & FS_NO_HOLES)
> +		return 1;
> +
> +	/* HOLE - DATA */
> +
> +	ret = do_pwrite(fd, buf, bufsz, holsz);
> +	if (ret)
> +		goto out;
> +
> +	/* offset at the beginning */
> +	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, 0);
> +	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, 1);
> +	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, holsz);
> +	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, holsz);
> +
> +	/* offset around hole-data boundary */
> +	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, holsz - 1, holsz - 1);
> +	ret += do_lseek(testnum,  6, fd, SEEK_DATA, holsz - 1, holsz);
> +	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, holsz,     filsz);
> +	ret += do_lseek(testnum,  8, fd, SEEK_DATA, holsz,     holsz);
> +	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, holsz + 1, filsz);
> +	ret += do_lseek(testnum, 10, fd, SEEK_DATA, holsz + 1, holsz + 1);
> +
> +	/* offset around eof */
> +	ret += do_lseek(testnum, 11, fd, SEEK_HOLE, filsz - 1, filsz);
> +	ret += do_lseek(testnum, 12, fd, SEEK_DATA, filsz - 1, filsz - 1);
> +	ret += do_lseek(testnum, 13, fd, SEEK_HOLE, filsz,     -1);
> +	ret += do_lseek(testnum, 14, fd, SEEK_DATA, filsz,     -1);
> +	ret += do_lseek(testnum, 15, fd, SEEK_HOLE, filsz + 1, -1);
> +	ret += do_lseek(testnum, 16, fd, SEEK_DATA, filsz + 1, -1);
> +out:
> +	return ret;
> +}
> +
> +/* test full file */
> +static int test03(int fd, int testnum)
> +{
> +	char *buf = NULL;
> +	int bufsz = alloc_size + 100;
> +	int ret = -1;
> +
> +	buf = do_malloc(bufsz);
> +	if (!buf)
> +		goto out;
> +	memset(buf, 'a', bufsz);
> +
> +	ret = do_pwrite(fd, buf, bufsz, 0);
> +	if (ret)
> +		goto out;
> +
> +	/* offset at the beginning */
> +	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, bufsz);
> +	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, bufsz);
> +	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, 0);
> +	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, 1);
> +
> +	/* offset around eof */
> +	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, bufsz - 1, bufsz);
> +	ret += do_lseek(testnum,  6, fd, SEEK_DATA, bufsz - 1, bufsz - 1);
> +	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, bufsz,     -1);
> +	ret += do_lseek(testnum,  8, fd, SEEK_DATA, bufsz,     -1);
> +	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, bufsz + 1, -1);
> +	ret += do_lseek(testnum, 10, fd, SEEK_DATA, bufsz + 1, -1);
> +
> +out:
> +	do_free(buf);
> +	return ret;
> +}
> +
> +/* test empty file */
> +static int test02(int fd, int testnum)
> +{
> +	int ret = 0;
> +
> +	ret += do_lseek(testnum, 1, fd, SEEK_DATA, 0, -1);
> +	ret += do_lseek(testnum, 2, fd, SEEK_HOLE, 0, -1);
> +	ret += do_lseek(testnum, 3, fd, SEEK_HOLE, 1, -1);
> +
> +	return ret;
> +}
> +
> +/* test feature support */
> +static int test01(int fd, int testnum)
> +{
> +	int ret;
> +	char buf[] = "ABCDEFGH";
> +	int bufsz = sizeof(buf);
> +
> +	ret = do_pwrite(fd, buf, bufsz, 0);
> +	if (ret)
> +		goto out;
> +
> +	ret += do_lseek(testnum, 1, fd, SEEK_DATA, 0, 0);
> +	ret += do_lseek(testnum, 2, fd, SEEK_HOLE, 0, bufsz);
> +
> +out:
> +	return ret;
> +}
> +
> +struct testrec {
> +	int	test_num;
> +	int	(*test_func)(int fd, int testnum);
> +	char	*test_desc;
> +};
> +
> +struct testrec seek_tests[] = {
> +	{  1, test01, "Test basic support" },
> +	{  2, test02, "Test an empty file" },
> +	{  3, test03, "Test a full file" },
> +	{  4, test04, "Test file hole at beg, data at end" },
> +	{  5, test05, "Test file data at beg, hole at end" },
> +	{  6, test06, "Test file hole data hole data" },
> +};
> +
> +static int run_test(int fd, struct testrec *tr)
> +{
> +	int ret;
> +
> +	ret = tr->test_func(fd, tr->test_num);
> +	if (!(flags & QUIET))
> +		printf("%02d. %-50s\t%s\n", tr->test_num, tr->test_desc,
> +		       ret < 0 ? "FAIL" : (ret == 0 ? "SUCC" : "NOT RUN"));
> +	return ret;
> +}
> +
> +void print_help()
> +{
> +	printf("seek-test [-h] [-q] filename\n");
> +	printf("\t-h - this message\n");
> +	printf("\t-q - quiet, no output\n");
> +	printf("\tfilename - file to use for the test\n");
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int ret = -1;
> +	int i, fd = -1;
> +	int c;
> +	int numtests = sizeof(seek_tests) / sizeof(struct testrec);
> +
> +	while ((c = getopt(argc, argv, "qh")) != -1) {
> +		switch (c) {
> +		case 'q':
> +			flags |= QUIET;
> +			break;
> +		case 'h':
> +			print_help();
> +			exit(0);
> +		default:
> +			print_help();
> +			exit(1);
> +		}
> +	}
> +
> +	if (optind >= argc) {
> +		print_help();
> +		exit(1);
> +	}
> +
> +	fd = open(argv[optind], O_RDWR|O_CREAT|O_TRUNC, 0644);
> +	if (fd < 0) {
> +		fprintf(stderr, "Failed to open testfile: %d\n", errno);
> +		goto out;
> +	}
> +
> +	ret = get_io_sizes(fd);
> +	if (ret)
> +		goto out;
> +
> +	ret = get_flags(fd);
> +	if (ret)
> +		goto out;
> +
> +	for (i = 0; i < numtests; ++i) {
> +		ret = do_truncate(fd, 0);
> +		if (ret)
> +			goto out;
> +		run_test(fd, &seek_tests[i]);
> +	}
> +
> +out:
> +	if (fd > -1)
> +		close(fd);
> +	return ret;
> +}
> -- 
> 1.7.5.2
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


Cheers, Andreas





--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Josef Bacik June 27, 2011, 6:47 p.m. UTC | #2
On 06/27/2011 02:32 PM, Andreas Dilger wrote:
> On 2011-06-27, at 12:02 PM, Josef Bacik wrote:

<snip>

>> +
>> +#define SEEK_DATA	3
>> +#define SEEK_HOLE	4
> 
> These should probably be "#ifndef SEEK_DATA" so that gcc doesn't complain
> in the future when these are added to a standard header.
> 

Good point, I will fix that, thanks.

<snip>

>> +
>> +	pos = lseek(fd, 0, SEEK_HOLE);
>> +	if (pos == alloc_size * 2) {
>> +		if (!(flags & QUIET))
>> +			printf("File system does not recognize holes, the only "
>> +			       "hole found will be at the end.\n");
>> +		flags |= FS_NO_HOLES;
> 
> This is a question that I've also had about compatibility with older
> (well, every) Linux kernel that does not support SEEK_{HOLE,DATA}
> today.
> 

If you look at the xfstest part of it, I grep for a complaint about not
support SEEK_HOLE and I just say _notrun.  I ran this test on an old
kernel and it just skipped the test.

> My reading of the existing generic_file_llseek() and default_llseek()
> code, along with most filesystem-specific llseek() implementations is
> that they will happily ignore the @whence parameter if it is not
> known, and pretend like it is 0 (SEEK_SET), so they will just set the
> position to the @offset parameter and return this value.  In that
> case, the above "SEEK_HOLE" test would incorrectly fail on every
> Linux kernel in existence today because the returned pos == 0.
> 

First, older kernels will check for whence > SEEK_MAX and automatically
return -EINVAL, so we are ok there.

Second, I looked at everybody in fs/ and changed anybody that did what
you suggest.  Anybody that I didn't change will return -EINVAL properly
so I didn't touch them.  I also looked at the drivers that didn't use
default_llseek/seq_lseek or whatever and they all seem to handle things
properly, though I'm sure I missed somebody.

> Should applications call both SEEK_HOLE and SEEK_DATA with @offset=0,
> and if they return the same values (which is normally impossible,
> decide that the kernel does not support this SEEK_* functionality?

Yeah if you want to be super careful.  I mean for all file systems we
should be ok with my patches, but if you hit some weird proc file that
has it's llseek thing tied to something specific in the driver you may
run into trouble, and by trouble you will just get weird return's for
your seek.  Thanks,

Josef

--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/255 b/255
new file mode 100755
index 0000000..4bb4d0b
--- /dev/null
+++ b/255
@@ -0,0 +1,71 @@ 
+#! /bin/bash
+# FS QA Test No. 255
+#
+# Test SEEK_DATA and SEEK_HOLE
+#
+#-----------------------------------------------------------------------
+# Copyright (c) 2011 Red Hat.  All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write the Free Software Foundation,
+# Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+#-----------------------------------------------------------------------
+#
+# creator
+owner=josef@redhat.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1	# failure is the default!
+
+_cleanup()
+{
+    rm -f $tmp.*
+}
+
+trap "_cleanup ; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+. ./common.rc
+. ./common.filter
+
+# real QA test starts here
+_supported_fs generic
+_supported_os Linux
+
+testfile=$TEST_DIR/seek_test.$$
+logfile=$TEST_DIR/seek_test.$$.log
+
+[ -x $here/src/seek-tester ] || _notrun "seek-tester not built"
+
+_cleanup()
+{
+	rm -f $testfile
+	rm -f $logfile
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+echo "Silence is golden"
+$here/src/seek-tester -q $testfile 2>&1 | tee -a $logfile
+
+if grep -q "SEEK_HOLE is not supported" $logfile; then
+	_notrun "SEEK_HOLE/SEEK_DATA not supported by this kernel"
+fi
+
+rm -f $logfile
+rm -f $testfile
+
+status=0 ; exit
diff --git a/255.out b/255.out
new file mode 100644
index 0000000..7eefb82
--- /dev/null
+++ b/255.out
@@ -0,0 +1,2 @@ 
+QA output created by 255
+Silence is golden
diff --git a/src/seek-tester.c b/src/seek-tester.c
new file mode 100644
index 0000000..2b8c957
--- /dev/null
+++ b/src/seek-tester.c
@@ -0,0 +1,473 @@ 
+/*
+ * Copyright (C) 2011 Oracle.  All rights reserved.
+ * Copyright (C) 2011 Red Hat.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#define _XOPEN_SOURCE 500
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define SEEK_DATA	3
+#define SEEK_HOLE	4
+
+#define FS_NO_HOLES	(1 << 0)
+#define QUIET		(1 << 1)
+
+static blksize_t alloc_size;
+static unsigned flags = 0;
+
+static int get_io_sizes(int fd)
+{
+	struct stat buf;
+	int ret;
+
+	ret = fstat(fd, &buf);
+	if (ret)
+		fprintf(stderr, "  ERROR %d: Failed to find io blocksize\n",
+			errno);
+
+	/* st_blksize is typically also the allocation size */
+	alloc_size = buf.st_blksize;
+
+	if (!(flags & QUIET))
+		printf("Allocation size: %ld\n", alloc_size);
+
+	return ret;
+}
+
+#define do_free(x)	do { if(x) free(x); } while(0);
+
+static void *do_malloc(size_t size)
+{
+	void *buf;
+
+	buf = malloc(size);
+	if (!buf)
+		fprintf(stderr, "  ERROR: Unable to allocate %ld bytes\n",
+			(long)size);
+
+	return buf;
+}
+
+static int do_truncate(int fd, off_t length)
+{
+	int ret;
+
+	ret = ftruncate(fd, length);
+	if (ret)
+		fprintf(stderr, "  ERROR %d: Failed to extend file "
+			"to %ld bytes\n", errno, (long)length);
+	return ret;
+}
+
+static ssize_t do_pwrite(int fd, const void *buf, size_t count, off_t offset)
+{
+	ssize_t ret, written = 0;
+
+	while (count > written) {
+		ret = pwrite(fd, buf + written, count - written, offset + written);
+		if (ret < 0) {
+			fprintf(stderr, "  ERROR %d: Failed to write %ld "
+				"bytes\n", errno, (long)count);
+			return ret;
+		}
+		written += ret;
+	}
+
+	return 0;
+}
+
+static int do_lseek(int testnum, int subtest, int fd, int origin, off_t set,
+		    off_t exp)
+{
+	off_t pos;
+	int ret = -1;
+
+	pos = lseek(fd, set, origin);
+
+	if (pos != exp) {
+		fprintf(stderr, "  ERROR in Test %d.%d: POS expected %ld, "
+			"got %ld\n", testnum, subtest, (long)exp, (long)pos);
+		goto out;
+	}
+
+	if (pos == -1 && errno != ENXIO) {
+		fprintf(stderr, "  ERROR in Test %d.%d: ERRNO expected %d, "
+			"got %d\n", testnum, subtest, ENXIO, errno);
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	return ret;
+}
+
+static int get_flags(int fd)
+{
+	const char *buf = "ABCDEF";
+	ssize_t written;
+	off_t pos;
+	int ret;
+
+	ret = do_truncate(fd, alloc_size * 2);
+	if (ret)
+		return ret;
+
+	written = do_pwrite(fd, buf, strlen(buf), 0);
+	if (written)
+		return -1;
+
+	pos = lseek(fd, 0, SEEK_HOLE);
+	if (pos == alloc_size * 2) {
+		if (!(flags & QUIET))
+			printf("File system does not recognize holes, the only "
+			       "hole found will be at the end.\n");
+		flags |= FS_NO_HOLES;
+	} else if (pos == (off_t)-1) {
+		fprintf(stderr, "SEEK_HOLE is not supported\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* test hole data hole data */
+static int test06(int fd, int testnum)
+{
+	int ret = 0;
+	char *buf = NULL;
+	int bufsz = alloc_size;
+	int filsz = bufsz * 4;
+	int off;
+
+	if (flags & FS_NO_HOLES)
+		return 1;
+
+	/* HOLE - DATA - HOLE - DATA */
+	/* Each unit is bufsz */
+
+	buf = do_malloc(bufsz);
+	if (!buf)
+		goto out;
+	memset(buf, 'a', bufsz);
+
+	ret = do_pwrite(fd, buf, bufsz, bufsz);
+	if (!ret)
+		do_pwrite(fd, buf, bufsz, bufsz * 3);
+	if (ret)
+		goto out;
+
+	/* offset at the beginning */
+	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, 0);
+	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, 1);
+	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, bufsz);
+	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, bufsz);
+
+	/* offset around first hole-data boundary */
+	off = bufsz;
+	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, off - 1, off - 1);
+	ret += do_lseek(testnum,  6, fd, SEEK_DATA, off - 1, off);
+	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, off,     bufsz * 2);
+	ret += do_lseek(testnum,  8, fd, SEEK_DATA, off,     off);
+	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, off + 1, bufsz * 2);
+	ret += do_lseek(testnum, 10, fd, SEEK_DATA, off + 1, off + 1);
+
+	/* offset around data-hole boundary */
+	off = bufsz * 2;
+	ret += do_lseek(testnum, 11, fd, SEEK_HOLE, off - 1, off);
+	ret += do_lseek(testnum, 12, fd, SEEK_DATA, off - 1, off - 1);
+	ret += do_lseek(testnum, 13, fd, SEEK_HOLE, off,     off);
+	ret += do_lseek(testnum, 14, fd, SEEK_DATA, off,     bufsz * 3);
+	ret += do_lseek(testnum, 15, fd, SEEK_HOLE, off + 1, off + 1);
+	ret += do_lseek(testnum, 16, fd, SEEK_DATA, off + 1, bufsz * 3);
+
+	/* offset around second hole-data boundary */
+	off = bufsz * 3;
+	ret += do_lseek(testnum, 17, fd, SEEK_HOLE, off - 1, off - 1);
+	ret += do_lseek(testnum, 18, fd, SEEK_DATA, off - 1, off);
+	ret += do_lseek(testnum, 19, fd, SEEK_HOLE, off,     filsz);
+	ret += do_lseek(testnum, 20, fd, SEEK_DATA, off,     off);
+	ret += do_lseek(testnum, 21, fd, SEEK_HOLE, off + 1, filsz);
+	ret += do_lseek(testnum, 22, fd, SEEK_DATA, off + 1, off + 1);
+
+	/* offset around the end of file */
+	off = filsz;
+	ret += do_lseek(testnum, 23, fd, SEEK_HOLE, off - 1, filsz);
+	ret += do_lseek(testnum, 24, fd, SEEK_DATA, off - 1, filsz - 1);
+	ret += do_lseek(testnum, 25, fd, SEEK_HOLE, off, -1);
+	ret += do_lseek(testnum, 26, fd, SEEK_DATA, off, -1);
+	ret += do_lseek(testnum, 27, fd, SEEK_HOLE, off + 1, -1);
+	ret += do_lseek(testnum, 28, fd, SEEK_DATA, off + 1, -1);
+
+out:
+	do_free(buf);
+	return ret;
+}
+
+/* test file with data at the beginning and a hole at the end */
+static int test05(int fd, int testnum)
+{
+	int ret = -1;
+	char *buf = NULL;
+	int bufsz = alloc_size;
+	int filsz = bufsz * 4;
+
+	if (flags & FS_NO_HOLES)
+		return 1;
+
+	/* DATA - HOLE */
+	/* Each unit is bufsz */
+
+	buf = do_malloc(bufsz);
+	if (!buf)
+		goto out;
+	memset(buf, 'a', bufsz);
+
+	ret = do_truncate(fd, filsz);
+	if (!ret)
+		ret = do_pwrite(fd, buf, bufsz, 0);
+	if (ret)
+		goto out;
+
+	/* offset at the beginning */
+	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, bufsz);
+	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, bufsz);
+	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, 0);
+	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, 1);
+
+	/* offset around data-hole boundary */
+	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, bufsz - 1, bufsz);
+	ret += do_lseek(testnum,  6, fd, SEEK_DATA, bufsz - 1, bufsz - 1);
+	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, bufsz,     bufsz);
+	ret += do_lseek(testnum,  8, fd, SEEK_DATA, bufsz,     -1);
+	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, bufsz + 1, bufsz + 1);
+	ret += do_lseek(testnum, 10, fd, SEEK_DATA, bufsz + 1, -1);
+
+	/* offset around eof */
+	ret += do_lseek(testnum, 11, fd, SEEK_HOLE, filsz - 1, filsz - 1);
+	ret += do_lseek(testnum, 12, fd, SEEK_DATA, filsz - 1, -1);
+	ret += do_lseek(testnum, 13, fd, SEEK_HOLE, filsz,     -1);
+	ret += do_lseek(testnum, 14, fd, SEEK_DATA, filsz,     -1);
+	ret += do_lseek(testnum, 15, fd, SEEK_HOLE, filsz + 1, -1);
+	ret += do_lseek(testnum, 16, fd, SEEK_DATA, filsz + 1, -1);
+
+out:
+	do_free(buf);
+	return ret;
+}
+
+/* test hole begin and data end */
+static int test04(int fd, int testnum)
+{
+	int ret;
+	char *buf = "ABCDEFGH";
+	int bufsz = sizeof(buf);
+	int holsz = alloc_size * 2;
+	int filsz = holsz + bufsz;
+
+	if (flags & FS_NO_HOLES)
+		return 1;
+
+	/* HOLE - DATA */
+
+	ret = do_pwrite(fd, buf, bufsz, holsz);
+	if (ret)
+		goto out;
+
+	/* offset at the beginning */
+	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, 0);
+	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, 1);
+	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, holsz);
+	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, holsz);
+
+	/* offset around hole-data boundary */
+	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, holsz - 1, holsz - 1);
+	ret += do_lseek(testnum,  6, fd, SEEK_DATA, holsz - 1, holsz);
+	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, holsz,     filsz);
+	ret += do_lseek(testnum,  8, fd, SEEK_DATA, holsz,     holsz);
+	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, holsz + 1, filsz);
+	ret += do_lseek(testnum, 10, fd, SEEK_DATA, holsz + 1, holsz + 1);
+
+	/* offset around eof */
+	ret += do_lseek(testnum, 11, fd, SEEK_HOLE, filsz - 1, filsz);
+	ret += do_lseek(testnum, 12, fd, SEEK_DATA, filsz - 1, filsz - 1);
+	ret += do_lseek(testnum, 13, fd, SEEK_HOLE, filsz,     -1);
+	ret += do_lseek(testnum, 14, fd, SEEK_DATA, filsz,     -1);
+	ret += do_lseek(testnum, 15, fd, SEEK_HOLE, filsz + 1, -1);
+	ret += do_lseek(testnum, 16, fd, SEEK_DATA, filsz + 1, -1);
+out:
+	return ret;
+}
+
+/* test full file */
+static int test03(int fd, int testnum)
+{
+	char *buf = NULL;
+	int bufsz = alloc_size + 100;
+	int ret = -1;
+
+	buf = do_malloc(bufsz);
+	if (!buf)
+		goto out;
+	memset(buf, 'a', bufsz);
+
+	ret = do_pwrite(fd, buf, bufsz, 0);
+	if (ret)
+		goto out;
+
+	/* offset at the beginning */
+	ret += do_lseek(testnum,  1, fd, SEEK_HOLE, 0, bufsz);
+	ret += do_lseek(testnum,  2, fd, SEEK_HOLE, 1, bufsz);
+	ret += do_lseek(testnum,  3, fd, SEEK_DATA, 0, 0);
+	ret += do_lseek(testnum,  4, fd, SEEK_DATA, 1, 1);
+
+	/* offset around eof */
+	ret += do_lseek(testnum,  5, fd, SEEK_HOLE, bufsz - 1, bufsz);
+	ret += do_lseek(testnum,  6, fd, SEEK_DATA, bufsz - 1, bufsz - 1);
+	ret += do_lseek(testnum,  7, fd, SEEK_HOLE, bufsz,     -1);
+	ret += do_lseek(testnum,  8, fd, SEEK_DATA, bufsz,     -1);
+	ret += do_lseek(testnum,  9, fd, SEEK_HOLE, bufsz + 1, -1);
+	ret += do_lseek(testnum, 10, fd, SEEK_DATA, bufsz + 1, -1);
+
+out:
+	do_free(buf);
+	return ret;
+}
+
+/* test empty file */
+static int test02(int fd, int testnum)
+{
+	int ret = 0;
+
+	ret += do_lseek(testnum, 1, fd, SEEK_DATA, 0, -1);
+	ret += do_lseek(testnum, 2, fd, SEEK_HOLE, 0, -1);
+	ret += do_lseek(testnum, 3, fd, SEEK_HOLE, 1, -1);
+
+	return ret;
+}
+
+/* test feature support */
+static int test01(int fd, int testnum)
+{
+	int ret;
+	char buf[] = "ABCDEFGH";
+	int bufsz = sizeof(buf);
+
+	ret = do_pwrite(fd, buf, bufsz, 0);
+	if (ret)
+		goto out;
+
+	ret += do_lseek(testnum, 1, fd, SEEK_DATA, 0, 0);
+	ret += do_lseek(testnum, 2, fd, SEEK_HOLE, 0, bufsz);
+
+out:
+	return ret;
+}
+
+struct testrec {
+	int	test_num;
+	int	(*test_func)(int fd, int testnum);
+	char	*test_desc;
+};
+
+struct testrec seek_tests[] = {
+	{  1, test01, "Test basic support" },
+	{  2, test02, "Test an empty file" },
+	{  3, test03, "Test a full file" },
+	{  4, test04, "Test file hole at beg, data at end" },
+	{  5, test05, "Test file data at beg, hole at end" },
+	{  6, test06, "Test file hole data hole data" },
+};
+
+static int run_test(int fd, struct testrec *tr)
+{
+	int ret;
+
+	ret = tr->test_func(fd, tr->test_num);
+	if (!(flags & QUIET))
+		printf("%02d. %-50s\t%s\n", tr->test_num, tr->test_desc,
+		       ret < 0 ? "FAIL" : (ret == 0 ? "SUCC" : "NOT RUN"));
+	return ret;
+}
+
+void print_help()
+{
+	printf("seek-test [-h] [-q] filename\n");
+	printf("\t-h - this message\n");
+	printf("\t-q - quiet, no output\n");
+	printf("\tfilename - file to use for the test\n");
+}
+
+int main(int argc, char **argv)
+{
+	int ret = -1;
+	int i, fd = -1;
+	int c;
+	int numtests = sizeof(seek_tests) / sizeof(struct testrec);
+
+	while ((c = getopt(argc, argv, "qh")) != -1) {
+		switch (c) {
+		case 'q':
+			flags |= QUIET;
+			break;
+		case 'h':
+			print_help();
+			exit(0);
+		default:
+			print_help();
+			exit(1);
+		}
+	}
+
+	if (optind >= argc) {
+		print_help();
+		exit(1);
+	}
+
+	fd = open(argv[optind], O_RDWR|O_CREAT|O_TRUNC, 0644);
+	if (fd < 0) {
+		fprintf(stderr, "Failed to open testfile: %d\n", errno);
+		goto out;
+	}
+
+	ret = get_io_sizes(fd);
+	if (ret)
+		goto out;
+
+	ret = get_flags(fd);
+	if (ret)
+		goto out;
+
+	for (i = 0; i < numtests; ++i) {
+		ret = do_truncate(fd, 0);
+		if (ret)
+			goto out;
+		run_test(fd, &seek_tests[i]);
+	}
+
+out:
+	if (fd > -1)
+		close(fd);
+	return ret;
+}