Skip to content

Commit ffdd751

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.14] gh-154324: Fix os.sendfile() error reporting on illumos (GH-154327) (GH-154833)
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. (cherry picked from commit e3634bb) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c3b7eb0 commit ffdd751

2 files changed

Lines changed: 26 additions & 15 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`os.sendfile` on illumos:
2+
it no longer reports a successful transfer
3+
when the underlying system call failed without writing any data.

Modules/posixmodule.c

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12095,27 +12095,35 @@ os_sendfile_impl(PyObject *module, int out_fd, int in_fd, PyObject *offobj,
1209512095
return PyLong_FromLong(0);
1209612096
}
1209712097

12098-
// On illumos specifically sendfile() may perform a partial write but
12099-
// return -1/an error (in one confirmed case the destination socket
12100-
// had a 5 second timeout set and errno was EAGAIN) and it's on the client
12101-
// code to check if the offset parameter was modified by sendfile().
12102-
//
12103-
// We need this variable to track said change.
12104-
off_t original_offset = offset;
12105-
#endif
12098+
// sendfile() may perform a partial write and still return -1, so the
12099+
// number of transferred bytes must be taken from the out parameter.
12100+
// sendfile() reports it by adding it to the offset, but does not
12101+
// initialize it when the transfer fails before writing any data, so use
12102+
// sendfilev(), which reports it explicitly.
12103+
sendfilevec_t vec;
12104+
size_t xferred;
12105+
12106+
vec.sfv_fd = in_fd;
12107+
vec.sfv_flag = 0;
12108+
vec.sfv_off = offset;
12109+
vec.sfv_len = count;
1210612110

1210712111
do {
1210812112
Py_BEGIN_ALLOW_THREADS
12109-
ret = sendfile(out_fd, in_fd, &offset, count);
12110-
#if defined(__sun) && defined(__SVR4)
12111-
// This handles illumos-specific sendfile() partial write behavior,
12112-
// see a comment above for more details.
12113-
if (ret < 0 && offset != original_offset) {
12114-
ret = offset - original_offset;
12113+
xferred = 0;
12114+
ret = sendfilev(out_fd, &vec, 1, &xferred);
12115+
if (ret < 0 && xferred != 0) {
12116+
ret = (Py_ssize_t)xferred;
1211512117
}
12116-
#endif
1211712118
Py_END_ALLOW_THREADS
1211812119
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
12120+
#else
12121+
do {
12122+
Py_BEGIN_ALLOW_THREADS
12123+
ret = sendfile(out_fd, in_fd, &offset, count);
12124+
Py_END_ALLOW_THREADS
12125+
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
12126+
#endif
1211912127
if (ret < 0)
1212012128
return (!async_err) ? posix_error() : NULL;
1212112129
return PyLong_FromSsize_t(ret);

0 commit comments

Comments
 (0)