mirror of
git://git.sv.gnu.org/coreutils.git
synced 2026-02-12 10:22:38 +02:00
cp: copy sparse files efficiently using the FIEMAP ioctl
* 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' is invoked with --sparse=[WHEN], we will try to do FIEMAP-copy if the underlying file system supports it, and fall back to a normal copy if it fails.
This commit is contained in:
159
src/copy.c
159
src/copy.c
@@ -62,6 +62,10 @@
|
||||
# include "verror.h"
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_FIEMAP
|
||||
# include "fiemap.h"
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_FCHOWN
|
||||
# define HAVE_FCHOWN false
|
||||
# define fchown(fd, uid, gid) (-1)
|
||||
@@ -148,6 +152,141 @@ 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 FIEMAP(available in mainline 2.6.27) copy if possible.
|
||||
Call ioctl(2) with FS_IOC_FIEMAP to efficiently map file allocation
|
||||
excepts holes. So the overhead to deal with holes with lseek(2) in
|
||||
normal copy could be saved. This would result in much faster backups
|
||||
for any kind of sparse file. */
|
||||
static bool
|
||||
fiemap_copy_ok (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 fail = false;
|
||||
bool last = false;
|
||||
char fiemap_buf[4096];
|
||||
struct fiemap *fiemap = (struct fiemap *)fiemap_buf;
|
||||
struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
|
||||
uint32_t count = (sizeof (fiemap_buf) - sizeof (*fiemap)) /
|
||||
sizeof (struct fiemap_extent);
|
||||
off_t last_ext_logical = 0;
|
||||
uint64_t last_ext_len = 0;
|
||||
uint64_t last_read_size = 0;
|
||||
unsigned int i = 0;
|
||||
|
||||
/* This is required at least to initialize fiemap->fm_start,
|
||||
but also serves (in May 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_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 `i > 0', then at least one ioctl(2) has been performed before. */
|
||||
if (i == 0)
|
||||
*normal_copy_required = true;
|
||||
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) < 0LL)
|
||||
{
|
||||
error (0, errno, _("cannot lseek %s"), quote (src_name));
|
||||
return fail;
|
||||
}
|
||||
|
||||
if (lseek (dest_fd, ext_logical, SEEK_SET) < 0LL)
|
||||
{
|
||||
error (0, errno, _("cannot lseek %s"), quote (dst_name));
|
||||
return fail;
|
||||
}
|
||||
|
||||
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 fail;
|
||||
}
|
||||
|
||||
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 fail;
|
||||
}
|
||||
|
||||
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, _("extending %s"), quote (dst_name));
|
||||
return fail;
|
||||
}
|
||||
}
|
||||
|
||||
return ! fail;
|
||||
}
|
||||
#else
|
||||
static bool fiemap_copy_ok (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
|
||||
@@ -676,6 +815,25 @@ copy_reg (char const *src_name, char const *dst_name,
|
||||
#endif
|
||||
}
|
||||
|
||||
if (make_holes)
|
||||
{
|
||||
bool require_normal_copy = false;
|
||||
/* Perform efficient FIEMAP copy for sparse files, fall back to the
|
||||
standard copy only if the ioctl(2) fails. */
|
||||
if (fiemap_copy_ok (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)
|
||||
@@ -804,6 +962,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
102
src/fiemap.h
Normal 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 <linux/types.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
|
||||
Reference in New Issue
Block a user