Subject: bug#79267: cp --sparse=auto heuristic fails on a squashfs mounted drive.

From: Paul Eggert <[email-masked]>
Date: Thu, 21 Aug 2025 17:03:32 -0600

Thanks for the bug report. Although this part of the code is messy and 
needs a revamp, in the meantime I installed the attached into the master 
branch on Savannah; please give it a try.

From b7fc76269bbc830bf96320cd5cca3cfd90d33f68 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Thu, 21 Aug 2025 16:02:10 -0600
Subject: [PATCH] cp: always punch holes that we make

Problem reported by Jeremy Allison <https://bugs.gnu.org/79267>.
* src/copy.c (create_hole, sparse_copy): Omit arg PUNCH_HOLES,
as we always punch holes now.  All uses changed.
(lseek_copy): When calling sparse_copy, scan for holes when
sparse_mode == SPARSE_AUTO, as that means we are making holes.
(copy_reg): Always punch any hole made at end.
---
 src/copy.c | 21 ++++++++-------------
 1 file changed, 8 insertions(+), 13 deletions(-)

diff --git a/src/copy.c b/src/copy.c
index 77f0c561e..e328bd886 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -253,12 +253,11 @@ punch_hole (int fd, off_t offset, off_t length)
 }
 
 /* Create a hole at the end of the file with descriptor FD and name NAME.
-   If PUNCH_HOLES, avoid preallocation if requested.
    The hole is of size SIZE.  Assume FD is already at file end,
    and advance FD past the newly-created hole.  */
 
 static bool
-create_hole (int fd, char const *name, bool punch_holes, off_t size)
+create_hole (int fd, char const *name, off_t size)
 {
   off_t file_end = lseek (fd, size, SEEK_CUR);
 
@@ -272,7 +271,7 @@ create_hole (int fd, char const *name, bool punch_holes, off_t size)
      I.e., a previous write() may have preallocated extra space
      that the seek above will not discard.  A subsequent write() could
      then make this allocation permanent.  */
-  if (punch_holes && punch_hole (fd, file_end - size, size) < 0)
+  if (punch_hole (fd, file_end - size, size) < 0)
     {
       error (0, errno, _("error deallocating %s"), quoteaf (name));
       return false;
@@ -311,7 +310,6 @@ is_CLONENOTSUP (int err)
    honoring the MAKE_HOLES setting and using the BUF_SIZE-byte buffer
    *ABUF for temporary storage, allocating it lazily if *ABUF is null.
    For best results, *ABUF should be well-aligned.
-   If PUNCH_HOLES, punch holes in the output.
    Copy no more than MAX_N_READ bytes.
    If HOLE_SIZE, look for holes in the input; *HOLE_SIZE contains
    the size of the current hole so far, and update *HOLE_SIZE
@@ -322,7 +320,7 @@ is_CLONENOTSUP (int err)
    print a diagnostic and return false upon error.  */
 static bool
 sparse_copy (int src_fd, int dest_fd, char **abuf, size_t buf_size,
-             bool punch_holes, bool allow_reflink,
+             bool allow_reflink,
              char const *src_name, char const *dst_name,
              uintmax_t max_n_read, off_t *hole_size, off_t *total_n_read)
 {
@@ -437,7 +435,7 @@ sparse_copy (int src_fd, int dest_fd, char **abuf, size_t buf_size,
                 psize += csize;
               else if (prev_hole)
                 {
-                  if (! create_hole (dest_fd, dst_name, punch_holes, psize))
+                  if (! create_hole (dest_fd, dst_name, psize))
                     return false;
                   pbuf = cbuf;
                   psize = csize;
@@ -591,9 +589,7 @@ lseek_copy (int src_fd, int dest_fd, char **abuf, size_t buf_size,
             *hole_size += ext_hole_size;
           else if (sparse_mode != SPARSE_NEVER)
             {
-              if (! create_hole (dest_fd, dst_name,
-                                 sparse_mode == SPARSE_ALWAYS,
-                                 ext_hole_size))
+              if (! create_hole (dest_fd, dst_name, ext_hole_size))
                 return false;
             }
           else
@@ -619,9 +615,9 @@ lseek_copy (int src_fd, int dest_fd, char **abuf, size_t buf_size,
          is conservative and may miss some holes.  */
       off_t n_read;
       if ( ! sparse_copy (src_fd, dest_fd, abuf, buf_size,
-                          true, allow_reflink, src_name, dst_name,
+                          allow_reflink, src_name, dst_name,
                           ext_len,
-                          sparse_mode == SPARSE_ALWAYS ? hole_size : nullptr,
+                          sparse_mode != SPARSE_NEVER ? hole_size : nullptr,
                           &n_read))
         return false;
 
@@ -1576,7 +1572,6 @@ copy_reg (char const *src_name, char const *dst_name,
              :
 #endif
                sparse_copy (source_desc, dest_desc, &buf, buf_size,
-                            x->sparse_mode == SPARSE_ALWAYS,
                             x->reflink_mode != REFLINK_NEVER,
                             src_name, dst_name, UINTMAX_MAX,
                             make_holes ? &hole_size : nullptr, &n_read)))
@@ -1594,7 +1589,7 @@ copy_reg (char const *src_name, char const *dst_name,
               return_val = false;
               goto close_src_and_dst_desc;
             }
-          if (x->sparse_mode == SPARSE_ALWAYS
+          if (make_holes
               && punch_hole (dest_desc, n_read - hole_size, hole_size) < 0)
             {
               error (0, errno, _("error deallocating %s"), quoteaf (dst_name));
-- 
2.50.1