[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, ¤t_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