Subject: Re: bug#79139: cp --reflink truncates sparse files on ZFS

From: Paul Eggert <[email-masked]>
Date: Fri, 1 Aug 2025 17:17:16 -0700

On 2025-08-01 15:05, Collin Funk wrote:
> I was hoping that file could be made a tiny stub, due to the
> workarounds for Linux 4.19 being mostly unnecessary now that it is EOL.
> But now we have a new problem to deal with. :)

That we do. But we can more thorougly stubify the old Linux kernel bug 
workaround while we're in the neighborhood. Probably best not to remove 
it entirely as RHEL 8 still uses the no-longer-supported kernel.

To do that, I installed the attached patches into Gnulib and propagated 
them into coreutils.

Boldly closing the bug report. Thanks, Leah, for reporting it. That one 
was quite a whopper.

From 626f229915b114731cc4c9d9bda9eaa82d58180b Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Fri, 1 Aug 2025 14:46:51 -0700
Subject: [PATCH 1/2] copy-file-range: tune for more-modern kernels
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* lib/copy-file-range.c [__linux__ && HAVE_COPY_FILE_RANGE]:
Include <linux/version.h>.  Include <sys/utsname.h>
only for kernel 5.2 and earlier.
(CHECK_LINUX_KERNEL_VERSION): New macro.
(copy_file_range) [__linux__ && HAVE_COPY_FILE_RANGE]:
Call utsname only when built for kernel 5.2 and earlier.
(copy_file_range) [!__linux__ && HAVE_COPY_FILE_RANGE]:
Use underlying copy_file_range.  This doesn’t change
behavior since the code is not compiled in this case,
but it makes the code a bit cleaner.
* modules/copy-file-range (Depends-on): Add bool.
---
 ChangeLog               | 15 +++++++++++++
 lib/copy-file-range.c   | 48 +++++++++++++++++++++++++----------------
 m4/copy-file-range.m4   |  3 +--
 modules/copy-file-range |  1 +
 4 files changed, 47 insertions(+), 20 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 60b8d08d37..b33d7fd00b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2025-08-01  Paul Eggert  <[email-masked]>
+
+	copy-file-range: tune for more-modern kernels
+	* lib/copy-file-range.c [__linux__ && HAVE_COPY_FILE_RANGE]:
+	Include <linux/version.h>.  Include <sys/utsname.h>
+	only for kernel 5.2 and earlier.
+	(CHECK_LINUX_KERNEL_VERSION): New macro.
+	(copy_file_range) [__linux__ && HAVE_COPY_FILE_RANGE]:
+	Call utsname only when built for kernel 5.2 and earlier.
+	(copy_file_range) [!__linux__ && HAVE_COPY_FILE_RANGE]:
+	Use underlying copy_file_range.  This doesn’t change
+	behavior since the code is not compiled in this case,
+	but it makes the code a bit cleaner.
+	* modules/copy-file-range (Depends-on): Add bool.
+
 2025-08-01  Bruno Haible  <[email-masked]>
 
 	sigsegv: Use new ioctl available in Linux >= 6.11.
diff --git a/lib/copy-file-range.c b/lib/copy-file-range.c
index 8e0c644c08..73e02db1c1 100644
--- a/lib/copy-file-range.c
+++ b/lib/copy-file-range.c
@@ -21,7 +21,13 @@
 #include <errno.h>
 
 #if defined __linux__ && HAVE_COPY_FILE_RANGE
+# include <linux/version.h>
 # include <sys/utsname.h>
+# if LINUX_VERSION_CODE < KERNEL_VERSION (5, 3, 0)
+#  define CHECK_LINUX_KERNEL_VERSION true
+# else
+#  define CHECK_LINUX_KERNEL_VERSION false
+# endif
 #endif
 
 ssize_t
@@ -31,32 +37,38 @@ copy_file_range (int infd, off_t *pinoff,
 {
 #undef copy_file_range
 
-#if defined __linux__ && HAVE_COPY_FILE_RANGE
+#if HAVE_COPY_FILE_RANGE
+  bool ok = true;
+
+# if CHECK_LINUX_KERNEL_VERSION
   /* The implementation of copy_file_range (which first appeared in
      Linux kernel release 4.5) had many issues before release 5.3
      <https://lwn.net/Articles/789527/>, so fail with ENOSYS for Linux
      kernels 5.2 and earlier.
 
-     This workaround, and the configure-time check for Linux, can be
-     removed when such kernels (released March 2016 through September
-     2019) are no longer a consideration.  As of January 2021, the
-     furthest-future planned kernel EOL is December 2024 for kernel
-     release 4.19.  */
+     This workaround can be removed when such kernels (released March
+     2016 through September 2019) are no longer a consideration.
+     Although all such kernels have reached EOL, some distros use
+     older kernels.  For example, RHEL 8 uses kernel 4.18 and has an
+     EOL of 2029.  */
 
-    static signed char ok;
+  static signed char kernel_ok;
+  if (! kernel_ok)
+    {
+      struct utsname name;
+      uname (&name);
+      char *p = name.release;
+      kernel_ok = ((p[1] != '.' || '5' < p[0]
+                    || (p[0] == '5' && (p[3] != '.' || '2' < p[2])))
+                   ? 1 : -1);
+    }
 
-    if (! ok)
-      {
-        struct utsname name;
-        uname (&name);
-        char *p = name.release;
-        ok = ((p[1] != '.' || '5' < p[0]
-               || (p[0] == '5' && (p[3] != '.' || '2' < p[2])))
-              ? 1 : -1);
-      }
+  if (kernel_ok < 0)
+    ok = false;
+# endif
 
-    if (0 < ok)
-      return copy_file_range (infd, pinoff, outfd, poutoff, length, flags);
+  if (ok)
+    return copy_file_range (infd, pinoff, outfd, poutoff, length, flags);
 #endif
 
   /* There is little need to emulate copy_file_range with read+write,
diff --git a/m4/copy-file-range.m4 b/m4/copy-file-range.m4
index 13081d9b11..ec17fc68c7 100644
--- a/m4/copy-file-range.m4
+++ b/m4/copy-file-range.m4
@@ -52,8 +52,7 @@ AC_DEFUN([gl_FUNC_COPY_FILE_RANGE],
 
     case $host_os in
       linux*)
-        # See copy-file-range.c comment re pre-5.3 Linux kernel bugs.
-        # We should be able to remove this hack in 2025.
+        # See copy-file-range.c comment re Linux kernel bugs.
         REPLACE_COPY_FILE_RANGE=1;;
     esac
   fi
diff --git a/modules/copy-file-range b/modules/copy-file-range
index 12b6fcb86e..573558a945 100644
--- a/modules/copy-file-range
+++ b/modules/copy-file-range
@@ -6,6 +6,7 @@ lib/copy-file-range.c
 m4/copy-file-range.m4
 
 Depends-on:
+bool
 largefile
 unistd-h
 
-- 
2.48.1

From 948ba80168293e3d8a905eeffb0ab61424c4c374 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Fri, 1 Aug 2025 16:01:13 -0700
Subject: [PATCH 2/2] copy-file-range: work around glibc bug 33245

Problem reported by Leah Neukirchen <https://bugs.gnu.org/79139>.
* lib/copy-file-range.c: Include sys-limits.h.
(copy_file_range) [glibc 2.42 and earlier]:
Copy at most SYS_BUFSIZE_MAX bytes.
* m4/copy-file-range.m4 (gl_FUNC_COPY_FILE_RANGE):
Replace on glibc platforms, as well as on Linux platforms.
* modules/copy-file-range (Files): Add lib/sys-limits.h.
---
 ChangeLog               |  9 +++++++++
 lib/copy-file-range.c   | 17 ++++++++++++++++-
 m4/copy-file-range.m4   |  4 ++--
 modules/copy-file-range |  1 +
 4 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b33d7fd00b..e60a387611 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2025-08-01  Paul Eggert  <[email-masked]>
 
+	copy-file-range: work around glibc bug 33245
+	Problem reported by Leah Neukirchen <https://bugs.gnu.org/79139>.
+	* lib/copy-file-range.c: Include sys-limits.h.
+	(copy_file_range) [glibc 2.42 and earlier]:
+	Copy at most SYS_BUFSIZE_MAX bytes.
+	* m4/copy-file-range.m4 (gl_FUNC_COPY_FILE_RANGE):
+	Replace on glibc platforms, as well as on Linux platforms.
+	* modules/copy-file-range (Files): Add lib/sys-limits.h.
+
 	copy-file-range: tune for more-modern kernels
 	* lib/copy-file-range.c [__linux__ && HAVE_COPY_FILE_RANGE]:
 	Include <linux/version.h>.  Include <sys/utsname.h>
diff --git a/lib/copy-file-range.c b/lib/copy-file-range.c
index 73e02db1c1..5c12f6f715 100644
--- a/lib/copy-file-range.c
+++ b/lib/copy-file-range.c
@@ -30,6 +30,8 @@
 # endif
 #endif
 
+#include "sys-limits.h"
+
 ssize_t
 copy_file_range (int infd, off_t *pinoff,
                  int outfd, off_t *poutoff,
@@ -68,7 +70,20 @@ copy_file_range (int infd, off_t *pinoff,
 # endif
 
   if (ok)
-    return copy_file_range (infd, pinoff, outfd, poutoff, length, flags);
+    {
+#   if defined __GLIBC__ && ! (2 < __GLIBC__ + (43 <= __GLIBC_MINOR__))
+      /* Work around glibc bug 33245
+         <https://sourceware.org/bugzilla/show_bug.cgi?id=33245>.
+         This bug is present in glibc 2.42 (2025) and fixed in 2.43,
+         so this workaround, and the configure-time check for glibc,
+         can be removed once glibc 2.42 and earlier is no longer a
+         consideration.  Perhaps in 2040.  */
+      if (SYS_BUFSIZE_MAX < length)
+        length = SYS_BUFSIZE_MAX;
+# endif
+
+      return copy_file_range (infd, pinoff, outfd, poutoff, length, flags);
+    }
 #endif
 
   /* There is little need to emulate copy_file_range with read+write,
diff --git a/m4/copy-file-range.m4 b/m4/copy-file-range.m4
index ec17fc68c7..85cfef71f4 100644
--- a/m4/copy-file-range.m4
+++ b/m4/copy-file-range.m4
@@ -51,8 +51,8 @@ AC_DEFUN([gl_FUNC_COPY_FILE_RANGE],
       [Define to 1 if the function copy_file_range exists.])
 
     case $host_os in
-      linux*)
-        # See copy-file-range.c comment re Linux kernel bugs.
+      *-gnu* | gnu* | linux*)
+        # See copy-file-range.c comment re glibc and Linux kernel bugs.
         REPLACE_COPY_FILE_RANGE=1;;
     esac
   fi
diff --git a/modules/copy-file-range b/modules/copy-file-range
index 573558a945..e3512eaffa 100644
--- a/modules/copy-file-range
+++ b/modules/copy-file-range
@@ -3,6 +3,7 @@ Copy parts of files
 
 Files:
 lib/copy-file-range.c
+lib/sys-limits.h
 m4/copy-file-range.m4
 
 Depends-on:
-- 
2.48.1