Skip to content

libkernel: readv from a pipe fills only the first buffer on Windows; fix branch ready #4858

Closed

Description

@DavidSamir

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.cpp as on main and the test kept, guest_file_vector fails at the first new check (File vector check failed at line 212). With the fix it passes 5/5 (--repeat until-fail:5), and guest_filesystem, guest_poll, guest_dup, guest_standard_streams, file_streams, guest_aio and guest_kernel_fcntl pass 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 with main's libraries.
  • A probe of 188 lines over the file, directory, vector and mapping functions, run with both sets of libraries: one line differs, readv from 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 readv on a file 4.2 to 4.4 µs with both sets of libraries; write three bytes to a pipe and readv them 1.16 to 1.24 µs with both; write ten bytes and read them all, which takes four readv calls with main'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 poll in both (fix(libkernel): poll pipe descriptors #3644). I do not know whether any of them calls readv on a pipe.
  • Full ctest --parallel 4: 1001 of 1001 pass (7 skipped, no display).
  • tools/check_conventions.py --base origin/main passes. tools/pr_overlap.py --branch reports 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

  1. milescoviello commented on Oct 13, 2026

    @milescoviello
    Contributor

    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 new guest_file_vector checks also pass with main's Stdio.cpp, so Linux is unchanged. The Windows results in the PR are yours from this issue. Thanks for the branch.

    AI-assisted: yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions