Skip to content

Commit e3634bb

Browse files
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 <noreply@anthropic.com>
1 parent 4f3e856 commit e3634bb

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
@@ -12614,27 +12614,35 @@ os_sendfile_impl(PyObject *module, int out_fd, int in_fd, PyObject *offobj,
1261412614
return PyLong_FromLong(0);
1261512615
}
1261612616

12617-
// On illumos specifically sendfile() may perform a partial write but
12618-
// return -1/an error (in one confirmed case the destination socket
12619-
// had a 5 second timeout set and errno was EAGAIN) and it's on the client
12620-
// code to check if the offset parameter was modified by sendfile().
12621-
//
12622-
// We need this variable to track said change.
12623-
off_t original_offset = offset;
12624-
#endif
12617+
// sendfile() may perform a partial write and still return -1, so the
12618+
// number of transferred bytes must be taken from the out parameter.
12619+
// sendfile() reports it by adding it to the offset, but does not
12620+
// initialize it when the transfer fails before writing any data, so use
12621+
// sendfilev(), which reports it explicitly.
12622+
sendfilevec_t vec;
12623+
size_t xferred;
12624+
12625+
vec.sfv_fd = in_fd;
12626+
vec.sfv_flag = 0;
12627+
vec.sfv_off = offset;
12628+
vec.sfv_len = count;
1262512629

1262612630
do {
1262712631
Py_BEGIN_ALLOW_THREADS
12628-
ret = sendfile(out_fd, in_fd, &offset, count);
12629-
#if defined(__sun) && defined(__SVR4)
12630-
// This handles illumos-specific sendfile() partial write behavior,
12631-
// see a comment above for more details.
12632-
if (ret < 0 && offset != original_offset) {
12633-
ret = offset - original_offset;
12632+
xferred = 0;
12633+
ret = sendfilev(out_fd, &vec, 1, &xferred);
12634+
if (ret < 0 && xferred != 0) {
12635+
ret = (Py_ssize_t)xferred;
1263412636
}
12635-
#endif
1263612637
Py_END_ALLOW_THREADS
1263712638
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
12639+
#else
12640+
do {
12641+
Py_BEGIN_ALLOW_THREADS
12642+
ret = sendfile(out_fd, in_fd, &offset, count);
12643+
Py_END_ALLOW_THREADS
12644+
} while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
12645+
#endif
1263812646
if (ret < 0)
1263912647
return (!async_err) ? posix_error() : NULL;
1264012648
return PyLong_FromSsize_t(ret);

0 commit comments

Comments
 (0)