Subject: bug#6131: [PATCH]: fiemap support for efficient sparse file copy

From: "jeff.liu" <[email-masked]>
Date: Fri, 21 May 2010 23:51:03 +0800

Hi Jim,

This is the revised version, it fixed the fiemap-start offset calculation approach to remove it out
of the 'for (i = 0; i < fiemap->fm_mapped_extents; i++)' loop.

I have not got a 64bits machine for the testing at the moment, at the following, the first case only
run againt x86 with valgrind for the non-extent file copy, it works for me, could you help verify on
x64?

The second case is to test the non-sparse extents logical offset and length of the copied file are
identical to the source file, `ex' is test tool I write in C to show the extents info through FIEMAP
ioctl(2), it step through each extent of a file to examine and print out the logical offset/extent
length/physical offset.

jeff@jeff-laptop:~/opensource_dev/coreutils$ dd if=/dev/null of=/ext4/sp1 bs=1 seek=2G
jeff@jeff-laptop:~/opensource_dev/coreutils$ valgrind --version
valgrind-3.3.0-Debian
jeff@jeff-laptop:~/opensource_dev/coreutils$ valgrind ./src/cp --sparse=always /ext4/sp1 /ext4/sp2
==13678== Memcheck, a memory error detector.
==13678== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al.
==13678== Using LibVEX rev 1804, a library for dynamic binary translation.
==13678== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP.
==13678== Using valgrind-3.3.0-Debian, a dynamic binary instrumentation framework.
==13678== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al.
==13678== For more details, rerun with: -v
==13678==
==13678==
==13678== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 23 from 1)
==13678== malloc/free: in use at exit: 0 bytes in 0 blocks.
==13678== malloc/free: 71 allocs, 71 frees, 10,255 bytes allocated.
==13678== For counts of detected errors, rerun with: -v
==13678== All heap blocks were freed -- no leaks areFrom 2a2df00acbcc9cdaef723f23efccb65d761d9093
Mon Sep 17 00:00:00 2001

jeff@jeff-laptop:~/opensource_dev/coreutils$ ./src/cp --sparse=always /ocfs2/sparse_dir/sparse_4
/ext4/sp2
jeff@jeff-laptop:~/opensource_dev/coreutils$ ./ex /ext4/sp2
Extents in file "/ext4/sp2":    14
Extents returned: 14
Logical: ###[       0]	Ext length: ###[   65536]	Physical: ###[352321536]	
Logical: ###[   98304]	Ext length: ###[   32768]	Physical: ###[352419840]	
Logical: ###[  229376]	Ext length: ###[   32768]	Physical: ###[352550912]	
Logical: ###[  458752]	Ext length: ###[   65536]	Physical: ###[352780288]	
Logical: ###[  950272]	Ext length: ###[   65536]	Physical: ###[353271808]	
Logical: ###[ 1966080]	Ext length: ###[   32768]	Physical: ###[354287616]	
Logical: ###[ 3932160]	Ext length: ###[   65536]	Physical: ###[356253696]	
Logical: ###[ 7897088]	Ext length: ###[   65536]	Physical: ###[360218624]	
Logical: ###[15826944]	Ext length: ###[   65536]	Physical: ###[384925696]	
Logical: ###[31719424]	Ext length: ###[   65536]	Physical: ###[1004797952]	
Logical: ###[63471616]	Ext length: ###[   65536]	Physical: ###[1011384320]	
Logical: ###[126976000]	Ext length: ###[   65536]	Physical: ###[1016168448]	
Logical: ###[254017536]	Ext length: ###[   65536]	Physical: ###[1025769472]	
Logical: ###[508100608]	Ext length: ###[   32768]	Physical: ###[1036582912]	
jeff@jeff-laptop:~/opensource_dev/coreutils$ ./src/cp --sparse=always /ext4/sp2 /ext4/sp2_fiemap
jeff@jeff-laptop:~/opensource_dev/coreutils$ ./ex /ext4/sp2_fiemap
Extents in file "/ext4/sp2_fiemap":    14
Extents returned: 14
Logical: ###[       0]	Ext length: ###[   65536]	Physical: ###[1040187392]	
Logical: ###[   98304]	Ext length: ###[   32768]	Physical: ###[1040285696]	
Logical: ###[  229376]	Ext length: ###[   32768]	Physical: ###[1040416768]	
Logical: ###[  458752]	Ext length: ###[   65536]	Physical: ###[1040646144]	
Logical: ###[  950272]	Ext length: ###[   65536]	Physical: ###[1041137664]	
Logical: ###[ 1966080]	Ext length: ###[   32768]	Physical: ###[1042153472]	
Logical: ###[ 3932160]	Ext length: ###[   65536]	Physical: ###[1044119552]	
Logical: ###[ 7897088]	Ext length: ###[   65536]	Physical: ###[1048084480]	
Logical: ###[15826944]	Ext length: ###[   65536]	Physical: ###[1056014336]	
Logical: ###[31719424]	Ext length: ###[   65536]	Physical: ###[1063518208]	
Logical: ###[63471616]	Ext length: ###[   65536]	Physical: ###[1070104576]	
Logical: ###[126976000]	Ext length: ###[   65536]	Physical: ###[1125220352]	
Logical: ###[254017536]	Ext length: ###[   65536]	Physical: ###[1134821376]	
Logical: ###[508100608]	Ext length: ###[   32768]	Physical: ###[1145634816]	

>From 056bb15018466cc2b6b7ae2603fb41b6f61fa084 Mon Sep 17 00:00:00 2001
From: Jie Liu <[email-masked]>
Date: Fri, 21 May 2010 22:49:03 +0800
Subject: [PATCH 1/1] 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.

Signed-off-by: Jie Liu <[email-masked]>
---
 src/copy.c   |  153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/fiemap.h |  102 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 255 insertions(+), 0 deletions(-)
 create mode 100644 src/fiemap.h

diff --git a/src/copy.c b/src/copy.c
index c16cef6..f32a676 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -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,135 @@ 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;
+
+  memset (fiemap, 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 (0 < 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
@@ -679,6 +812,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)
@@ -807,6 +959,7 @@ copy_reg (char const *src_name, char const *dst_name,
         }
     }

+preserve_metadata:
   if (x->preserve_timestamps)
     {
       struct timespec timespec[2];
diff --git a/src/fiemap.h b/src/fiemap.h
new file mode 100644
index 0000000..d33293b
--- /dev/null
+++ b/src/fiemap.h
@@ -0,0 +1,102 @@
+/* FS_IOC_FIEMAP ioctl infrastructure.
+   Some portions copyright (C) 2007 Cluster File Systems, Inc
+   Authors: Mark Fasheh <[email-masked]>
+            Kalpak Shah <[email-masked]>
+            Andreas Dilger <[email-masked]>.  */
+
+/* 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
-- 
1.5.4.3

Cheers,
-Jeff

-- 
With Windows 7, Microsoft is asserting legal control over your computer and is using this power to
abuse computer users.