Repository navigation
libkernel: readv from a pipe fills only the first buffer on Windows; fix branch ready #4858
Description
On Windows, readv and sceKernelReadv on a pipe fill only the first buffer and return, however much is waiting. FreeBSD's readv(2) "scatters the input data into the iovcnt buffers", and the Linux build returns everything that is there. A caller that reads a header and its body in one readv gets the header alone on Windows and takes the body for missing.
Cause. TransferIovecs stops after the first transfer when the descriptor is not a disk file (!whole), so that it never waits on an empty pipe for the second buffer. That also stops it when the second buffer's bytes are already waiting.
Observed on main 0428328, Windows 11 (10.0.26300), WinLibs GCC 15.2.0 r7, Release, on a pipe from pipe() with buffers of 3, 5 and 16 bytes:
| waiting in the pipe | Windows | Linux build |
|---|---|---|
| 2 bytes | 2 | 2 |
| 3 bytes | 3 | 3 |
| 5 bytes | 3 | 5 |
| 12 bytes | 3 | 12 |
| 36 bytes | 3 | 24, then 12 |
| 5 bytes, write end closed | 3 | 5, then 0 |
| 7 bytes with buffers of 3, 0, 0 and 16 | 3 | 7 |
The rows were run in this order on one pipe, so on Windows each row also had the bytes the rows before it left behind.
I have not found a title that stops on this. It came out of running one probe of the file functions on the Windows build and on the Linux build and comparing the two outputs.
Fix, ready to cherry-pick. DavidSamir:fix/libkernel-readv-pipe-windows (ab3d836, one commit on main 0428328, two files, +16/-1). I can't open it as a PR while #4532 is open (one open PR without a merged one).
Windows only. After a buffer has been filled from a descriptor that is not a disk file, TransferIovecs goes on to the next buffer when the descriptor is a pipe with bytes waiting (PeekNamedPipe), and returns as before when nothing is waiting. It still never waits once it has read something: with three bytes in the pipe and two more arriving 300 ms later, readv returns the three at once. Disk files, positioned reads, writes, sockets and /dev/urandom do not reach the new check.
Not changed, and seen while testing: readv on an empty pipe that fcntl has set non-blocking fails with EINVAL on Windows where read fails with EAGAIN, because the vector path does not have the mapping sceKernelRead has. That is a separate fix, which I am preparing.
guest_file_vector gains checks on its pipe: with five bytes waiting sceKernelReadv into buffers of 3 and 4 must return 5 with stu and vw; with ten waiting readv must return 7 and the next sceKernelReadv the remaining 3. The existing check, three bytes into the same buffers with the write end open, still has to return 3 without waiting.
Checked on Windows (ab3d836 on main 0428328, Windows 11 10.0.26300, WinLibs GCC 15.2.0 r7, Release, full builds):
- With
Stdio.cppas onmainand the test kept,guest_file_vectorfails at the first new check (File vector check failed at line 212). With the fix it passes 5/5 (--repeat until-fail:5), andguest_filesystem,guest_poll,guest_dup,guest_standard_streams,file_streams,guest_aioandguest_kernel_fcntlpass 3/3. - The probe behind the table, 33 lines, with
main's libraries, the branch's and on the Linux build: every blocking case (2, 3, 5, 8, 12 and 36 bytes waiting, zero-length buffers in the middle, a single buffer, an empty pipe that gets five bytes after 150 ms, three bytes now and two later, a duplicate of the read end, the write end closed, the end of the pipe, a closed descriptor, the write end, a file,/dev/urandom) gives the same result on the branch as on the Linux build, and none of them takes longer than 400 ms. The two lines that differ from Linux are the non-blocking empty pipe above, the same withmain's libraries. - A probe of 188 lines over the file, directory, vector and mapping functions, run with both sets of libraries: one line differs,
readvfrom a pipe. A probe of 122 lines over the descriptor functions is identical in both. - Timing, 300,000 rounds, three passes in two alternating runs each: seek and
readvon a file 4.2 to 4.4 µs with both sets of libraries; write three bytes to a pipe andreadvthem 1.16 to 1.24 µs with both; write ten bytes and read them all, which takes fourreadvcalls withmain's libraries and one with the branch's, 2.84 to 3.04 µs and 2.85 to 3.04 µs. - Titles from the homebrew catalog, 35 s each with both sets of libraries: ccleste (PPSA59322), RommPS (PPSA76677), SwanStationPS5 (PPSA98510), ProsperoTV (PPSA99003), PSXS5 (PPSA97510), DOOM (PPSA99666), ScreenTester5 (PPSA69420), Prospero Vibrate (PPSA99920), SymphonyStation5 (PPSA17641), ProsperoStore (PPSA99000) and OpenNOW (PPSA99082) end at the same place in both, with the same number of stderr lines; PSXS5 stops on
pollin both (fix(libkernel): poll pipe descriptors #3644). I do not know whether any of them callsreadvon a pipe. - Full
ctest --parallel 4: 1001 of 1001 pass (7 skipped, no display). tools/check_conventions.py --base origin/mainpasses.tools/pr_overlap.py --branchreports no overlap with open pull requests.
Checked on Linux (same commit, WSL2 Ubuntu 24.04, GCC 13.3.0, Release): guest_file_vector passes 3/3, so the new checks match the existing Linux behaviour. Only that target and libs were built there.
Not checked on a console or with a commercial title.
AI-assisted: yes
Activity
milescoviello commentedon Oct 13, 2026
Carried in #4864, keeping your commit and authorship. I added the full Linux run (Ubuntu 24.04, GCC 13.3): all 324
guest_*tests pass in two runs, and the newguest_file_vectorchecks also pass withmain'sStdio.cpp, so Linux is unchanged. The Windows results in the PR are yours from this issue. Thanks for the branch.AI-assisted: yes