From 355f25203fd8cb27b33a645a15560ba1ec281849 Mon Sep 17 00:00:00 2001
From: Meta11ic0 <ihaveabigdoor@gmail.com>
Date: Sun, 23 Aug 2026 19:38:46 +0800
Subject: [PATCH] Disable copy_file_range on Linux before 5.3.

glibc exposes the symbol on Linux 4.19, but the kernel implementation
is incomplete and a second copy_file_range(..., SSIZE_MAX) fails with
EINVAL.  Do not define HAVE_COPY_FILE_RANGE when the build-time
<linux/version.h> is older than 5.3.  Non-Linux still uses a plain
function check.

Discussion: https://postgr.es/m/CAJh1VjasvNHhLoJu7WX55f2eW2i424MPRKMUF9hVt3YxDdMNAw@mail.gmail.com
---
 configure                              | 52 +++++++++++++++++++++++++-
 configure.ac                           | 23 +++++++++++-
 doc/src/sgml/config.sgml               |  2 +-
 doc/src/sgml/ref/pg_combinebackup.sgml |  2 +-
 doc/src/sgml/ref/pgupgrade.sgml        |  2 +-
 meson.build                            | 11 +++++-
 6 files changed, 86 insertions(+), 6 deletions(-)

diff --git a/configure b/configure
index d42a7a794ff..38fcde36f25 100755
--- a/configure
+++ b/configure
@@ -15858,7 +15858,7 @@ fi
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info explicit_memset getauxval getifaddrs getpeerucred inet_pton kqueue localeconv_l mbstowcs_l memset_explicit posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l
+for ac_func in backtrace_symbols copyfile elf_aux_info explicit_memset getauxval getifaddrs getpeerucred inet_pton kqueue localeconv_l mbstowcs_l memset_explicit posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
@@ -15871,6 +15871,56 @@ fi
 done
 
 
+# copy_file_range() can fail with EINVAL on Linux before 5.3.
+# Other platforms only require the function to be available.
+pgac_check_copy_file_range=yes
+case $host_os in
+  linux*)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether <linux/version.h> is 5.3 or later" >&5
+$as_echo_n "checking whether <linux/version.h> is 5.3 or later... " >&6; }
+if ${pgac_cv_linux_headers_5_3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0)
+#error "Linux kernel headers older than 5.3"
+#endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  pgac_cv_linux_headers_5_3=yes
+else
+  pgac_cv_linux_headers_5_3=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_linux_headers_5_3" >&5
+$as_echo "$pgac_cv_linux_headers_5_3" >&6; }
+    test "$pgac_cv_linux_headers_5_3" = yes || pgac_check_copy_file_range=no
+    ;;
+esac
+if test "$pgac_check_copy_file_range" = yes; then :
+  for ac_func in copy_file_range
+do :
+  ac_fn_c_check_func "$LINENO" "copy_file_range" "ac_cv_func_copy_file_range"
+if test "x$ac_cv_func_copy_file_range" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_COPY_FILE_RANGE 1
+_ACEOF
+
+fi
+done
+
+fi
+
 # These typically are compiler builtins, for which AC_CHECK_FUNCS fails.
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_bswap16" >&5
 $as_echo_n "checking for __builtin_bswap16... " >&6; }
diff --git a/configure.ac b/configure.ac
index a331749fcb5..b9653f7f1b1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1851,7 +1851,6 @@ LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 AC_CHECK_FUNCS(m4_normalize([
 	backtrace_symbols
 	copyfile
-	copy_file_range
 	elf_aux_info
 	explicit_memset
 	getauxval
@@ -1874,6 +1873,28 @@ AC_CHECK_FUNCS(m4_normalize([
 	wcstombs_l
 ]))
 
+# copy_file_range() can fail with EINVAL on Linux before 5.3.
+# Other platforms only require the function to be available.
+pgac_check_copy_file_range=yes
+case $host_os in
+  linux*)
+    AC_CACHE_CHECK([whether <linux/version.h> is 5.3 or later],
+                   [pgac_cv_linux_headers_5_3],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0)
+#error "Linux kernel headers older than 5.3"
+#endif]],
+          [[]])],
+       [pgac_cv_linux_headers_5_3=yes],
+       [pgac_cv_linux_headers_5_3=no])])
+    test "$pgac_cv_linux_headers_5_3" = yes || pgac_check_copy_file_range=no
+    ;;
+esac
+AS_IF([test "$pgac_check_copy_file_range" = yes],
+      [AC_CHECK_FUNCS([copy_file_range])])
+
 # These typically are compiler builtins, for which AC_CHECK_FUNCS fails.
 PGAC_CHECK_BUILTIN_FUNC([__builtin_bswap16], [int x])
 PGAC_CHECK_BUILTIN_FUNC([__builtin_bswap32], [int x])
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0165eb9ec02..c1d15f86bed 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2488,7 +2488,7 @@ include_dir 'conf.d'
 
        <para>
         <literal>CLONE</literal> uses the <function>copy_file_range()</function>
-        (Linux, FreeBSD) or <function>copyfile</function>
+        (Linux kernel 5.3 and later, FreeBSD) or <function>copyfile</function>
         (macOS) system calls, giving the kernel the opportunity to share disk
         blocks or push work down to lower layers on some file systems.
        </para>
diff --git a/doc/src/sgml/ref/pg_combinebackup.sgml b/doc/src/sgml/ref/pg_combinebackup.sgml
index 9a6d201e0b8..beaa7256615 100644
--- a/doc/src/sgml/ref/pg_combinebackup.sgml
+++ b/doc/src/sgml/ref/pg_combinebackup.sgml
@@ -210,7 +210,7 @@ PostgreSQL documentation
         copying.  On some file systems this gives results similar to
         <option>--clone</option>, sharing physical disk blocks, while on others
         it may still copy blocks, but do so via an optimized path.  At present,
-        it is supported on Linux and FreeBSD.
+        it is supported on Linux (kernel 5.3 and later) and FreeBSD.
        </para>
 
        <para>
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index e4e8c02e6d6..22545328c1f 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -257,7 +257,7 @@ PostgreSQL documentation
         copying.  On some file systems this gives results similar to
         <option>--clone</option>, sharing physical disk blocks, while on others
         it may still copy blocks, but do so via an optimized path.  At present,
-        it is supported on Linux and FreeBSD.
+        it is supported on Linux (kernel 5.3 and later) and FreeBSD.
        </para>
       </listitem>
      </varlistentry>
diff --git a/meson.build b/meson.build
index f4cde249242..068dc6cd8d0 100644
--- a/meson.build
+++ b/meson.build
@@ -3185,13 +3185,22 @@ else
   socket_dep = not_found_dep
 endif
 
+# copy_file_range() is incomplete on Linux before 5.3 (EINVAL).
+# Non-Linux skips this compile probe and keeps a plain function check.
+have_linux_5_3_headers = host_system != 'linux' or cc.compiles('''
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0)
+#error "Linux kernel headers older than 5.3"
+#endif
+''', name: '<linux/version.h> is 5.3 or later', args: test_c_args)
+
 # XXX: Might be worth conditioning some checks on the OS, to avoid doing
 # unnecessary checks over and over, particularly on windows.
 func_checks = [
   ['backtrace_symbols', {'dependencies': [execinfo_dep]}],
   ['clock_gettime', {'dependencies': [rt_dep], 'define': false}],
   ['copyfile'],
-  ['copy_file_range'],
+  ['copy_file_range', {'skip': not have_linux_5_3_headers}],
   # gcc/clang's sanitizer helper library provides dlopen but not dlsym, thus
   # when enabling asan the dlopen check doesn't notice that -ldl is actually
   # required. Just checking for dlsym() ought to suffice.
-- 
2.34.1

