]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-154324: Fix os.sendfile() error reporting on illumos (GH-154327)
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 28 Jul 2026 17:14:00 +0000 (20:14 +0300)
committerGitHub <noreply@github.com>
Tue, 28 Jul 2026 17:14:00 +0000 (20:14 +0300)
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 <noreply@anthropic.com>
Misc/NEWS.d/next/Library/2026-07-21-08-31-55.gh-issue-154324.vDxV5U.rst [new file with mode: 0644]
Modules/posixmodule.c

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 (file)
index 0000000..388e4de
--- /dev/null
@@ -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.
index 350f56c393d3b97a111c433f178a94632f27b7b5..f754d0e18b5fb09c8a9a12f6c8f7eb0a966f26e4 100644 (file)
@@ -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);