Merge pull request #3150 from MarcZX100/codex/marc-socket-vector-io

fix(libkernel): route vector I/O on guest sockets
This commit is contained in:
Dean 2026-10-11 18:47:36 +13:00 committed by GitHub
commit d7cfa72813
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 112 additions and 1 deletions

View File

@ -27,6 +27,7 @@ struct KernelIovec {
void* base;
std::size_t length;
};
static_assert(sizeof(KernelIovec) == sizeof(GuestSockets::Iovec));
static constexpr int KERNEL_IOV_MAX = 1024;
@ -822,6 +823,11 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, co
if (offset != nullptr && total > static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max() - *offset)) return SceErrorFromErrno(GUEST_EINVAL);
std::deque<GuestArena::HostWrite> destinations;
if (!write && !OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT);
if (offset == nullptr && d >= GuestSockets::FirstDescriptor) {
const auto result = write ? GuestSockets::Writev(d, reinterpret_cast<const GuestSockets::Iovec*>(iov), iovcnt)
: GuestSockets::Readv(d, reinterpret_cast<const GuestSockets::Iovec*>(iov), iovcnt);
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}
if (!write && File::IsRandomDevice(d)) return ReadRandomIovecs(d, iov, iovcnt);
if (total == 0) {
char none = 0;
@ -883,6 +889,10 @@ int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* iov, int iovcnt) {
if (const int error = CheckIovecs(iov, iovcnt)) return error;
std::deque<GuestArena::HostWrite> destinations;
if (!OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT);
if (d >= GuestSockets::FirstDescriptor) {
const auto result = GuestSockets::Readv(d, reinterpret_cast<const GuestSockets::Iovec*>(iov), iovcnt);
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}
if (File::IsRandomDevice(d)) return ReadRandomIovecs(d, iov, iovcnt);
const auto result = static_cast<std::int64_t>(::readv(d, NativeIovecs(iov), iovcnt));
return result < 0 ? SceErrorFromErrno(errno) : result;
@ -890,6 +900,10 @@ int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* iov, int iovcnt) {
int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* iov, int iovcnt) {
if (const int error = CheckIovecs(iov, iovcnt)) return error;
if (d >= GuestSockets::FirstDescriptor) {
const auto result = GuestSockets::Writev(d, reinterpret_cast<const GuestSockets::Iovec*>(iov), iovcnt);
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}
const auto result = static_cast<std::int64_t>(::writev(d, NativeIovecs(iov), iovcnt));
return result < 0 ? SceErrorFromErrno(errno) : result;
}

View File

@ -4,6 +4,10 @@
#include <vector>
namespace GuestSockets {
constexpr int FirstDescriptor = 0x10000000;
struct Iovec {
void* base;
std::size_t length;
};
int Duplicate(int descriptor);
int DuplicateTo(int descriptor, int target);
int Close(int descriptor);
@ -19,6 +23,8 @@ struct Interest {
std::uintptr_t CurrentWaker();
void Wake(std::uintptr_t waker);
bool WaitAny(const std::vector<Interest>& interests, std::uint64_t deadlineNanos);
std::int64_t Readv(int descriptor, const Iovec* iov, int iovcnt);
std::int64_t Writev(int descriptor, const Iovec* iov, int iovcnt);
}
extern "C" bool GuestSocketIsOpen_nid_no_patch(int descriptor);

View File

@ -915,3 +915,50 @@ std::int64_t GuestSockets::Read(int descriptor, void* buffer, std::size_t length
std::int64_t GuestSockets::Write(int descriptor, const void* buffer, std::size_t length) {
return send_nid_postfix(descriptor, buffer, length, 0);
}
std::int64_t GuestSockets::Readv(int descriptor, const Iovec* iov, int iovcnt) {
std::size_t total = 0;
for (int i = 0; i < iovcnt; ++i) {
if (iov[i].base == nullptr && iov[i].length != 0) return Fail(14);
if (iov[i].length > static_cast<std::size_t>(INT_MAX) - total) return Fail(22);
total += iov[i].length;
}
if (total == 0) return IsOpen(descriptor) ? 0 : Fail(9);
std::vector<char> buffer;
try {
buffer.resize(total);
} catch (const std::bad_alloc&) {
return Fail(12);
}
const auto result = Read(descriptor, buffer.data(), total);
if (result < 0) return result;
std::size_t copied = 0;
for (int i = 0; i < iovcnt && copied < static_cast<std::size_t>(result); ++i) {
const auto amount = std::min(iov[i].length, static_cast<std::size_t>(result) - copied);
if (amount != 0) std::memcpy(iov[i].base, buffer.data() + copied, amount);
copied += amount;
}
return result;
}
std::int64_t GuestSockets::Writev(int descriptor, const Iovec* iov, int iovcnt) {
std::size_t total = 0;
for (int i = 0; i < iovcnt; ++i) {
if (iov[i].base == nullptr && iov[i].length != 0) return Fail(14);
if (iov[i].length > static_cast<std::size_t>(INT_MAX) - total) return Fail(22);
total += iov[i].length;
}
if (total == 0) return Write(descriptor, nullptr, 0);
std::vector<char> buffer;
try {
buffer.resize(total);
} catch (const std::bad_alloc&) {
return Fail(12);
}
std::size_t copied = 0;
for (int i = 0; i < iovcnt; ++i) {
if (iov[i].length != 0) std::memcpy(buffer.data() + copied, iov[i].base, iov[i].length);
copied += iov[i].length;
}
return Write(descriptor, buffer.data(), total);
}

View File

@ -15,6 +15,11 @@ struct PollDescriptor {
short revents;
};
struct Iovec {
void* base;
std::size_t length;
};
extern "C" {
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
@ -31,6 +36,8 @@ std::int64_t APS5_VABI _read_nid_postfix(int, void*, std::size_t);
std::int64_t APS5_VABI _write_nid_postfix(int, const void*, std::size_t);
std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t);
std::int64_t APS5_VABI sceKernelWrite(int, const void*, std::size_t);
std::int64_t APS5_VABI sceKernelReadv(int, const Iovec*, int);
std::int64_t APS5_VABI sceKernelWritev(int, const Iovec*, int);
int* APS5_VABI __error_nid_postfix();
}
@ -86,10 +93,34 @@ int main() {
Require(Readable(server) && sceKernelRead(server, received, sizeof(received)) == sizeof(kernel));
Require(std::strcmp(received, kernel) == 0);
const char vectorFirst[] = "vector ";
const char vectorSecond[] = "write";
Iovec vectorWrite[] = {{const_cast<char*>(vectorFirst), sizeof(vectorFirst) - 1},
{const_cast<char*>(vectorSecond), sizeof(vectorSecond)}};
Require(sceKernelWritev(client, vectorWrite, 2) == sizeof(vectorFirst) + sizeof(vectorSecond) - 1);
char vectorReadFirst[sizeof(vectorFirst) - 1]{};
char vectorReadSecond[sizeof(vectorSecond)]{};
Iovec vectorRead[] = {{vectorReadFirst, sizeof(vectorReadFirst)}, {vectorReadSecond, sizeof(vectorReadSecond)}};
Require(Readable(server) && sceKernelReadv(server, vectorRead, 2) == sizeof(vectorFirst) + sizeof(vectorSecond) - 1);
Require(std::memcmp(vectorReadFirst, "vector ", sizeof(vectorReadFirst)) == 0 && std::strcmp(vectorReadSecond, "write") == 0);
const char sceVectorFirst[] = "sceKernel";
const char sceVectorSecond[] = "Writev";
Iovec sceVectorWrite[] = {{const_cast<char*>(sceVectorFirst), sizeof(sceVectorFirst) - 1},
{const_cast<char*>(sceVectorSecond), sizeof(sceVectorSecond)}};
Require(sceKernelWritev(client, sceVectorWrite, 2) == sizeof(sceVectorFirst) + sizeof(sceVectorSecond) - 1);
char sceVectorReadFirst[sizeof(sceVectorFirst) - 1]{};
char sceVectorReadSecond[sizeof(sceVectorSecond)]{};
Iovec sceVectorRead[] = {{sceVectorReadFirst, sizeof(sceVectorReadFirst)}, {sceVectorReadSecond, sizeof(sceVectorReadSecond)}};
Require(Readable(server) && sceKernelReadv(server, sceVectorRead, 2) == sizeof(sceVectorFirst) + sizeof(sceVectorSecond) - 1);
Require(std::memcmp(sceVectorReadFirst, "sceKernel", sizeof(sceVectorReadFirst)) == 0 && std::strcmp(sceVectorReadSecond, "Writev") == 0);
Require(fcntl_nid_postfix(server, 4, 4) == 0);
*__error_nid_postfix() = 0;
Require(read_nid_postfix(server, received, sizeof(received)) == -1 && *__error_nid_postfix() == 35);
Require(sceKernelRead(server, received, sizeof(received)) == KernelAgain);
Iovec emptyRead[] = {{received, sizeof(received)}};
Require(sceKernelReadv(server, emptyRead, 1) == KernelAgain);
Require(read_nid_postfix(server, received, 0) == 0 && sceKernelRead(server, received, 0) == 0);
Require(close_nid_postfix(client) == 0);

View File

@ -7,6 +7,7 @@
#include <chrono>
extern "C" {
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI fcntl_nid_postfix(int, int, ...);
int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t);
int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*);
@ -36,6 +37,8 @@ struct Msghdr {
extern "C" {
std::int64_t APS5_VABI sendmsg_nid_postfix(int, const Msghdr*, int);
std::int64_t APS5_VABI recvmsg_nid_postfix(int, Msghdr*, int);
std::int64_t APS5_VABI sceKernelReadv(int, const Iovec*, int);
std::int64_t APS5_VABI sceKernelWritev(int, const Iovec*, int);
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
@ -47,6 +50,16 @@ int main() {
std::uint32_t size = destination.size();
Require(getsockname_nid_postfix(receiver, destination.data(), &size) == 0 && size == 16);
Require(destination[2] || destination[3]);
Require(connect_nid_postfix(sender, destination.data(), destination.size()) == 0);
char vectorHead[] = "atomic ";
char vectorTail[] = "datagram";
Iovec vectorOutput[]{{vectorHead, sizeof(vectorHead) - 1}, {vectorTail, sizeof(vectorTail)}};
Require(sceKernelWritev(sender, vectorOutput, 2) == sizeof(vectorHead) + sizeof(vectorTail) - 1);
char vectorFirst[sizeof(vectorHead) - 1]{};
char vectorSecond[sizeof(vectorTail)]{};
Iovec vectorInput[]{{vectorFirst, sizeof(vectorFirst)}, {vectorSecond, sizeof(vectorSecond)}};
Require(sceKernelReadv(receiver, vectorInput, 2) == sizeof(vectorHead) + sizeof(vectorTail) - 1);
Require(std::strcmp(vectorFirst, "atomic ") == 0 && std::strcmp(vectorSecond, "datagram") == 0);
int enabled = 1;
Require(setsockopt_nid_postfix(sender, 0xffff, 0x20, &enabled, sizeof(enabled)) == 0);
int option = 0;

View File

@ -488,7 +488,7 @@ Throughout the project, every function at every stage either **does exactly what
- [collate<char>::_Getcat](../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) builds the facet for the C locale only and throws for any other locale name. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size, and for results of 4095 characters or more, whose string storage uses the guest's over-aligned large allocation.
- [The scanf family](../../core/libs/prx/libc/src/Formatting.cpp) (libc) hands `%i` to glibc on Linux, which since 2.38 reads the C23 `0b`/`0B` binary prefix when the library is built as GNU C++: `sscanf("0b101", "%i", &value)` stores 5 where C17 stores 0 and stops at the `b`. The strtol family stops at the prefix as C17 does
- [socketpair](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - the pair is the host's `AF_UNIX` pair on Linux and, on Windows, a loopback TCP connection for `SOCK_STREAM` or two connected UDP sockets for `SOCK_DGRAM`; `SOCK_SEQPACKET` throws on Windows. A datagram sent to a full receiver gets `ENOBUFS` on the console, waits or gets `EAGAIN` on Linux and is dropped on Windows, and on Windows a datagram `recv` after the peer closed reports `ECONNRESET` instead of the console's `ENOTCONN`, and a stream `send` after the peer closed reports `ECONNABORTED` instead of the console's `EPIPE`. `read` and `write` on a pair end go to the host like on every guest socket. `getpeername` on a pair end succeeds after the peer closed, where the console returns `ENOTCONN`, and `connect`, `bind` and `sendto` with an address on a pair end report an address-family error instead of the console's `EISCONN`. On Windows `poll` on a stream end reports `POLLHUP` without `POLLIN` after the peer's `shutdown(SHUT_WR)` or `close`, where the console reports `POLLIN`, with `POLLHUP` only after `close`
- [read and write](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket are `recv` and `send` without flags, and `sceKernelRead` and `sceKernelWrite` return the kernel error for them; `EPIPE` raises the host's `SIGPIPE` on Linux, which the guest can neither ignore (`signal` maps only six signals) nor suppress (`SO_NOSIGPIPE` is not an option here), and raises nothing on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it; `readv`, `writev`, `pread`, `pwrite`, their `sceKernel` forms and AIO on a guest socket still go to the host, which has no such descriptor, where the console reads and writes through `readv` and `writev` and returns `ESPIPE` for the positioned forms
- [read, write and vector I/O](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket use `recv` and `send` without flags; `sceKernelReadv` and `sceKernelWritev` gather or scatter through one socket operation, preserving stream short reads and datagram boundaries. The positioned `pread`, `pwrite`, `preadv` and `pwritev` forms still go to the host, which has no such descriptor; the console returns `ESPIPE` for these calls. `EPIPE` from a write raises the host's `SIGPIPE` on Linux, which the guest can neither ignore (`signal` maps only six signals) nor suppress (`SO_NOSIGPIPE` is not an option here), and raises nothing on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it. AIO on a guest socket still goes to the host, which has no such descriptor
- [poll](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits only on guest sockets, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A descriptor below the socket range (files, pipes, stdio) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), `POLLPRI` on Windows, which `WSAPoll` rejects, and an infinite timeout with no socket to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked
- [msync](../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `kern_msync` and `vm_map_sync`, not measured on the console: the address is rounded down to the page, a range that wraps is `EINVAL`, as is `MS_ASYNC` with `MS_INVALIDATE`, and a range whose start or any page is not mapped (as `sceKernelVirtualQuery` reports it, reservations included) is `ENOMEM`; a page unmapped from inside a larger mapping still counts as mapped there, since `sceKernelVirtualQuery` keeps reporting the whole mapping. The guest has no file mappings, so a successful call writes nothing back. `MS_INVALIDATE` on a mapped range throws: FreeBSD returns `EBUSY` when the range holds `mlock`ed pages, and locked pages are not tracked
- [Thread exit destructors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped