From 2215bb48745311cd271ab9afb2f494f6583722ae Mon Sep 17 00:00:00 2001 From: Hussein Kadhim Date: Tue, 13 Oct 2026 14:29:28 +0300 Subject: [PATCH 1/2] fix(libkernel): fail a write to a pipe with no reader with EPIPE on Windows The UCRT maps ERROR_NO_DATA, which WriteFile reports for a pipe whose read end is closed, to EINVAL. write, writev, sceKernelWrite and sceKernelWritev returned EINVAL where FreeBSD's pipe_write returns EPIPE. (cherry picked from commit 427044c7dbd1b699ebd0d5513ea35190036e6945) --- core/libs/prx/libkernel/CMakeLists.txt | 10 ++++ core/libs/prx/libkernel/File/src/Open.cpp | 1 + core/libs/prx/libkernel/File/src/Stdio.cpp | 1 + core/libs/tests/GuestPipeClosedWrite.cpp | 60 ++++++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 core/libs/tests/GuestPipeClosedWrite.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 798d82118e71..b593d06c7e4d 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -1189,6 +1189,16 @@ if(BUILD_TESTING) set_tests_properties(guest_file_vector PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pipe_closed_write_tests ${LIBS_INCLUDE_DIR}/tests/GuestPipeClosedWrite.cpp) + target_include_directories(guest_pipe_closed_write_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pipe_closed_write_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pipe_closed_write_tests) + add_test(NAME guest_pipe_closed_write COMMAND guest_pipe_closed_write_tests) + set_tests_properties(guest_pipe_closed_write PROPERTIES TIMEOUT 15) + if(WIN32) + set_tests_properties(guest_pipe_closed_write PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_fstatfs_tests ${LIBS_INCLUDE_DIR}/tests/GuestFstatfs.cpp) target_include_directories(guest_fstatfs_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_fstatfs_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 0868d227ca50..ef745f4c9255 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -67,6 +67,7 @@ static int NativeWrite(int fd, const void* buf, std::size_t n) { if (shared != -2) return static_cast(shared); const int result = ::_write(fd, buf, static_cast(n)); if (result < 0 && errno == ENOSPC && _doserrno == 0 && ::GetFileType(reinterpret_cast(::_get_osfhandle(fd))) == FILE_TYPE_PIPE) errno = EAGAIN; + if (result < 0 && errno == EINVAL && _doserrno == ERROR_NO_DATA) errno = EPIPE; return result; } extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index d9ceb8756bc4..fda991ab140d 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1009,6 +1009,7 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, co for (std::size_t position = 0; position < iov[i].length;) { const auto chunk = std::min(iov[i].length - position, std::numeric_limits::max()); const auto result = offset != nullptr ? NativePositioned_nid_no_patch(d, base + position, chunk, *offset + done, write) : NativeTransfer(d, base + position, chunk, write); + if (result < 0 && offset == nullptr && write && errno == EINVAL && _doserrno == ERROR_NO_DATA) errno = EPIPE; if (result < 0) return done != 0 ? done : SceErrorFromErrno(errno); done += result; position += static_cast(result); diff --git a/core/libs/tests/GuestPipeClosedWrite.cpp b/core/libs/tests/GuestPipeClosedWrite.cpp new file mode 100644 index 000000000000..5ec16e488b18 --- /dev/null +++ b/core/libs/tests/GuestPipeClosedWrite.cpp @@ -0,0 +1,60 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#ifndef _WIN32 +#include +#endif + +struct GuestIovec { + const void* base; + std::size_t length; +}; + +extern "C" { +int APS5_VABI pipe_nid_postfix(int*); +int APS5_VABI close_nid_postfix(int); +int APS5_VABI fcntl_nid_postfix(int, int, ...); +std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); +std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int); +std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t); +std::int64_t APS5_VABI sceKernelWritev(int, const GuestIovec*, int); +int* APS5_VABI __error_nid_postfix(); +} + +static void Require(bool value) { if (!value) std::abort(); } + +constexpr int GuestEpipe = 32; +constexpr int SetFlags = 4; +constexpr int NonBlocking = 4; +constexpr std::int64_t SceEpipe = static_cast(0x80020020u); + +static void RequireBrokenWrites(int writer) { + const GuestIovec vector{"x", 1}; + *__error_nid_postfix() = 0; + Require(write_nid_postfix(writer, "x", 1) == -1); + Require(*__error_nid_postfix() == GuestEpipe); + *__error_nid_postfix() = 0; + Require(writev_nid_postfix(writer, &vector, 1) == -1); + Require(*__error_nid_postfix() == GuestEpipe); + Require(sceKernelWrite(writer, "x", 1) == SceEpipe); + Require(sceKernelWritev(writer, &vector, 1) == SceEpipe); +} + +int main() { +#ifndef _WIN32 + std::signal(SIGPIPE, SIG_IGN); +#endif + int blocking[2]; + Require(pipe_nid_postfix(blocking) == 0); + Require(close_nid_postfix(blocking[0]) == 0); + RequireBrokenWrites(blocking[1]); + Require(close_nid_postfix(blocking[1]) == 0); + + int nonblocking[2]; + Require(pipe_nid_postfix(nonblocking) == 0); + Require(fcntl_nid_postfix(nonblocking[1], SetFlags, NonBlocking) == 0); + Require(write_nid_postfix(nonblocking[1], "x", 1) == 1); + Require(close_nid_postfix(nonblocking[0]) == 0); + RequireBrokenWrites(nonblocking[1]); + Require(close_nid_postfix(nonblocking[1]) == 0); +} From e0f0350a595d3eb6cd8a63aecfc5b93fbba8af6c Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Tue, 13 Oct 2026 15:59:21 +0000 Subject: [PATCH 2/2] fix(libkernel): guard Windows pipe error mapping --- core/libs/prx/libkernel/File/src/Stdio.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index fda991ab140d..9f171f190953 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1009,7 +1009,9 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, co for (std::size_t position = 0; position < iov[i].length;) { const auto chunk = std::min(iov[i].length - position, std::numeric_limits::max()); const auto result = offset != nullptr ? NativePositioned_nid_no_patch(d, base + position, chunk, *offset + done, write) : NativeTransfer(d, base + position, chunk, write); +#ifdef _WIN32 if (result < 0 && offset == nullptr && write && errno == EINVAL && _doserrno == ERROR_NO_DATA) errno = EPIPE; +#endif if (result < 0) return done != 0 ? done : SceErrorFromErrno(errno); done += result; position += static_cast(result);