Skip to content

Commit 92fd074

Browse files
serhiy-storchakaclaude
authored andcommitted
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. (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 2d0da0b commit 92fd074

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
@@ -11972,27 +11972,35 @@ os_sendfile_impl(PyObject *module, int out_fd, int in_fd, PyObject *offobj,
1197211972
return PyLong_FromLong(0);
1197311973
}
1197411974

11975-
// On illumos specifically sendfile() may perform a partial write but
11976-
// return -1/an error (in one confirmed case the destination socket
11977-
// had a 5 second timeout set and errno was EAGAIN) and it's on the client
11978-
// code to check if the offset parameter was modified by sendfile().
11979-
//
11980-
// We need this variable to track said change.
11981-
off_t original_offset = offset;
11982-
#endif
11975+
// sendfile() may perform a partial write and still return -1, so the
11976+
// number of transferred bytes must be taken from the out parameter.
11977+
// sendfile() reports it by adding it to the offset, but does not
11978+
// initialize it when the transfer fails before writing any data, so use
11979+
// sendfilev(), which reports it explicitly.
11980+
sendfilevec_t vec;
11981+
size_t xferred;
11982+
11983+
vec.sfv_fd = in_fd;
11984+
vec.sfv_flag = 0;
11985+
vec.sfv_off = offset;
11986+
vec.sfv_len = count;
1198311987

1198411988
do {
1198511989
Py_BEGIN_ALLOW_THREADS
11986-
ret = sendfile(out_fd, in_fd, &offset, count);
11987-
#if defined(__sun) && defined(__SVR4)
11988-
// This handles illumos-specific sendfile() partial write behavior,
11989-
// see a comment above for more details.
11990-
if (ret < 0 && offset != original_offset) {
11991-
ret = offset - original_offset;
11990+
xferred = 0;
11991+
ret = sendfilev(out_fd, &vec, 1, &xferred);
11992+
if (ret < 0 && xferred != 0) {
11993+
ret = (Py_ssize_t)xferred;
1199211994
}
11993-
#endif
1199411995
Py_END_ALLOW_THREADS
1199511996
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
11997+
#else
11998+
do {
11999+
Py_BEGIN_ALLOW_THREADS
12000+
ret = sendfile(out_fd, in_fd, &offset, count);
12001+
Py_END_ALLOW_THREADS
12002+
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
12003+
#endif
1199612004
if (ret < 0)
1199712005
return (!async_err) ? posix_error() : NULL;
1199812006
return PyLong_FromSsize_t(ret);

0 commit comments

Comments
 (0)