Compare commits

...

15 Commits

Author SHA1 Message Date
Jim Meyering
f25181d32c build: distribute new test script, filefrag-extent-compare
* tests/Makefile.am (EXTRA_DIST): Add filefrag-extent-compare.
2010-06-13 16:34:42 +02:00
Jim Meyering
be5548445e build: distribute new file, fiemap.h
* src/Makefile.am (noinst_HEADERS): Add fiemap.h.
2010-06-13 16:19:29 +02:00
Jie Liu
98b2a24d7f copy.c: add FIEMAP_FLAG_SYNC to fiemap ioctl
* src/copy.c (fiemap_copy): Force kernel to sync the source
file before mapping.
2010-06-11 15:30:58 +02:00
Jim Meyering
484903dc41 tests: accommodate varying filefrag -v "flags" output
* tests/cp/sparse-fiemap: Accommodate values other than "eof"
in the "flags" column of filefrag -v output
2010-06-11 14:34:17 +02:00
Jim Meyering
20c1eeec11 fiemap.h: include <stdint.h>, not <linux/types.h>
* src/fiemap.h: Include stdint.h, not linux/types.h,
now that this file uses only portable type names.
2010-06-11 14:14:39 +02:00
Paul Eggert
f9daf7e7ed copy.c: ensure proper alignment of fiemap buffer
* src/copy.c (fiemap_copy): Ensure that our fiemap buffer
is large enough and well-aligned.
Replace "0LL" with equivalent "0" as 3rd argument to lseek.
2010-06-11 14:14:39 +02:00
Jim Meyering
1a2b6d0938 copy.c: adjust comments, tweak semantics
* src/copy.c (fiemap_copy): Rename from fiemap_copy_ok.
Add/improve comments.
Remove local, "fail".
(fiemap_copy): Do not require caller to set
"normal_copy_required" before calling fiemap_copy.
Report ioctl failure if it's the 2nd or subsequent call.
2010-06-11 14:14:39 +02:00
Jim Meyering
e955826e4c tests: improve fiemap test to work with 4 FS types; fall back on ext4
* tests/cp/sparse-fiemap: Improve.
* tests/filefrag-extent-compare: New file.
2010-06-11 14:10:57 +02:00
Jim Meyering
50975af729 tests: relax the root-tests cross-check
* cfg.mk (sc_root_tests): Allow spaces before "require_root_",
now that tests/cp/sparse-fiemap has a conditional use.
2010-06-11 14:10:57 +02:00
Jim Meyering
912e7d26c6 tests: test fiemap-enabled cp more thoroughly
* tests/cp/sparse-fiemap: More tests.
2010-06-11 14:10:57 +02:00
Jim Meyering
e62e22b798 tests: require root only if current partition is neither btrfs nor xfs
* tests/cp/sparse-fiemap: Don't require root access if current
partition is btrfs or xfs.
Use init.sh, not test-lib.sh.
2010-06-11 14:10:57 +02:00
Jim Meyering
578db289bb tests: exercise more of the new FIEMAP copying code
* tests/cp/sparse-fiemap: Ensure that a file with many extents (more
than fit in copy.c's internal 4KiB buffer) is copied properly.
2010-06-11 14:10:57 +02:00
Jim Meyering
9cc9dbaf5b tests: sparse-fiemap: factor out some set-up
* tests/cp/sparse-fiemap: Cd into test directory sooner.
2010-06-11 14:10:57 +02:00
Jie Liu
e3dca50c2a tests: add a new test for FIEMAP-copy
* tests/cp/sparse-fiemap: Add a new test for FIEMAP-copy against a
loopbacked ext4 partition.
* tests/Makefile.am (sparse-fiemap): Reference the new test.
2010-06-11 14:10:57 +02:00
Jie Liu
1ba1e9ae94 cp: Add FIEMAP support for efficient sparse file copy
* src/fiemap.h: Add fiemap.h for fiemap ioctl(2) support.
Copied from linux's include/linux/fiemap.h, with minor formatting changes.
* src/copy.c (copy_reg): Now, when `cp' invoked with --sparse=[WHEN] option, we
will try to do FIEMAP-copy if the underlaying file system support it, fall back
to a normal copy if it fails.
2010-06-11 14:10:57 +02:00
7 changed files with 464 additions and 1 deletions

2
cfg.mk
View File

@@ -80,7 +80,7 @@ sc_root_tests:
@if test -d tests \
&& grep check-root tests/Makefile.am>/dev/null 2>&1; then \
t1=sc-root.expected; t2=sc-root.actual; \
grep -nl '^require_root_$$' \
grep -nl '^ *require_root_$$' \
$$($(VC_LIST) tests) |sed s,tests/,, |sort > $$t1; \
sed -n '/^root_tests =[ ]*\\$$/,/[^\]$$/p' \
$(srcdir)/tests/Makefile.am \

View File

@@ -145,6 +145,7 @@ noinst_HEADERS = \
copy.h \
cp-hash.h \
dircolors.h \
fiemap.h \
fs.h \
group-list.h \
ls.h \

View File

@@ -63,6 +63,10 @@
#include <sys/ioctl.h>
#ifndef HAVE_FIEMAP
# include "fiemap.h"
#endif
#ifndef HAVE_FCHOWN
# define HAVE_FCHOWN false
# define fchown(fd, uid, gid) (-1)
@@ -149,6 +153,153 @@ clone_file (int dest_fd, int src_fd)
#endif
}
#ifdef __linux__
# ifndef FS_IOC_FIEMAP
# define FS_IOC_FIEMAP _IOWR ('f', 11, struct fiemap)
# endif
/* Perform a FIEMAP copy, if possible.
Call ioctl(2) with FS_IOC_FIEMAP (available in linux 2.6.27) to
obtain a map of file extents excluding holes. This avoids the
overhead of detecting holes in a hole-introducing/preserving copy,
and thus makes copying sparse files much more efficient. Upon a
successful copy, return true. If the initial ioctl fails, set
*NORMAL_COPY_REQUIRED to true and return false. Upon any other
failure, set *NORMAL_COPY_REQUIRED to false and return false. */
static bool
fiemap_copy (int src_fd, int dest_fd, size_t buf_size,
off_t src_total_size, char const *src_name,
char const *dst_name, bool *normal_copy_required)
{
bool last = false;
union { struct fiemap f; char c[4096]; } fiemap_buf;
struct fiemap *fiemap = &fiemap_buf.f;
struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
enum { count = (sizeof fiemap_buf - sizeof *fiemap) / sizeof *fm_ext };
verify (count != 0);
off_t last_ext_logical = 0;
uint64_t last_ext_len = 0;
uint64_t last_read_size = 0;
unsigned int i = 0;
*normal_copy_required = false;
/* This is required at least to initialize fiemap->fm_start,
but also serves (in mid 2010) to appease valgrind, which
appears not to know the semantics of the FIEMAP ioctl. */
memset (&fiemap_buf, 0, sizeof fiemap_buf);
do
{
fiemap->fm_length = FIEMAP_MAX_OFFSET;
fiemap->fm_flags = FIEMAP_FLAG_SYNC;
fiemap->fm_extent_count = count;
/* When ioctl(2) fails, fall back to the normal copy only if it
is the first time we met. */
if (ioctl (src_fd, FS_IOC_FIEMAP, fiemap) < 0)
{
/* If the first ioctl fails, tell the caller that it is
ok to proceed with a normal copy. */
if (i == 0)
*normal_copy_required = true;
else
{
/* If the second or subsequent ioctl fails, diagnose it,
since it ends up causing the entire copy/cp to fail. */
error (0, errno, _("%s: FIEMAP ioctl failed"), quote (src_name));
}
return false;
}
/* If 0 extents are returned, then more ioctls are not needed. */
if (fiemap->fm_mapped_extents == 0)
break;
for (i = 0; i < fiemap->fm_mapped_extents; i++)
{
assert (fm_ext[i].fe_logical <= OFF_T_MAX);
off_t ext_logical = fm_ext[i].fe_logical;
uint64_t ext_len = fm_ext[i].fe_length;
if (lseek (src_fd, ext_logical, SEEK_SET) < 0)
{
error (0, errno, _("cannot lseek %s"), quote (src_name));
return false;
}
if (lseek (dest_fd, ext_logical, SEEK_SET) < 0)
{
error (0, errno, _("cannot lseek %s"), quote (dst_name));
return false;
}
if (fm_ext[i].fe_flags & FIEMAP_EXTENT_LAST)
{
last_ext_logical = ext_logical;
last_ext_len = ext_len;
last = true;
}
while (ext_len)
{
char buf[buf_size];
/* Avoid reading into the holes if the left extent
length is shorter than the buffer size. */
if (ext_len < buf_size)
buf_size = ext_len;
ssize_t n_read = read (src_fd, buf, buf_size);
if (n_read < 0)
{
#ifdef EINTR
if (errno == EINTR)
continue;
#endif
error (0, errno, _("reading %s"), quote (src_name));
return false;
}
if (n_read == 0)
{
/* Figure out how many bytes read from the last extent. */
last_read_size = last_ext_len - ext_len;
break;
}
if (full_write (dest_fd, buf, n_read) != n_read)
{
error (0, errno, _("writing %s"), quote (dst_name));
return false;
}
ext_len -= n_read;
}
}
fiemap->fm_start = fm_ext[i - 1].fe_logical + fm_ext[i - 1].fe_length;
} while (! last);
/* If a file ends up with holes, the sum of the last extent logical offset
and the read-returned size will be shorter than the actual size of the
file. Use ftruncate to extend the length of the destination file. */
if (last_ext_logical + last_read_size < src_total_size)
{
if (ftruncate (dest_fd, src_total_size) < 0)
{
error (0, errno, _("failed to extend %s"), quote (dst_name));
return false;
}
}
return true;
}
#else
static bool fiemap_copy (ignored) { errno == ENOTSUP; return false; }
#endif
/* FIXME: describe */
/* FIXME: rewrite this to use a hash table so we avoid the quadratic
performance hit that's probably noticeable only on trees deeper
@@ -679,6 +830,25 @@ copy_reg (char const *src_name, char const *dst_name,
#endif
}
if (make_holes)
{
bool require_normal_copy;
/* Perform efficient FIEMAP copy for sparse files, fall back to the
standard copy only if the ioctl(2) fails. */
if (fiemap_copy (source_desc, dest_desc, buf_size,
src_open_sb.st_size, src_name,
dst_name, &require_normal_copy))
goto preserve_metadata;
else
{
if (! require_normal_copy)
{
return_val = false;
goto close_src_and_dst_desc;
}
}
}
/* If not making a sparse file, try to use a more-efficient
buffer size. */
if (! make_holes)
@@ -807,6 +977,7 @@ copy_reg (char const *src_name, char const *dst_name,
}
}
preserve_metadata:
if (x->preserve_timestamps)
{
struct timespec timespec[2];

102
src/fiemap.h Normal file
View File

@@ -0,0 +1,102 @@
/* FS_IOC_FIEMAP ioctl infrastructure.
Some portions copyright (C) 2007 Cluster File Systems, Inc
Authors: Mark Fasheh <mfasheh@suse.com>
Kalpak Shah <kalpak.shah@sun.com>
Andreas Dilger <adilger@sun.com>. */
/* Copy from kernel, modified to respect GNU code style by Jie Liu. */
#ifndef _LINUX_FIEMAP_H
# define _LINUX_FIEMAP_H
# include <stdint.h>
struct fiemap_extent
{
/* Logical offset in bytes for the start of the extent
from the beginning of the file. */
uint64_t fe_logical;
/* Physical offset in bytes for the start of the extent
from the beginning of the disk. */
uint64_t fe_physical;
/* Length in bytes for this extent. */
uint64_t fe_length;
uint64_t fe_reserved64[2];
/* FIEMAP_EXTENT_* flags for this extent. */
uint32_t fe_flags;
uint32_t fe_reserved[3];
};
struct fiemap
{
/* Logical offset(inclusive) at which to start mapping(in). */
uint64_t fm_start;
/* Logical length of mapping which userspace wants(in). */
uint64_t fm_length;
/* FIEMAP_FLAG_* flags for request(in/out). */
uint32_t fm_flags;
/* Number of extents that were mapped(out). */
uint32_t fm_mapped_extents;
/* Size of fm_extents array(in). */
uint32_t fm_extent_count;
uint32_t fm_reserved;
/* Array of mapped extents(out). */
struct fiemap_extent fm_extents[0];
};
/* The maximum offset can be mapped for a file. */
# define FIEMAP_MAX_OFFSET (~0ULL)
/* Sync file data before map. */
# define FIEMAP_FLAG_SYNC 0x00000001
/* Map extented attribute tree. */
# define FIEMAP_FLAG_XATTR 0x00000002
# define FIEMAP_FLAGS_COMPAT (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_XATTR)
/* Last extent in file. */
# define FIEMAP_EXTENT_LAST 0x00000001
/* Data location unknown. */
# define FIEMAP_EXTENT_UNKNOWN 0x00000002
/* Location still pending, Sets EXTENT_UNKNOWN. */
# define FIEMAP_EXTENT_DELALLOC 0x00000004
/* Data can not be read while fs is unmounted. */
# define FIEMAP_EXTENT_ENCODED 0x00000008
/* Data is encrypted by fs. Sets EXTENT_NO_BYPASS. */
# define FIEMAP_EXTENT_DATA_ENCRYPTED 0x00000080
/* Extent offsets may not be block aligned. */
# define FIEMAP_EXTENT_NOT_ALIGNED 0x00000100
/* Data mixed with metadata. Sets EXTENT_NOT_ALIGNED. */
# define FIEMAP_EXTENT_DATA_INLINE 0x00000200
/* Multiple files in block. Set EXTENT_NOT_ALIGNED. */
# define FIEMAP_EXTENT_DATA_TAIL 0x00000400
/* Space allocated, but not data (i.e. zero). */
# define FIEMAP_EXTENT_UNWRITTEN 0x00000800
/* File does not natively support extents. Result merged for efficiency. */
# define FIEMAP_EXTENT_MERGED 0x00001000
/* Space shared with other files. */
# define FIEMAP_EXTENT_SHARED 0x00002000
#endif

View File

@@ -10,6 +10,7 @@ EXTRA_DIST = \
CuTmpdir.pm \
check.mk \
envvar-check \
filefrag-extent-compare \
init.sh \
lang-default \
other-fs-tmpdir \
@@ -25,6 +26,7 @@ root_tests = \
cp/special-bits \
cp/cp-mv-enotsup-xattr \
cp/capability \
cp/sparse-fiemap \
dd/skip-seek-past-dev \
install/install-C-root \
ls/capability \

119
tests/cp/sparse-fiemap Executable file
View File

@@ -0,0 +1,119 @@
#!/bin/sh
# Test cp --sparse=always through fiemap copy
# Copyright (C) 2010 Free Software Foundation, Inc.
# 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, either version 3 of the License, or
# (at your option) any later version.
# 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, see <http://www.gnu.org/licenses/>.
if test "$VERBOSE" = yes; then
set -x
cp --version
fi
. "${srcdir=.}/init.sh"; path_prepend_ ../src
if df -T -t btrfs -t xfs -t ext4 -t ocfs2 . ; then
: # Current dir is on a partition with working extents. Good!
else
# It's not; we need to create one, hence we need root access.
require_root_
cwd=$PWD
cleanup_() { cd /; umount "$cwd/mnt"; }
skip=0
# Create an ext4 loopback file system
dd if=/dev/zero of=blob bs=32k count=1000 || skip=1
mkdir mnt
mkfs -t ext4 -F blob ||
skip_test_ "failed to create ext4 file system"
mount -oloop blob mnt || skip=1
cd mnt || skip=1
echo test > f || skip=1
test -s f || skip=1
test $skip = 1 &&
skip_test_ "insufficient mount/ext4 support"
fi
# Create a 1TiB sparse file
dd if=/dev/zero of=sparse bs=1k count=1 seek=1G || framework_failure
# It takes many minutes to copy this sparse file using the old method.
# By contrast, it takes far less than 1 second using FIEMAP-copy.
timeout 10 cp --sparse=always sparse fiemap || fail=1
# Ensure that the sparse file copied through fiemap has the same size
# in bytes as the original.
test $(stat --printf %s sparse) = $(stat --printf %s fiemap) || fail=1
# =================================================
# Ensure that we exercise the FIEMAP-copying code enough
# to provoke at least two iterations of the do...while loop
# in which it calls ioctl (fd, FS_IOC_FIEMAP,...
# This also verifies that non-trivial extents are preserved.
$PERL -e 1 || skip_test_ 'skipping part of this test; you lack perl'
# Extract logical block number and length pairs from filefrag -v output.
# The initial sed is to remove the "eof" from the normally-empty "flags" field.
# Similarly, remove flags values like "unknown,delalloc,eof".
# That is required when that final extent has no number in the "expected" field.
f()
{
sed 's/ [a-z,][a-z,]*$//' $@ \
| awk '/^ *[0-9]/ {printf "%d %d ", $2 ,NF < 5 ? $NF : $5 } END {print ""}'
}
for i in $(seq 1 2 21); do
for j in 1 2 31 100; do
$PERL -e 'BEGIN { $n = '$i' * 1024; *F = *STDOUT }' \
-e 'for (1..'$j') { sysseek (*F, $n, 1)' \
-e '&& syswrite (*F, chr($_)x$n) or die "$!"}' > j1 || fail=1
# sync
cp --sparse=always j1 j2 || fail=1
# sync
# Technically we may need the 'sync' uses above, but
# uncommenting them makes this test take much longer.
cmp j1 j2 || fail=1
filefrag -v j1 | grep extent \
|| skip_test_ 'skipping part of this test; you lack filefrag'
# Here is sample filefrag output:
# $ perl -e 'BEGIN{$n=16*1024; *F=*STDOUT}' \
# -e 'for (1..5) { sysseek(*F,$n,1)' \
# -e '&& syswrite *F,"."x$n or die "$!"}' > j
# $ filefrag -v j
# File system type is: ef53
# File size of j is 163840 (40 blocks, blocksize 4096)
# ext logical physical expected length flags
# 0 4 6258884 4
# 1 12 6258892 6258887 4
# 2 20 6258900 6258895 4
# 3 28 6258908 6258903 4
# 4 36 6258916 6258911 4 eof
# j: 6 extents found
# exclude the physical block numbers; they always differ
filefrag -v j1 > ff1 || fail=1
filefrag -v j2 > ff2 || fail=1
{ f ff1; f ff2; } \
| $PERL $abs_top_srcdir/tests/filefrag-extent-compare \
|| { fail=1; break; }
done
test $fail = 1 && break
done
Exit $fail

View File

@@ -0,0 +1,68 @@
eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}'
& eval 'exec perl -wS "$0" $argv:q'
if 0;
# Determine whether two files have the same extents by comparing
# the logical block numbers and lengths from filefrag -v for each.
# Invoke like this:
# This helper function, f, extracts logical block number and lengths.
# f() { awk '/^ *[0-9]/ {printf "%d %d ",$2,NF<5?$NF:$5} END {print ""}'; }
# { filefrag -v j1 | f; filefrag -v j2 | f; } | ./filefrag-extent-compare
use warnings;
use strict;
(my $ME = $0) =~ s|.*/||;
my @line = <>;
my $n_lines = @line;
$n_lines == 2
or die "$ME: expected exactly two input lines; got $n_lines\n";
my @A = split ' ', $line[0];
my @B = split ' ', $line[1];
@A % 2 || @B % 2
and die "$ME: unexpected input: odd number of numbers; expected even\n";
my @a;
my @b;
foreach my $i (0..@A/2-1) { $a[$i] = { L_BLK => $A[2*$i], LEN => $A[2*$i+1] } };
foreach my $i (0..@B/2-1) { $b[$i] = { L_BLK => $B[2*$i], LEN => $B[2*$i+1] } };
my $i = 0;
my $j = 0;
while (1)
{
!defined $a[$i] && !defined $b[$j]
and exit 0;
defined $a[$i] && defined $b[$j]
or die "\@a and \@b have different lengths, even after adjustment\n";
($a[$i]->{L_BLK} == $b[$j]->{L_BLK}
&& $a[$i]->{LEN} == $b[$j]->{LEN})
and next;
($a[$i]->{LEN} < $b[$j]->{LEN}
&& exists $a[$i+1] && $a[$i]->{LEN} + $a[$i+1]->{LEN} == $b[$j]->{LEN})
and ++$i, next;
exists $b[$j+1] && $a[$i]->{LEN} == $b[$i]->{LEN} + $b[$i+1]->{LEN}
and ++$j, next;
die "differing extent:\n"
. " [$i]=$a[$i]->{L_BLK} $a[$i]->{LEN}\n"
. " [$j]=$b[$j]->{L_BLK} $b[$j]->{LEN}\n"
}
continue
{
++$i;
++$j;
}
### Setup "GNU" style for perl-mode and cperl-mode.
## Local Variables:
## mode: perl
## perl-indent-level: 2
## perl-continued-statement-offset: 2
## perl-continued-brace-offset: 0
## perl-brace-offset: 0
## perl-brace-imaginary-offset: 0
## perl-label-offset: -2
## perl-extra-newline-before-brace: t
## perl-merge-trailing-else: nil
## End: