[PATCH] xfstests: Introduce a new SEEK_DATA/SEEK_HOLE tester

Jeff Liu
2011-12-29T13:32:18Z

Hello,

This is another SEEK_DATA/SEEK_HOLE tester which is intended to cover multiple extents checking.
I have ran it against btrfs to ensure the tester works, and ran it against XFS to ensure the SEEK_DATA/SEEK_HOLE patch works too.

There have four test cases in xfstest-277.
1. test file created in preallocation mode with dirty pages.
2. test file created in preallocation mode with writeback pages.
3. test file created as normal, but make holes with pwrite(2), full with zeros(data extent) at the end of file.
4. test file created as normal, same as 3, but make holes at the end of file.

Any feekback are appreciated!

Signed-off-by: Jie Liu <jeff.liu@oracle.com>

---
277 | 165 ++++++++++++
group | 1 +
src/Makefile | 3 +-
src/seek_copy_tester.c | 674 ++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 842 insertions(+), 1 deletions(-)
create mode 100755 277
create mode 100755 src/seek_copy_tester.c

diff --git a/277 b/277
new file mode 100755
index 0000000..04eb32d
--- /dev/null
+++ b/277
@@ -0,0 +1,165 @@
+#! /bin/bash
+# FS QA Test No. 276
+#
+# SEEK_DATA/SEEK_HOLE tester.
+# Create a sparse file with a couple of holes and data extents.
+# Simulates the copy process to verify SEEK_DATA/SEEK_HOLE stuff works
+# as expected.
+#
+#-----------------------------------------------------------------------
+# Copyright (c) 2011 Oracle Inc. 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=jeff.liu@oracle.com
+
+seq=`basename $0`
+echo "QA output created by $seq"
+
+here=`pwd`
+fail=0
+status=0 # success is the default!
+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 Solaris
+
+src=$TEST_DIR/seek_copy_testfile
+dest=$TEST_DIR/seek_copy_testfile.dest
+
+[ -x $here/src/seek_copy_tester ] || _notrun "seek_copy_tester not built"
+
+_cleanup()
+{
+ rm -f $src $dest
+}
+
+# seek_copy_test_01()
+# create a 100Mytes file in preallocation mode.
+# fallocate offset start from 0.
+# the first data extent offset start from 80991, write 4Kbytes,
+# and then skip 195001 bytes for next write.
+# this is intended to test data buffer lookup for DIRTY pages.
+# verify results:
+# 1. file size is identical.
+# 2. perform cmp(1) to compare SRC and DEST file byte by byte.
+test01()
+{
+ rm -f $src $dest
+
+ $here/src/seek_copy_tester -P -O 0 -L 100m -s 80991 -k 195001 -l 4k $src $dest
+
+ test $(stat --printf "%s" $src) = $(stat --printf "%s" $dest) ||
+ echo "FAIL_TEST01: file size no equal" >> $seq.out &&
+ fail=1
+ cmp $src $dest | tee -a $seq.out
+ cmp $src $dest ||
+ echo "FAIL_TEST01: file compare failed" >> $seq.out &&
+ fail=1
+ test $fail -eq 1 && status=1
+}
+
+# seek_copy_test_02()
+# create a 100Mytes file in preallocation mode.
+# fallocate offset start from 0.
+# the first data extent offset start from 0, write 16Kbytes,
+# and then skip 8Mbytes for next write.
+# Try flushing DIRTY pages to WRITEBACK mode, this is intended to
+# test data buffer lookup in WRITEBACK pages.
+# verify results:
+# 1. file size is identical.
+# 2. perform cmp(1) to compare SRC and DEST file byte by byte.
+test02()
+{
+ rm -rf $src $dest
+ fail=0
+
+ $here/src/seek_copy_tester -S -P -O 0 -L 100m -s 0 -k 8m -l 16k $src $dest
+
+ test $(stat --printf "%s" $src) = $(stat --printf "%s" $dest) ||
+ echo "FAIL_TEST02: file size no equal" >> $seq.out &&
+ fail=1
+ cmp $src $dest | tee -a $seq.out
+ cmp $src $dest ||
+ echo "FAIL_TEST02: file compare failed" >>$seq.out &&
+ fail=1
+ test $fail -eq 1 && status=1
+}
+
+# seek_copy_test_03
+# create a 100Mytes sparse file.
+# the first data extent offset start from 512, write 4Kbytes,
+# and then skip 1Mbytes for next write.
+# don't make holes at the end of file.
+# verify results:
+# 1. file size is identical.
+# 2. perform cmp(1) to compare SRC and DEST file byte by byte.
+test03()
+{
+ rm -f $src $dest
+ fail=0
+
+ $here/src/seek_copy_tester -M 100m -s 512 -k 1m -l 4k $src $dest
+
+ test $(stat --printf "%s" $src) = $(stat --printf "%s" $dest) ||
+ echo "FAIL_TEST03: file size no equal" >> $seq.out &&
+ fail=1
+ cmp $src $dest | tee -a $seq.out
+ cmp $src $dest ||
+ echo "FAIL_TEST03: file compare failed" >>$seq.out &&
+ fail=1
+ test $fail -eq 1 && status=1
+}
+
+# seek_copy_test_04
+# create a 1Gbytes sparse file.
+# the first data extent offset start from 512, write 4Kbytes,
+# and then skip 1Mbytes for next write.
+# make holes at the end of file.
+# verify results:
+# 1. file size is identical.
+# 2. perform cmp(1) to compare SRC and DEST file byte by byte.
+test04()
+{
+ rm -f $src $dest
+ fail=0
+
+ $here/src/seek_copy_tester -M 1g -s 512 -k 1m -l 4k -E $src $dest
+
+ test $(stat --printf "%s" $src) = $(stat --printf "%s" $dest) ||
+ echo "FAIL_TEST04: file size no equal" >> $seq.out &&
+ fail=1
+ cmp $src $dest | tee -a $seq.out
+ cmp $src $dest ||
+ echo "FAIL_TEST04: file compare failed" >>$seq.out &&
+ fail=1
+ test $fail -eq 1 && status=1
+}
+echo "QA output created by $seq" >> $seq.out
+
+test01
+test02
+test03
+test04
+
+status=0
+exit
diff --git a/group b/group
index be4bd9b..5228abe 100644
--- a/group
+++ b/group
@@ -390,3 +390,4 @@ deprecated
274 auto rw
275 auto rw
276 auto quick
+277 auto rw
diff --git a/src/Makefile b/src/Makefile
index 6797064..4a24d3c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -17,7 +17,8 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
preallo_rw_pattern_writer ftrunc trunc fs_perms testx looptest \
locktest unwritten_mmap bulkstat_unlink_test t_stripealign \
bulkstat_unlink_test_modified t_dir_offset t_futimens t_immutable \
- stale_handle pwrite_mmap_blocked fstrim t_dir_offset2 seek_sanity_tester
+ stale_handle pwrite_mmap_blocked fstrim t_dir_offset2 seek_sanity_tester \
+ seek_copy_tester

SUBDIRS =

diff --git a/src/seek_copy_tester.c b/src/seek_copy_tester.c
new file mode 100755
index 0000000..4971f34
--- /dev/null
+++ b/src/seek_copy_tester.c
@@ -0,0 +1,674 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+#include <linux/falloc.h>
+
+#ifndef SEEK_DATA
+#define SEEK_DATA 3
+#define SEEK_HOLE 4
+#endif
+
+#define BUF_SIZE 4096
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+/* Below check stuff copied from Coreutils */
+/* True if the arithmetic type T is signed */
+#define TYPE_SIGNED(t) (! ((t)0 < (t)-1))
+#define TYPE_MAXIMUM(t) \
+ ((t) (! TYPE_SIGNED(t) \
+ ? (t) -1 \
+ : ((((t) 1 << (sizeof(t) * CHAR_BIT - 2)) - 1) * 2 + 1)))
+
+#ifndef OFF_T_MAX
+# define OFF_T_MAX TYPE_MAXIMUM (off_t)
+#endif
+
+void
+error(const char *fmt, ...)
+{
+ char buf[256];
+ va_list args;
+ va_start(args, fmt);
+ vsprintf(buf, fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "ERROR: %s\n", buf);
+}
+
+/* copied from btrfs progs */
+uint64_t
+parse_size(char *s)
+{
+ int len = strlen(s);
+ char c;
+ uint64_t mult = 1;
+
+ if (!isdigit(s[len - 1])) {
+ c = tolower(s[len - 1]);
+ switch (c) {
+ case 'g':
+ mult <<= 30;
+ break;
+ case 'm':
+ mult <<= 20;
+ break;
+ case 'k':
+ mult <<= 10;
+ break;
+ case 'b':
+ break;
+ default:
+ error("unknown size descriptor %c", c);
+ exit(1);
+ }
+ s[len - 1] = '\0';
+ }
+
+ return atoll(s) * mult;
+}
+
+int
+full_write(int fd, const void *buf, size_t count)
+{
+ int ret = 0;
+ const char *ptr = (const char *) buf;
+
+ while (count > 0) {
+ ssize_t n = write(fd, ptr, count);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ error("full_write failed as %s", strerror(errno));
+ ret = -1;
+ break;
+ }
+
+ if (n == 0)
+ break;
+
+ ptr += n;
+ count -= n;
+ }
+
+ return ret;
+}
+
+size_t
+get_block_size(int fd)
+{
+ struct stat st;
+ return (fstat(fd, &st) < 0) ? -1 : st.st_blksize;
+}
+
+/*
+ * Write out of all dirty pages in the specified range which are
+ * not presently submitted write-out.
+ * @offset: the starting byte of the file range to be synchronized.
+ * @nbytes: specifies the length of the range to be synchronized, in bytes;
+ * if nbytes is zero, then all bytes from offset through to the end
+ * of file are synchronized.
+ * @flags: by default, SYNC_FILE_RANGE_WRITE will be used.
+ */
+int
+writeout_dirty_pages(int fd, off_t offset, off_t nbytes,
+ unsigned int flags)
+{
+ if (sync_file_range(fd, offset, nbytes, flags) < 0) {
+ error("sync file range failed as %s", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static ssize_t
+do_pwrite(int fd, const void *buf, size_t count, off_t offset)
+{
+ ssize_t ret, written = 0;
+
+ while (written < count) {
+ ret = pwrite(fd, buf + written, count - written, offset + written);
+ if (ret < 0) {
+ error("Failed to write %ld bytes as %s",
+ (long long)count, strerror(errno));
+ return ret;
+ }
+
+ written += ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Write N_BYTES zero bytes to file descriptor FD. Return true if successful.
+ * Upon write failure, set errno and return false.
+ * Copied from cp(1).
+ */
+static int
+write_zeros(int fd, uint64_t n_bytes)
+{
+ static char *zeros;
+ static size_t nz = BUF_SIZE;
+
+ /*
+ * Attempt to use a relatively large calloc'd source buffer for
+ * efficiency, but if that allocation fails, resort to a smaller
+ * statically allocated one.
+ */
+ if (zeros == NULL) {
+ static char fallback[1024];
+ zeros = calloc(nz, 1);
+ if (zeros == NULL) {
+ zeros = fallback;
+ nz = sizeof(fallback);
+ }
+ }
+
+ while (n_bytes) {
+ uint64_t n = MIN(nz, n_bytes);
+ if ((full_write(fd, zeros, n)) < 0)
+ return -1;
+ n_bytes -= n;
+ }
+
+ return 0;
+}
+
+/*
+ * Produce a sparse file with data extents and holes.
+ * @len: the maximum length of the produced file.
+ * @start_offset: seek to here first to write data.
+ * @skip_bytes: for the next lseek(2) operation, we need to
+ * skip the number of bytes to create holes.
+ * @data_len: how many bytes for each write(2).
+ */
+int
+create_data_and_holes(int fd, size_t nr_total_bytes, off_t start_offset,
+ uint64_t nr_skip_bytes, uint64_t nr_data_bytes,
+ int wrote_hole_at_eof)
+{
+ int ret = 0;
+ off_t total = nr_total_bytes;
+ off_t data_len = nr_data_bytes;
+ off_t off = start_offset;
+ char buf[4096];
+
+ memset(buf, 'A', sizeof(buf));
+
+ total -= start_offset;
+ while (total > 0) {
+ do {
+ ssize_t nr_write = do_pwrite(fd, buf, sizeof(buf), off);
+ if (nr_write < 0) {
+ error("do_pwrite() failed as %s", strerror(errno));
+ ret = -1;
+ goto out;
+ }
+ if (nr_write == 0)
+ break;
+
+ off += nr_write;
+ data_len -= nr_write;
+ } while (data_len > 0);
+
+ off += (nr_skip_bytes + nr_data_bytes);
+ total -= off;
+ }
+
+ if (off < nr_total_bytes) {
+ if (wrote_hole_at_eof) {
+ ret = ftruncate(fd, nr_total_bytes);
+ if (ret < 0)
+ error("truncate source file to %lld bytes failed as %s",
+ (long long)nr_total_bytes, strerror(errno));
+ }
+
+ ret = write_zeros(fd, nr_total_bytes - off);
+ if (ret < 0)
+ error("write_zeros to end of file failed as %s",
+ strerror(errno));
+ }
+
+out:
+ return ret;
+}
+
+/*
+ * Copy a data extent from source file to dest file.
+ * @data_off: data offset
+ * @hole_off: hole offset
+ * The length of this extent is (hole_off - data_off).
+ */
+int
+do_extent_copy(int src_fd, int dest_fd, off_t data_off, off_t hole_off)
+{
+ uint64_t len = (uint64_t)(hole_off - data_off);
+ char buf[BUF_SIZE];
+ int ret;
+
+ /* Seek to data_off for data reading */
+ ret = lseek(src_fd, data_off, SEEK_SET);
+ if (ret < 0) {
+ error("seek source file to %llu failed as %s",
+ (uint64_t)data_off, strerror(errno));
+ return ret;
+ }
+
+ /* Seek to data_off for data writing, make holes as well */
+ ret = lseek(dest_fd, data_off, SEEK_SET);
+ if (ret < 0) {
+ error("seek dest file to %llu failed as %s",
+ (uint64_t)data_off, strerror(errno));
+ return ret;
+ }
+
+ while (len > 0) {
+ memset(buf, 0, sizeof(buf));
+ ssize_t n_read = read(src_fd, buf, BUF_SIZE);
+ if (n_read < 0) {
+ if (errno == EINTR)
+ continue;
+
+ error("read source file extent failed as %s",
+ strerror(errno));
+ return n_read;
+ }
+
+ if (n_read == 0)
+ break;
+
+ ret = full_write(dest_fd, buf, n_read);
+ if (ret < 0) {
+ error("write data to dest file failed as %s",
+ strerror(errno));
+ return ret;
+ }
+
+ len -= n_read;
+ }
+
+ return ret;
+}
+
+/*
+ * If lseek(2) failed and the errno is set to ENXIO, for
+ * SEEK_DATA there are no more data regions past the supplied
+ * offset. For SEEK_HOLE, there are no more holes past the
+ * supplied offset. Set scan->hit_final_extent to true for
+ * either case.
+ */
+int
+copy_extents(int src_fd, int dest_fd, off_t src_total_size)
+{
+ int ret = 0;
+ unsigned int i = 0;
+ off_t seek_start = 0;
+ off_t dest_pos = 0;
+ off_t data_pos, hole_pos;
+
+ do {
+ data_pos = lseek(src_fd, seek_start, SEEK_DATA);
+ if (data_pos < 0) {
+ if (errno == ENXIO)
+ ret = 0;
+ else {
+ error("SEEK_DATA failed due to %s",
+ strerror(errno));
+ ret = -1;
+ }
+ break;
+ }
+
+ hole_pos = lseek(src_fd, data_pos, SEEK_HOLE);
+ if (hole_pos < 0) {
+ if (errno == ENXIO)
+ ret = 0;
+ else {
+ error("SEEK_HOLE failed due to %s\n",
+ strerror(errno));
+ ret = -1;
+ }
+ break;
+ }
+
+ /* do extent copy */
+ ret = do_extent_copy(src_fd, dest_fd, data_pos, hole_pos);
+ if (ret < 0) {
+ error("copy extent failed");
+ break;
+ }
+
+ dest_pos += (hole_pos - data_pos);
+ ++i;
+ seek_start = hole_pos;
+ } while (seek_start < src_total_size);
+
+ if (dest_pos < src_total_size)
+ ftruncate(dest_fd, src_total_size);
+
+ return ret;
+}
+
+static struct option const longopts[] = {
+ {"fallocate", no_argument, NULL, 'P'},
+ {"falloc-offset", required_argument, NULL, 'O'},
+ {"falloc-length", required_argument, NULL, 'L'},
+ {"falloc-keep-size", no_argument, NULL, 'N'},
+ {"max-file-size", required_argument, NULL, 'M'},
+ {"write-start-offset", required_argument, NULL, 's'},
+ {"write-skip-bytes", required_argument, NULL, 'k'},
+ {"write-bytes", required_argument, NULL, 'l'},
+ {"sync-dirty-pages", no_argument, NULL, 'S'},
+ {"sync-page-offset", required_argument, NULL, 'p'},
+ {"sync-page-bytes", required_argument, NULL, 'b'},
+ {"sync-page-flags", required_argument, NULL, 'f'},
+ {"wrote-hole-at-eof", no_argument, NULL, 'E'},
+ {NULL, 0, NULL, 0}
+};
+
+void
+usage(const char *progname)
+{
+ fprintf(stdout, "Usage: %s [OPTION]... SOURCE DEST\n"
+ " [-P] [-O falloc-offset] [-L falloc-length] [-N]\n"
+ " [-M max-file-size]\n"
+ " [-s write-start-offset] [-k write-skip-bytes] [-l write-bytes]\n"
+ " [-S] [-p sync-page-offset] [-b sync-page-bytes] [-f sync-page-flags]\n"
+ " [-E wrote-hole-at-eof]\n"
+ " -L, --falloc-length=LENGTH fallocate length\n"
+ " -O, --falloc-offset=OFFSET fallocate offset\n"
+ " -M, --max-file-size=LENGTH maximum size of file, don't required in preallocation mode\n"
+ " -s, --write-start-offset=OFFSET make holes at the begnning of source file\n"
+ " -k, --write-skip-bytes=BYTES skip a range of bytes for next write\n"
+ " -l, --write-bytes=BYTES create data extent in number of bytes\n"
+ " -p, --sync-page-offset=OFFSET sync dirty pages from where\n"
+ " -b, --sync-page-bytes=BYTES sync dirty pages for a number of bytes\n"
+ " -f, --sync-page-flags=FLAGS sync dirty pages in which mode\n"
+ " -P preallocate space for source file\n"
+ " -N fallocate(2) in keep size mode\n"
+ " -S sync out dirty pages\n"
+ " -E make hole at the end of source file in non-preallocate mode\n",
+ progname);
+
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int opt;
+ int src_fd;
+ int dest_fd;
+ int ret = 0;
+ int do_falloc = 0;
+ int falloc_mode = 0;
+ int do_sync_dirty_pages = 0;
+ int wrote_hole_at_eof = 0;
+ unsigned int sync_page_flags = 0;
+ size_t src_total_size;
+ size_t max_file_size = 0;
+ off_t falloc_length = 0;
+ off_t falloc_offset = 0;
+ off_t seek_start_offset = 0;
+ off_t seek_skip_bytes = 0;
+ off_t sync_page_offset = 0;
+ off_t sync_page_bytes = 0;
+ uint64_t seek_write_bytes = 0;
+ char *src_file = NULL;
+ char *dest_file = NULL;
+ struct stat st;
+
+ while ((opt = getopt_long(argc, argv, "PO:L:NM:s:k:l:Sp:b:f:E",
+ longopts, NULL)) != -1) {
+ switch(opt) {
+ case 'P':
+ do_falloc = 1;
+ break;
+ case 'O':
+ /* Preallocate disk space from where */
+ falloc_offset = parse_size(optarg);
+ assert(falloc_offset <= OFF_T_MAX);
+ break;
+ case 'L':
+ /* Preallocate disk space length */
+ falloc_length = parse_size(optarg);
+ assert(falloc_offset <= OFF_T_MAX);
+ break;
+ case 'N':
+ /* Preallocation in KEEP_FILE_SIZE mode */
+ falloc_mode = FALLOC_FL_KEEP_SIZE;
+ break;
+ case 'M':
+ /*
+ * The maximum length of source file, only valid
+ * if the source file created in non-preallocation
+ * mode. Otherwise, it will be set to falloc_length.
+ */
+ max_file_size = parse_size(optarg);
+ case 's':
+ /*
+ * Seek to where for the first write. It will create
+ * a hole at the beginning of the source file if this
+ * option was specified. It can be ignored if you don't
+ * want that.
+ */
+ seek_start_offset = parse_size(optarg);
+ assert(seek_start_offset <= OFF_T_MAX);
+ break;
+ case 'k':
+ /*
+ * Skip the number of bytes for the next data write.
+ * It is used to create holes in the middle of file.
+ * If this option was not specified, using blocksize
+ * instead.
+ */
+ seek_skip_bytes = parse_size(optarg);
+ assert(seek_start_offset <= OFF_T_MAX);
+ break;
+ case 'l':
+ /*
+ * Write the number of bytes after seeking, we
+ * we can make the disk fragmented as much as
+ * possbile by tweaking up this value.
+ */
+ seek_write_bytes = parse_size(optarg);
+ break;
+ case 'S':
+ /*
+ * Call sync_file_range(2) to writeout dirty
+ * pages. It can be used to test WRITEBACK pages
+ * probing branch.
+ */
+ do_sync_dirty_pages = 1;
+ break;
+ case 'p':
+ /*
+ * Sync out dirty pages starting from where, sync
+ * from 0 if it was not specified.
+ */
+ sync_page_offset = parse_size(optarg);
+ break;
+ case 'b':
+ /*
+ * If it was not specified, sync out pages from
+ * above offset to the end of file.
+ */
+ sync_page_bytes = parse_size(optarg);
+ break;
+ case 'f':
+ /*
+ * By default, SYNC_FILE_RANGE_WRITE will be used if
+ * this option was not specified.
+ */
+ sync_page_flags = (unsigned int)atol(optarg);
+ break;
+ case 'E':
+ wrote_hole_at_eof = 1;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (argc - optind < 2) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ src_file = strdup(argv[argc - 2]);
+ if (!src_file) {
+ usage(argv[0]);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ dest_file = strdup(argv[argc - 1]);
+ if (!dest_file) {
+ usage(argv[0]);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (!do_falloc && (falloc_length || falloc_offset)) {
+ error("Invalid arguments, missing -F or --fallocate option "
+ "for fallocation tests");
+ usage(argv[0]);
+ goto out;
+ }
+
+ if (do_falloc && !falloc_length) {
+ error("Invalid arguments, fallocate length must be specified "
+ "for fallocation tests");
+ usage(argv[0]);
+ goto out;
+ }
+
+ if (!do_falloc && !max_file_size) {
+ error("Invalid arguments, missing -M or --max-file-size option "
+ "in none-fallocate mode");
+ usage(argv[0]);
+ goto out;
+ }
+
+ if (falloc_length && max_file_size) {
+ error("Invalid arguments, don't combine -M with -F options");
+ usage(argv[0]);
+ goto out;
+ }
+
+ if (!do_sync_dirty_pages && (sync_page_offset ||
+ sync_page_bytes ||
+ sync_page_flags)) {
+ error("Invalid argument, missing -S or --sync-pages option "
+ "for sync file range tests");
+ usage(argv[0]);
+ return 1;
+ }
+
+ if (do_sync_dirty_pages && !sync_page_flags)
+ sync_page_flags = SYNC_FILE_RANGE_WRITE;
+
+ src_fd = open(src_file, O_RDWR|O_CREAT|O_TRUNC, 0644);
+ if (src_fd < 0) {
+ error("create %s failed as %s", src_file, strerror(errno));
+ goto out;
+ }
+
+ dest_fd = open(dest_file, O_RDWR|O_CREAT|O_TRUNC, 0644);
+ if (dest_fd < 0) {
+ error("create %s failed as %s", dest_file, strerror(errno));
+ close(src_fd);
+ goto close_src_fd;
+ }
+
+ if (do_falloc) {
+ /* Preallocate space for source file */
+ ret = fallocate(src_fd, falloc_mode,
+ falloc_offset, falloc_length);
+ if (ret < 0) {
+ error("fallocate file %s failed as %s",
+ src_file, strerror(errno));
+ goto close_dest_fd;
+ }
+ max_file_size = falloc_length;
+ }
+
+ /*
+ * If seek_write_bytes was not specified, fill up data extent
+ * to st.st_blksize for each write.
+ */
+ if (!seek_write_bytes) {
+ seek_write_bytes = get_block_size(src_fd);
+ if (seek_write_bytes < 0) {
+ error("get %s block size failed as %s",
+ src_file, strerror(errno));
+ goto close_dest_fd;
+ }
+ }
+
+ /*
+ * Seek to seek_offset, write seek_write_len to dest file,
+ * skip seek_skip_len for next write.
+ */
+ ret = create_data_and_holes(src_fd, max_file_size,
+ seek_start_offset,
+ seek_skip_bytes,
+ seek_write_bytes,
+ wrote_hole_at_eof);
+ if (ret < 0) {
+ error("seek write to %s failed", src_file);
+ goto close_dest_fd;
+ }
+
+ if (do_sync_dirty_pages) {
+ ret = writeout_dirty_pages(src_fd, sync_page_offset,
+ sync_page_bytes, sync_page_flags);
+ if (ret < 0) {
+ error("write out dirty pages failed as %s",
+ strerror(errno));
+ goto close_dest_fd;
+ }
+ }
+
+ /*
+ * Note that if the source file created in non-fallocte mode,
+ * the source file size might less than the max_file_size.
+ */
+ ret = fstat(src_fd, &st);
+ if (ret < 0) {
+ error("get file %s staticis failed as %s",
+ src_file, strerror(errno));
+ goto close_dest_fd;
+ }
+
+ src_total_size = st.st_size;
+ ret = copy_extents(src_fd, dest_fd, src_total_size);
+ if (ret < 0)
+ error("extents copy failed");
+
+close_dest_fd:
+ close(dest_fd);
+close_src_fd:
+ close(src_fd);
+out:
+ if (src_file)
+ free(src_file);
+ if (dest_file)
+ free(dest_file);
+ return ret;
+}
--
1.7.4.1