From e3634bb44f7f1395dcb1b7ab3736811e9e2a4589 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 28 Jul 2026 20:14:00 +0300 Subject: [PATCH] gh-154324: Fix os.sendfile() error reporting on illumos (GH-154327) illumos sendfile(3EXT) returns the number of transferred bytes by adding it to the offset without initializing it, so the offset cannot be used to detect a partial write. Use sendfilev(3EXT), which reports the number of transferred bytes in an explicit out parameter. Co-Authored-By: Claude Opus 4.8 --- ...-07-21-08-31-55.gh-issue-154324.vDxV5U.rst | 3 ++ Modules/posixmodule.c | 38 +++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-21-08-31-55.gh-issue-154324.vDxV5U.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-21-08-31-55.gh-issue-154324.vDxV5U.rst b/Misc/NEWS.d/next/Library/2026-07-21-08-31-55.gh-issue-154324.vDxV5U.rst new file mode 100644 index 000000000000..388e4deb7c41 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-21-08-31-55.gh-issue-154324.vDxV5U.rst @@ -0,0 +1,3 @@ +Fix :func:`os.sendfile` on illumos: +it no longer reports a successful transfer +when the underlying system call failed without writing any data. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 350f56c393d3..f754d0e18b5f 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -12614,27 +12614,35 @@ done: return PyLong_FromLong(0); } - // On illumos specifically sendfile() may perform a partial write but - // return -1/an error (in one confirmed case the destination socket - // had a 5 second timeout set and errno was EAGAIN) and it's on the client - // code to check if the offset parameter was modified by sendfile(). - // - // We need this variable to track said change. - off_t original_offset = offset; -#endif + // sendfile() may perform a partial write and still return -1, so the + // number of transferred bytes must be taken from the out parameter. + // sendfile() reports it by adding it to the offset, but does not + // initialize it when the transfer fails before writing any data, so use + // sendfilev(), which reports it explicitly. + sendfilevec_t vec; + size_t xferred; + + vec.sfv_fd = in_fd; + vec.sfv_flag = 0; + vec.sfv_off = offset; + vec.sfv_len = count; do { Py_BEGIN_ALLOW_THREADS - ret = sendfile(out_fd, in_fd, &offset, count); -#if defined(__sun) && defined(__SVR4) - // This handles illumos-specific sendfile() partial write behavior, - // see a comment above for more details. - if (ret < 0 && offset != original_offset) { - ret = offset - original_offset; + xferred = 0; + ret = sendfilev(out_fd, &vec, 1, &xferred); + if (ret < 0 && xferred != 0) { + ret = (Py_ssize_t)xferred; } -#endif Py_END_ALLOW_THREADS } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); +#else + do { + Py_BEGIN_ALLOW_THREADS + ret = sendfile(out_fd, in_fd, &offset, count); + Py_END_ALLOW_THREADS + } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); +#endif if (ret < 0) return (!async_err) ? posix_error() : NULL; return PyLong_FromSsize_t(ret); -- 2.47.3