From: Matteo Croce <teknora...@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