]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
copy: don't avoid copy-offload upon SEEK_HOLE indicating non-sparse
authorPádraig Brady <P@draigBrady.com>
Thu, 30 Oct 2025 13:02:48 +0000 (13:02 +0000)
committerPádraig Brady <P@draigBrady.com>
Sat, 1 Nov 2025 11:24:59 +0000 (11:24 +0000)
* src/copy-file-data.c (infer_scantype): Fall back to a plain copy
if SEEK_HOLE indicates non-sparse, as zero copy avoids copy offload.
This was seen with transparently compressed files on OpenZFS.
* tests/cp/sparse-perf.sh: Add a test case even though it might
only trigger on compressed file systems that don't support reflink.
* NEWS: Mention the bug fix.
Addresses https://github.com/coreutils/coreutils/issues/122

NEWS
src/copy-file-data.c
tests/cp/sparse-perf.sh

diff --git a/NEWS b/NEWS
index ee0213fb787b3303751c2f5f999a82ff63d4241a..53e5b387c2298378d962cd55e4ab526437d5e47b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,10 @@ GNU coreutils NEWS                                    -*- outline -*-
   Also non standard SHA2 tags with a bad length resulted in undefined behavior.
   [bug introduced in coreutils-9.8]
 
+  'cp' restores performance with transparently compressed files, which
+  regressed due to the avoidance of copy offload, seen with OpenZFS at least.
+  [bug introduced in coreutils-9.8]
+
   'numfmt' no longer reads out-of-bounds memory with trailing blanks in input.
   [bug introduced with numfmt in coreutils-8.21]
 
@@ -42,6 +46,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Changes in behavior
 
+  'cp' with default options may again, like with versions before v9.8,
+  miss opportunities to create holes with file systems like squashfs,
+  that support SEEK_HOLE only trivially.
+
   'sort --compress-program' will continue without compressing temporary files
   if the specified program cannot be executed.  Also malformed shell scripts
   without a "shebang line" will no longer be executed.
index 9eb6f47244f0a62c2f4934c7663794fd4dcf21bf..8fd25fee9201eda7bd0a8caf01f02821a3390448 100644 (file)
@@ -481,12 +481,19 @@ infer_scantype (int fd, struct stat const *sb, off_t pos,
           if (scan_inference->hole_start < sb->st_size)
             return LSEEK_SCANTYPE;
 
-          /* Though the file likely has holes, SEEK_DATA and SEEK_HOLE
+          /* Though the file may have holes, SEEK_DATA and SEEK_HOLE
              didn't find any.  This can happen with file systems like
              circa-2025 squashfs that support SEEK_HOLE only trivially.
-             Fall back on ZERO_SCANTYPE.  */
+             This can also happen due to transparent file compression,
+             which can also indicate fewer than the usual number of blocks.  */
+
           if (lseek (fd, pos, SEEK_SET) < 0)
             return ERROR_SCANTYPE;
+
+          /* we prefer to return PLAIN_SCANTYPE here so that copy offload
+             continues to be used.  Falling through to ZERO_SCANTYPE would be
+             less performant in the compressed file case.  */
+          return PLAIN_SCANTYPE;
         }
     }
   else if (pos < scan_inference->ext_start || errno == ENXIO)
index 5a283c1fe65816a36342d9f583c1ed787947bf10..5ee984c527d7d8b6395d4193fcf81804b1135b8a 100755 (executable)
@@ -35,6 +35,16 @@ cmp $other_partition_sparse k2 || fail=1
 grep ': avoided' cp.out && { cat cp.out; fail=1; }
 
 
+# Create a large-non-sparse-but-compressible file
+# Ensure we don't avoid copy offload which we saw with
+# transparent compression on OpenZFS at least
+# (as that triggers our sparse heuristic).
+mls='might-look-sparse'
+yes | head -n1M > "$mls" || framework_failure_
+cp --debug "$mls" "$mls.cp" >cp.out || fail=1
+cmp "$mls" "$mls.cp" || fail=1
+grep ': avoided' cp.out && { cat cp.out; fail=1; }
+
 
 # Create a large-but-sparse file on the current partition.
 # We disable relinking below, thus verifying SEEK_HOLE support