bug-tar
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 2/4] reflink: add support for reflink when extracting


From: Matteo Croce
Subject: [PATCH 2/4] reflink: add support for reflink when extracting
Date: Sat, 26 Oct 2024 03:27:15 +0200

From: Matteo Croce <teknoraver@meta.com>

Add support for copy-on-write archive extraction via the --reflink option.
This uses the filesystem FICLONERANGE ioctl to a lightweight copy,
similarly to what `cp --reflink=auto` do.
This has two advantages:
first, the extraction is faster because the only IO performed is to read
the header entries from the archive and create destination inodes:

        $ time tar xf linux-6.11.5.tar

        real    0m14.329s
        user    0m0.225s
        sys     0m3.359s

        $ time tar xf linux-6.11.5.tar --reflink

        real    0m1.703s
        user    0m0.180s
        sys     0m1.298s

second, because of the block sharing, the extracted files don't take
more space than the original archive alone:

        $ df -h .
        Filesystem      Size  Used Avail Use% Mounted on
        /dev/vda3        19G   15G  3.5G  81% /home
        $ tar xf linux-6.11.5.tar
        $ df -h .
        Filesystem      Size  Used Avail Use% Mounted on
        /dev/vda3        19G   15G  3.2G  83% /home
        $ rm -rf linux-6.11.5
        $ tar xf linux-6.11.5.tar --reflink
        $ df -h .
        Filesystem      Size  Used Avail Use% Mounted on
        /dev/vda3        19G   15G  3.5G  81% /home

If some some reason the reflink fails (unsupported by the filesystem,
different mountpoint, etc.) a regular copy is made as fallback.
---
 src/extract.c | 101 ++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 73 insertions(+), 28 deletions(-)

diff --git a/src/extract.c b/src/extract.c
index 1121e596..38cfb51c 100644
--- a/src/extract.c
+++ b/src/extract.c
@@ -28,6 +28,13 @@
 #include <root-uid.h>
 #include <utimens.h>
 
+#ifdef __linux__
+# include <linux/fs.h>
+# ifdef FICLONERANGE
+#  include <sys/ioctl.h>
+# endif
+#endif
+
 #include "common.h"
 
 dev_t root_device;
@@ -1270,6 +1277,35 @@ open_output_file (char const *file_name, int typeflag, 
mode_t mode,
   return fd;
 }
 
+static int
+reflink_file (MAYBE_UNUSED int fd, MAYBE_UNUSED size_t size)
+{
+#ifdef FICLONERANGE
+  if (size <= 0)
+    return 0;
+
+  size_t pos = (unsigned long)(records_read-1) * record_size + 
(current_block->buffer - record_start->buffer);
+  struct file_clone_range fcr = {
+    .src_fd = archive,
+    .src_offset = pos,
+    .src_length = round_up (size, REFLINK_BLOCK_SIZE),
+  };
+  int rc;
+
+  rc = ioctl(fd, FICLONERANGE, &fcr);
+  if (rc < 0)
+      return rc;
+
+  rc = ftruncate(fd, size);
+  if (rc < 0)
+      return rc;
+
+  return 0;
+#else
+  return -ENOSYS;
+#endif
+}
+
 static int
 extract_file (char *file_name, int typeflag)
 {
@@ -1278,7 +1314,7 @@ extract_file (char *file_name, int typeflag)
   union block *data_block;
   int status;
   size_t written;
-  bool interdir_made = false;
+  bool interdir_made = false, reflinked = false;
   mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
                 & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
   mode_t current_mode = 0;
@@ -1327,39 +1363,48 @@ extract_file (char *file_name, int typeflag)
   if (current_stat_info.is_sparse)
     sparse_extract_file (fd, &current_stat_info, &size);
   else
-    for (size = current_stat_info.stat.st_size; size > 0; )
-      {
-       mv_size_left (size);
+    {
+      if (reflink_option)
+       {
+         reflinked = reflink_file (fd, current_stat_info.stat.st_size) == 0;
+         if (reflinked)
+           size = current_stat_info.stat.st_size;
+       }
+      if (!reflinked)
+       for (size = current_stat_info.stat.st_size; size > 0; )
+         {
+           mv_size_left (size);
 
-       /* Locate data, determine max length writeable, write it,
-          block that we have used the data, then check if the write
-          worked.  */
+           /* Locate data, determine max length writeable, write it,
+              block that we have used the data, then check if the write
+              worked.  */
 
-       data_block = find_next_block ();
-       if (! data_block)
-         {
-           paxerror (0, _("Unexpected EOF in archive"));
-           break;              /* FIXME: What happens, then?  */
-         }
+           data_block = find_next_block ();
+           if (! data_block)
+             {
+               paxerror (0, _("Unexpected EOF in archive"));
+               break;          /* FIXME: What happens, then?  */
+             }
 
-       written = available_space_after (data_block);
+           written = available_space_after (data_block);
 
-       if (written > size)
-         written = size;
-       errno = 0;
-       idx_t count = blocking_write (fd, data_block->buffer, written);
-       size -= written;
+           if (written > size)
+             written = size;
+           errno = 0;
+           idx_t count = blocking_write (fd, data_block->buffer, written);
+           size -= written;
 
-       set_next_block_after ((union block *)
-                             (data_block->buffer + written - 1));
-       if (count != written)
-         {
-           if (!to_command_option)
-             write_error_details (file_name, count, written);
-           /* FIXME: shouldn't we restore from backup? */
-           break;
+           set_next_block_after ((union block *)
+                                 (data_block->buffer + written - 1));
+           if (count != written)
+             {
+               if (!to_command_option)
+                 write_error_details (file_name, count, written);
+               /* FIXME: shouldn't we restore from backup? */
+               break;
+             }
          }
-      }
+    }
 
   skim_file (size, false);
 
-- 
2.46.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]