diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh index 18e765840..725f57ddc 100755 --- a/cpython-unix/build-cpython.sh +++ b/cpython-unix/build-cpython.sh @@ -610,6 +610,19 @@ if [[ -n "${LINUX_UAPI_INCLUDE_ARCH:-}" && "${TARGET_TRIPLE}" == *-linux-gnu* ]] CONFIGURE_FLAGS="${CONFIGURE_FLAGS} ac_cv_func_memfd_create=yes" fi +# Some glibc sysroots predate the copy_file_range() wrapper. Force the +# configure check on and weak-link the wrapper so the function is exposed only +# when runtime glibc provides it. +if [[ "${TARGET_TRIPLE}" == *-linux-gnu* ]]; then + if [[ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_14}" ]]; then + patch -p1 -i "${ROOT}/patch-posixmodule-copy-file-range-weak.patch" + else + patch -p1 -i "${ROOT}/patch-posixmodule-copy-file-range-weak-3.13.patch" + fi + + CONFIGURE_FLAGS="${CONFIGURE_FLAGS} ac_cv_func_copy_file_range=yes" +fi + # Define the base PGO profiling task, which we'll extend below with ignores export PROFILE_TASK='-m test --pgo' diff --git a/cpython-unix/patch-posixmodule-copy-file-range-weak-3.13.patch b/cpython-unix/patch-posixmodule-copy-file-range-weak-3.13.patch new file mode 100644 index 000000000..d1e7e5e3f --- /dev/null +++ b/cpython-unix/patch-posixmodule-copy-file-range-weak-3.13.patch @@ -0,0 +1,31 @@ +diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c +--- a/Modules/posixmodule.c ++++ b/Modules/posixmodule.c +@@ -10431,4 +10431,9 @@ + #ifdef HAVE_COPY_FILE_RANGE ++/* Weak references. */ ++__attribute__((weak)) ++ssize_t copy_file_range(int src, off_t *offset_src, int dst, ++ off_t *offset_dst, size_t count, unsigned int flags); ++ + /*[clinic input] + + os.copy_file_range +@@ -15737,5 +15742,17 @@ posixmodule_exec(PyObject *m) + #endif + ++#ifdef HAVE_COPY_FILE_RANGE ++ if (copy_file_range == NULL) { ++ PyObject *dict = PyModule_GetDict(m); ++ if (dict == NULL) { ++ return -1; ++ } ++ if (PyDict_DelItemString(dict, "copy_file_range") < 0) { ++ return -1; ++ } ++ } ++#endif ++ + /* Initialize environ dictionary */ + PyObject *v = convertenviron(); + Py_XINCREF(v); diff --git a/cpython-unix/patch-posixmodule-copy-file-range-weak.patch b/cpython-unix/patch-posixmodule-copy-file-range-weak.patch new file mode 100644 index 000000000..903e0f39b --- /dev/null +++ b/cpython-unix/patch-posixmodule-copy-file-range-weak.patch @@ -0,0 +1,31 @@ +diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c +--- a/Modules/posixmodule.c ++++ b/Modules/posixmodule.c +@@ -12488,4 +12488,9 @@ + #ifdef HAVE_COPY_FILE_RANGE ++/* Weak references. */ ++__attribute__((weak)) ++ssize_t copy_file_range(int src, off_t *offset_src, int dst, ++ off_t *offset_dst, size_t count, unsigned int flags); ++ + /*[clinic input] + + os.copy_file_range +@@ -18147,5 +18152,17 @@ posixmodule_exec(PyObject *m) + #endif + ++#ifdef HAVE_COPY_FILE_RANGE ++ if (copy_file_range == NULL) { ++ PyObject *dict = PyModule_GetDict(m); ++ if (dict == NULL) { ++ return -1; ++ } ++ if (PyDict_PopString(dict, "copy_file_range", NULL) < 0) { ++ return -1; ++ } ++ } ++#endif ++ + /* Initialize environ dictionary */ + if (PyModule_Add(m, "environ", convertenviron()) != 0) { + return -1; diff --git a/pythonbuild/disttests/__init__.py b/pythonbuild/disttests/__init__.py index 7314fc3a0..90c852d35 100644 --- a/pythonbuild/disttests/__init__.py +++ b/pythonbuild/disttests/__init__.py @@ -388,6 +388,38 @@ def test_os_memfd_create(self): finally: os.close(fd) + @unittest.skipUnless( + "-linux-gnu" in os.environ["TARGET_TRIPLE"], + "copy_file_range weak linking is enabled for Linux GNU targets", + ) + def test_os_copy_file_range(self): + import ctypes + import errno + + libc = ctypes.CDLL(None) + libc_has_copy_file_range = hasattr(libc, "copy_file_range") + self.assertEqual(hasattr(os, "copy_file_range"), libc_has_copy_file_range) + + if not libc_has_copy_file_range: + return + + data = b"copy_file_range" + with tempfile.TemporaryFile() as src, tempfile.TemporaryFile() as dst: + src.write(data) + src.flush() + src.seek(0) + + try: + copied = os.copy_file_range(src.fileno(), dst.fileno(), len(data)) + except OSError as exc: + if exc.errno == errno.ENOSYS: + self.skipTest("the runtime kernel does not support copy_file_range") + raise + + self.assertEqual(copied, len(data)) + dst.seek(0) + self.assertEqual(dst.read(), data) + def test_linux_uapi_not_in_sysconfig(self): import sysconfig