From 96a7a840e1fdb62f7d2be7499868e4a8495da8c1 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 9 Jul 2026 09:52:38 +0200 Subject: [PATCH] gh-153400: Add syscall fallbacks for copy_file_range/memfd_create glibc only grew copy_file_range() and memfd_create() in 2.27, and we compile the os functions out when the libc we build against doesn't have them. That loses them for good in a redistributable built against an older glibc, such as the python-build-standalone builds targeting glibc 2.17, even when the kernel it runs on implements the syscalls. Keep calling the libc wrappers when they are available, so we don't lose their symbol versioning and _FORTIFY_SOURCE checks, and issue the syscall directly when they aren't. If the syscall number is missing as well, the functions are still left out. pidfd_open() and pidfd_getfd() already use raw syscalls, so nothing changes for them. Include whenever it exists rather than only when the getrandom() syscall was detected, since __NR_* is now needed for more than getrandom(). Signed-off-by: Daan De Meyer --- Doc/library/os.rst | 12 +++++-- ...-08-10-22-23-29.gh-issue-153400.nucElC.rst | 4 +++ Modules/clinic/posixmodule.c.h | 10 +++--- Modules/posixmodule.c | 36 ++++++++++++++----- 4 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-10-22-23-29.gh-issue-153400.nucElC.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index fceadde7df6bf4..525ec3a0c858ff 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1011,10 +1011,14 @@ as internal buffering of data. It will always copy no bytes and return 0 as if the file was empty because of a known Linux kernel issue. - .. availability:: Linux >= 4.5 with glibc >= 2.27. + .. availability:: Linux >= 4.5. .. versionadded:: 3.8 + .. versionchanged:: 3.16 + The function is now also available when Python is built against a libc + that lacks ``copy_file_range()``, such as glibc older than 2.27. + .. function:: device_encoding(fd) @@ -4400,10 +4404,14 @@ The following flags are used in :attr:`statvfs_result.f_flag`. the file descriptor, and as such multiple files can have the same name without any side effects. - .. availability:: Linux >= 3.17 with glibc >= 2.27. + .. availability:: Linux >= 3.17. .. versionadded:: 3.8 + .. versionchanged:: 3.16 + The function is now also available when Python is built against a libc + that lacks ``memfd_create()``, such as glibc older than 2.27. + .. data:: MFD_CLOEXEC MFD_ALLOW_SEALING diff --git a/Misc/NEWS.d/next/Library/2026-08-10-22-23-29.gh-issue-153400.nucElC.rst b/Misc/NEWS.d/next/Library/2026-08-10-22-23-29.gh-issue-153400.nucElC.rst new file mode 100644 index 00000000000000..78545a40c6b501 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-10-22-23-29.gh-issue-153400.nucElC.rst @@ -0,0 +1,4 @@ +On Linux, :func:`os.copy_file_range` and :func:`os.memfd_create` now fall back +to the raw syscall when the libc Python is built against does not provide the +wrapper function, so they stay available on interpreters built against a libc +older than glibc 2.27. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index a6092dd9f5638f..c9307a1c44d315 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -8853,7 +8853,7 @@ os_pwritev(PyObject *module, PyObject *const *args, Py_ssize_t nargs) #endif /* (defined(HAVE_PWRITEV) || defined (HAVE_PWRITEV2)) */ -#if defined(HAVE_COPY_FILE_RANGE) +#if defined(_Py_HAVE_COPY_FILE_RANGE) PyDoc_STRVAR(os_copy_file_range__doc__, "copy_file_range($module, /, src, dst, count, offset_src=None,\n" @@ -8969,7 +8969,7 @@ os_copy_file_range(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py return return_value; } -#endif /* defined(HAVE_COPY_FILE_RANGE) */ +#endif /* defined(_Py_HAVE_COPY_FILE_RANGE) */ #if ((defined(HAVE_SPLICE) && !defined(_AIX))) @@ -11547,7 +11547,7 @@ os_urandom(PyObject *module, PyObject *arg) return return_value; } -#if defined(HAVE_MEMFD_CREATE) +#if defined(_Py_HAVE_MEMFD_CREATE) PyDoc_STRVAR(os_memfd_create__doc__, "memfd_create($module, /, name, flags=MFD_CLOEXEC)\n" @@ -11633,7 +11633,7 @@ os_memfd_create(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj return return_value; } -#endif /* defined(HAVE_MEMFD_CREATE) */ +#endif /* defined(_Py_HAVE_MEMFD_CREATE) */ #if (defined(HAVE_EVENTFD) && defined(EFD_CLOEXEC)) @@ -13746,4 +13746,4 @@ os__emscripten_log(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #ifndef OS__EMSCRIPTEN_LOG_METHODDEF #define OS__EMSCRIPTEN_LOG_METHODDEF #endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */ -/*[clinic end generated code: output=6dc1e061bfd47375 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=f77ed566165d51da input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index a7208aeef1871e..d94291e2ae524b 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -157,8 +157,8 @@ #ifdef HAVE_LINUX_RANDOM_H # include // GRND_RANDOM #endif -#ifdef HAVE_GETRANDOM_SYSCALL -# include // syscall() +#ifdef HAVE_SYS_SYSCALL_H +# include // syscall(), __NR_xxx syscall numbers #endif #ifdef HAVE_POSIX_SPAWN @@ -13133,7 +13133,12 @@ os_pwritev_impl(PyObject *module, int fd, PyObject *buffers, Py_off_t offset, } #endif /* HAVE_PWRITEV */ -#ifdef HAVE_COPY_FILE_RANGE +#if defined(HAVE_COPY_FILE_RANGE) || \ + (defined(__linux__) && defined(__NR_copy_file_range)) +# define _Py_HAVE_COPY_FILE_RANGE +#endif + +#ifdef _Py_HAVE_COPY_FILE_RANGE /*[clinic input] os.copy_file_range @@ -13185,7 +13190,13 @@ os_copy_file_range_impl(PyObject *module, int src, int dst, Py_ssize_t count, do { Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_COPY_FILE_RANGE ret = copy_file_range(src, p_offset_src, dst, p_offset_dst, count, flags); +#else + /* Largefile support makes off_t 64-bit, as the kernel expects. */ + ret = syscall(__NR_copy_file_range, src, p_offset_src, dst, p_offset_dst, + count, flags); +#endif Py_END_ALLOW_THREADS } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); @@ -13195,7 +13206,7 @@ os_copy_file_range_impl(PyObject *module, int src, int dst, Py_ssize_t count, return PyLong_FromSsize_t(ret); } -#endif /* HAVE_COPY_FILE_RANGE*/ +#endif /* _Py_HAVE_COPY_FILE_RANGE */ #if (defined(HAVE_SPLICE) && !defined(_AIX)) /*[clinic input] @@ -15897,7 +15908,12 @@ os_urandom_impl(PyObject *module, Py_ssize_t size) return PyBytesWriter_Finish(writer); } -#ifdef HAVE_MEMFD_CREATE +#if defined(HAVE_MEMFD_CREATE) || \ + (defined(__linux__) && defined(__NR_memfd_create) && defined(MFD_CLOEXEC)) +# define _Py_HAVE_MEMFD_CREATE +#endif + +#ifdef _Py_HAVE_MEMFD_CREATE /*[clinic input] os.memfd_create @@ -15913,7 +15929,11 @@ os_memfd_create_impl(PyObject *module, PyObject *name, unsigned int flags) int fd; const char *bytes = PyBytes_AS_STRING(name); Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_MEMFD_CREATE fd = memfd_create(bytes, flags); +#else + fd = syscall(__NR_memfd_create, bytes, flags); +#endif Py_END_ALLOW_THREADS if (fd == -1) { return PyErr_SetFromErrno(PyExc_OSError); @@ -18533,7 +18553,7 @@ all_ins(PyObject *m) if (PyModule_AddIntMacro(m, GRND_RANDOM)) return -1; if (PyModule_AddIntMacro(m, GRND_NONBLOCK)) return -1; #endif -#ifdef HAVE_MEMFD_CREATE +#ifdef _Py_HAVE_MEMFD_CREATE if (PyModule_AddIntMacro(m, MFD_CLOEXEC)) return -1; if (PyModule_AddIntMacro(m, MFD_ALLOW_SEALING)) return -1; #ifdef MFD_HUGETLB @@ -18581,7 +18601,7 @@ all_ins(PyObject *m) #ifdef MFD_HUGE_16GB if (PyModule_AddIntMacro(m, MFD_HUGE_16GB)) return -1; #endif -#endif /* HAVE_MEMFD_CREATE */ +#endif /* _Py_HAVE_MEMFD_CREATE */ #if defined(HAVE_EVENTFD) && defined(EFD_CLOEXEC) if (PyModule_AddIntMacro(m, EFD_CLOEXEC)) return -1; @@ -18837,7 +18857,7 @@ static const struct have_function { { "HAVE_LUTIMES", NULL }, #endif -#ifdef HAVE_MEMFD_CREATE +#ifdef _Py_HAVE_MEMFD_CREATE { "HAVE_MEMFD_CREATE", NULL }, #endif