Merge pull request #4391 from MarcZX100/codex/socket-descriptors-4366

fix(libkernel): give guest sockets ordinary descriptor numbers below FD_SETSIZE
This commit is contained in:
Dean 2026-10-13 19:52:08 +13:00 committed by GitHub
commit c2565f0149
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 598 additions and 222 deletions

View File

@ -620,8 +620,9 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol)
close_socket(native);
return fail(12);
}
const int fd = KernelReserveSocketNumber_nid_no_patch(static_cast<std::uintptr_t>(handle->value));
if (fd < 0) return fail(-fd);
std::lock_guard lk(g_mutex);
const int fd = g_next_sock++;
Sock s;
s.family = family;
s.type = type;
@ -639,6 +640,7 @@ int APS5_VABI sceNetSocketClose(int s) {
native = it->second.native;
g_socks.erase(it);
}
KernelReleaseSocketNumber_nid_no_patch(s);
if (native) {
#ifdef _WIN32
::shutdown(native->value, SD_BOTH);
@ -735,10 +737,10 @@ int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) {
connection.family = listener.family;
connection.type = NET_SOCK_STREAM;
connection.native = std::move(handle);
int descriptor;
const int descriptor = KernelReserveSocketNumber_nid_no_patch(static_cast<std::uintptr_t>(connection.native->value));
if (descriptor < 0) return fail(-descriptor);
{
std::lock_guard lk(g_mutex);
descriptor = g_next_sock++;
g_socks.emplace(descriptor, connection);
}
if (addr && !native_to_guest_address(peer, addr, addrlen)) {

View File

@ -62,8 +62,8 @@ extern "C" int APS5_VABI mkstemp_nid_postfix(char* pattern) {
}
extern "C" int APS5_VABI isatty_nid_postfix(int descriptor) {
if (descriptor >= GuestSockets::FirstDescriptor) {
Failure(GuestSocketIsOpen_nid_no_patch(descriptor) ? 25 : 9);
if (GuestSocketIsOpen_nid_no_patch(descriptor)) {
Failure(25);
return 0;
}
#ifdef _WIN32

View File

@ -1043,6 +1043,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_kqueue PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_socket_descriptors_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketDescriptors.cpp)
target_include_directories(guest_socket_descriptors_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_socket_descriptors_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_socket_descriptors_tests)
add_test(NAME guest_socket_descriptors COMMAND guest_socket_descriptors_tests)
set_tests_properties(guest_socket_descriptors PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_socket_descriptors PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_socket_event_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketEvents.cpp)
target_include_directories(guest_socket_event_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_socket_event_tests PRIVATE libkernel libc)

View File

@ -2,6 +2,7 @@
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include <algorithm>
#include <chrono>
@ -593,13 +594,14 @@ int APS5_VABI sceKernelDeleteAmprSystemEvent(KernelEqueue eq, int id) {
}
static int AddDescriptorEvent(KernelEqueue eq, int fd, std::size_t size, void* udata, int16_t filter) {
if (fd < GuestSockets::FirstDescriptor) {
const bool isSocket = GuestSockets::IsOpen(fd);
if (!isSocket && DescriptorIsOpen_nid_no_patch(fd)) {
NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: non-socket descriptor" : "sceKernelAddWriteEvent: non-socket descriptor");
}
if (size > 1) {
NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: low watermark" : "sceKernelAddWriteEvent: low watermark");
}
if (!GuestSockets::IsOpen(fd)) {
if (!isSocket) {
return SCE_KERNEL_ERROR_EBADF;
}
return EqueueAddEvent_nid_postfix(eq, EqueueDescriptorEvent(fd, filter, EV_ADD, udata));

View File

@ -559,8 +559,8 @@ int VnodeDescriptorError(int descriptor) {
if (descriptor < 0) {
return GUEST_EBADF;
}
if (descriptor >= GuestSockets::FirstDescriptor) {
return GuestSockets::IsOpen(descriptor) ? GUEST_EINVAL : GUEST_EBADF;
if (GuestSockets::IsOpen(descriptor)) {
return GUEST_EINVAL;
}
if (!DescriptorIsOpen_nid_no_patch(descriptor)) {
return GUEST_EBADF;
@ -647,7 +647,7 @@ int APS5_VABI sceKernelDeleteFileEvent(KernelEqueue eq, int fd) {
if (!EqueuePin_nid_postfix(eq)) {
return SCE_KERNEL_ERROR_EBADF;
}
if (fd < 0 || (fd >= GuestSockets::FirstDescriptor ? !GuestSockets::IsOpen(fd) : !DescriptorIsOpen_nid_no_patch(fd))) {
if (fd < 0 || (!GuestSockets::IsOpen(fd) && !DescriptorIsOpen_nid_no_patch(fd))) {
return SCE_KERNEL_ERROR_EBADF;
}
return EqueueDeleteEvent_nid_postfix(eq, static_cast<std::uintptr_t>(fd), EVFILT_VNODE);

View File

@ -118,8 +118,8 @@ int DescriptorFilterError(int kqueueDescriptor, std::uintptr_t ident) {
return GUEST_EBADF;
}
const int descriptor = static_cast<int>(ident);
if (descriptor >= GuestSockets::FirstDescriptor) {
return GuestSockets::IsOpen(descriptor) ? 0 : GUEST_EBADF;
if (GuestSockets::IsOpen(descriptor)) {
return 0;
}
if (!DescriptorIsOpen_nid_no_patch(descriptor)) {
return GUEST_EBADF;
@ -135,7 +135,7 @@ int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::u
}
NotImplemented_nid_no_patch("kevent: EVFILT_READ on a kqueue descriptor");
}
if (descriptor < GuestSockets::FirstDescriptor) {
if (!GuestSockets::IsOpen(descriptor)) {
NotImplemented_nid_no_patch(change.filter == EVFILT_READ ? "kevent: EVFILT_READ on a non-socket descriptor" : "kevent: EVFILT_WRITE on a non-socket descriptor");
}
if ((change.fflags & NOTE_LOWAT) != 0) {
@ -325,10 +325,6 @@ int APS5_VABI kqueue_nid_postfix() {
if (errno == ENFILE) return PosixFailure(GUEST_ENFILE);
throw std::runtime_error(std::string(__func__) + ": reserving a descriptor failed, errno=" + std::to_string(errno));
}
if (descriptor >= GuestSockets::FirstDescriptor) {
ReleaseDescriptor(descriptor);
throw std::runtime_error(std::string(__func__) + ": host descriptor reached the guest socket range");
}
auto queue = std::make_shared<KernelEqueuePrivate>(static_cast<KernelEqueue>(descriptor), true);
queue->SetName("kqueue");
std::lock_guard lock(g_kqueueMutex);

View File

@ -222,7 +222,7 @@ int OpenInDirectory_nid_no_patch(int directory, const char* path, int flags, std
}
int APS5_VABI sceKernelClose(int d) {
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d) == 0 ? 0 : SceKernelError(*__error_nid_postfix());
if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d) == 0 ? 0 : SceKernelError(*__error_nid_postfix());
EqueueDescriptorClosed(d);
File::ForgetRandomDevice(d);
File::ForgetDescriptor(d);
@ -239,7 +239,7 @@ int APS5_VABI sceKernelClose(int d) {
}
std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) {
if (d >= GuestSockets::FirstDescriptor) {
if (GuestSockets::IsOpen(d)) {
const auto n = GuestSockets::Read(d, buf, nbytes);
return n < 0 ? SceKernelError(*__error_nid_postfix()) : n;
}
@ -255,7 +255,7 @@ std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) {
}
std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes) {
if (d >= GuestSockets::FirstDescriptor) {
if (GuestSockets::IsOpen(d)) {
const auto n = GuestSockets::Write(d, buf, nbytes);
return n < 0 ? SceKernelError(*__error_nid_postfix()) : n;
}
@ -268,6 +268,7 @@ std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes
}
std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) {
if (GuestSockets::IsOpen(d)) return SCE_KERNEL_ERROR_ESPIPE;
if (whence < 0 || whence > 2) {
throw std::invalid_argument(std::string(__func__) + ": invalid whence=" + std::to_string(whence));
}
@ -342,9 +343,12 @@ int APS5_VABI sceKernelFcntl(int d, int command, ...) {
#endif
}
int result;
if (d < GuestSockets::FirstDescriptor) result = File::Fcntl(d, command, argument);
else if (command == GuestSetFlags) result = fcntl_nid_postfix(d, command, static_cast<int>(argument));
else result = fcntl_nid_postfix(d, command);
if (GuestSockets::IsOpen(d)) {
if (command == GuestSetFlags) result = fcntl_nid_postfix(d, command, static_cast<int>(argument));
else result = fcntl_nid_postfix(d, command);
} else {
result = File::Fcntl(d, command, argument);
}
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}

View File

@ -302,17 +302,17 @@ static constexpr int GUEST_EDEADLK = 11;
static constexpr int GUEST_ELOOP = 62;
static constexpr int GUEST_EDQUOT = 69;
static constexpr int GUEST_EOVERFLOW = 84;
static constexpr int GUEST_EOPNOTSUPP = 45;
static int SceErrorFromErrno(int error);
static std::int64_t PositionedGuestSocketError(int descriptor) {
if (descriptor < GuestSockets::FirstDescriptor) return 0;
return SceErrorFromErrno(GuestSockets::IsOpen(descriptor) ? GUEST_ESPIPE : GUEST_EBADF);
return GuestSockets::IsOpen(descriptor) ? SceErrorFromErrno(GUEST_ESPIPE) : 0;
}
static bool FailPosixPositionedGuestSocket(int descriptor) {
if (descriptor < GuestSockets::FirstDescriptor) return false;
errno = GuestSockets::IsOpen(descriptor) ? GUEST_ESPIPE : GUEST_EBADF;
if (!GuestSockets::IsOpen(descriptor)) return false;
errno = GUEST_ESPIPE;
return true;
}
@ -472,8 +472,6 @@ int File::DuplicateFrom(int fd, int start) {
#else
const int duplicate = ::fcntl(fd, F_DUPFD, start);
if (duplicate < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
if (duplicate >= GuestSockets::FirstDescriptor)
throw std::runtime_error("fcntl: host descriptor reached the guest socket range");
if (File::IsRandomDevice(fd)) File::RememberRandomDevice(duplicate);
File::ShareDescriptor(fd, duplicate);
return duplicate;
@ -491,13 +489,13 @@ int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
}
int APS5_VABI close_nid_postfix(int d) {
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d);
int socketError = 0;
if (KernelCloseSocket_nid_no_patch(d, &socketError)) {
if (socketError == 0) return 0;
*__error_nid_postfix() = socketError;
return -1;
}
if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d);
EqueueDescriptorClosed(d);
File::ForgetRandomDevice(d);
File::ForgetDescriptor(d);
@ -544,7 +542,7 @@ int File::HostStandardError() {
extern "C" {
int APS5_VABI dup_nid_postfix(int d) {
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Duplicate(d);
if (GuestSockets::IsOpen(d)) return GuestSockets::Duplicate(d);
if (d < 0) return PosixFailure(GUEST_EBADF);
if (IsKqueueDescriptor(d)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported");
#ifdef _WIN32
@ -554,8 +552,6 @@ int APS5_VABI dup_nid_postfix(int d) {
const int duplicate = ::dup(d);
#endif
if (duplicate < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
if (duplicate >= GuestSockets::FirstDescriptor)
throw std::runtime_error(std::string(__func__) + ": host descriptor reached the guest socket range");
if (File::IsRandomDevice(d)) File::RememberRandomDevice(duplicate);
File::ShareDescriptor(d, duplicate);
DuplicateJitSharedMemory(d, duplicate);
@ -564,15 +560,18 @@ int APS5_VABI dup_nid_postfix(int d) {
int APS5_VABI dup2_nid_postfix(int from, int to) {
if (from < 0 || to < 0) return PosixFailure(GUEST_EBADF);
const bool socketFrom = from >= GuestSockets::FirstDescriptor;
const bool socketTo = to >= GuestSockets::FirstDescriptor;
if (socketFrom && socketTo) return GuestSockets::DuplicateTo(from, to);
if (socketFrom) {
if (!GuestSockets::IsOpen(from)) return PosixFailure(GUEST_EBADF);
throw std::runtime_error(std::string(__func__) + ": moving a guest socket onto host descriptor " + std::to_string(to) + " is not supported");
if (GuestSockets::IsOpen(from)) {
if (from != to && to <= 2)
throw std::runtime_error(std::string(__func__) + ": moving a guest socket onto standard descriptor " + std::to_string(to) + " is not supported");
if (GuestSockets::DuplicateTo(from, to) < 0) return -1;
if (from == to) return to;
File::ForgetRandomDevice(to);
#ifdef _WIN32
File::ForgetDirectoryDescriptor(to);
File::ForgetFileLock(to);
#endif
return to;
}
if (socketTo)
throw std::runtime_error(std::string(__func__) + ": moving host descriptor " + std::to_string(from) + " into the guest socket range is not supported");
const bool sourceIsRandom = File::IsRandomDevice(from);
if (from != to && IsKqueueDescriptor(from)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported");
#ifdef _WIN32
@ -584,6 +583,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
KeepStandardHandle_nid_no_patch(to, standard);
if (duplicated != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
EqueueDescriptorClosed(to);
GuestSockets::Forget(to);
File::ForgetDirectoryDescriptor(to);
File::DuplicateDirectoryDescriptor(from, to);
File::ForgetFileLock(to);
@ -601,6 +601,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
const int result = ::dup2(from, to);
if (result < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
EqueueDescriptorClosed(to);
GuestSockets::Forget(to);
File::ForgetRandomDevice(to);
if (sourceIsRandom) File::RememberRandomDevice(to);
File::ShareDescriptor(from, to);
@ -610,6 +611,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
}
int APS5_VABI flock_nid_postfix(int d, int operation) {
if (GuestSockets::IsOpen(d)) return PosixFailure(GUEST_EOPNOTSUPP);
const int type = operation & 8 ? 8 : operation & 2 ? 2 : operation & 1 ? 1 : 0;
if (type == 0) return PosixFailure(GUEST_EBADF);
if (NativeFlock(d, type | (operation & 4)) != 0) {
@ -627,7 +629,7 @@ int APS5_VABI flock_nid_postfix(int d, int operation) {
}
int APS5_VABI _fstatfs_nid_postfix(int d, KernelStatfs* buf) {
if (d >= GuestSockets::FirstDescriptor) return PosixFailure(GuestSockets::IsOpen(d) ? GUEST_EINVAL : GUEST_EBADF);
if (GuestSockets::IsOpen(d)) return PosixFailure(GUEST_EINVAL);
if (d < 0) return PosixFailure(GUEST_EBADF);
KernelStatfs result{};
result.f_version = KERNEL_STATFS_VERSION;
@ -643,12 +645,13 @@ int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) {
if (sb == nullptr) {
APS5_INVALID_ARG_EX;
}
File::FillFileStat(d, sb);
if (GuestSockets::IsOpen(d)) throw std::runtime_error(std::string(__func__) + ": the status of a guest socket is not implemented");
if (!File::FillFileStatFromDescriptor(d, sb)) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
return 0;
}
int APS5_VABI ftruncate_nid_postfix(int d, int64_t length) {
if (length < 0) {
if (length < 0 || GuestSockets::IsOpen(d)) {
return PosixFailure(GUEST_EINVAL);
}
#ifdef _WIN32
@ -726,6 +729,7 @@ int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t off
if (!destination.Open()) errno = EFAULT;
else if (File::ReadRandomDevice(d, buf, nbytes)) return static_cast<int64_t>(nbytes);
auto n = destination.Open() ? NativePread(d, buf, nbytes, offset) : -1;
if (n < 0 && errno == EBADF) return PosixFailure(GUEST_EBADF);
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": pread failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
@ -742,14 +746,14 @@ int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_
}
int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) {
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Read(d, buf, static_cast<size_t>(nbytes));
if (GuestSockets::IsOpen(d)) return GuestSockets::Read(d, buf, static_cast<size_t>(nbytes));
if (buf == nullptr && nbytes != 0) return PosixFailure(GUEST_EFAULT);
char emptyBuffer = 0;
return PosixResult(sceKernelRead(d, buf == nullptr ? &emptyBuffer : buf, static_cast<size_t>(nbytes)));
}
std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) {
if (descriptor >= GuestSockets::FirstDescriptor) return GuestSockets::Read(descriptor, buffer, count);
if (GuestSockets::IsOpen(descriptor)) return GuestSockets::Read(descriptor, buffer, count);
if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT);
char emptyBuffer = 0;
return PosixResult(sceKernelRead(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count));
@ -759,14 +763,14 @@ int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) {
if (size < 0) {
APS5_INVALID_ARG_EX;
}
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Write(d, str, static_cast<std::size_t>(size));
if (GuestSockets::IsOpen(d)) return GuestSockets::Write(d, str, static_cast<std::size_t>(size));
if (str == nullptr && size != 0) return PosixFailure(GUEST_EFAULT);
const char emptyBuffer = 0;
return PosixResult(sceKernelWrite(d, str == nullptr ? &emptyBuffer : str, static_cast<size_t>(size)));
}
std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) {
if (descriptor >= GuestSockets::FirstDescriptor) return GuestSockets::Write(descriptor, buffer, count);
if (GuestSockets::IsOpen(descriptor)) return GuestSockets::Write(descriptor, buffer, count);
if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT);
const char emptyBuffer = 0;
return PosixResult(sceKernelWrite(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count));
@ -805,12 +809,13 @@ int APS5_VABI sceKernelCheckReachability(const char* path) {
int APS5_VABI sceKernelFstat(int d, FileStat* sb) {
if (sb == nullptr) throw std::invalid_argument("sceKernelFstat: sb is null");
if (GuestSockets::IsOpen(d)) throw std::runtime_error("sceKernelFstat: the status of a guest socket is not implemented");
if (!File::FillFileStatFromDescriptor(d, sb)) return SceErrorFromErrno(errno);
return 0;
}
int APS5_VABI sceKernelFsync(int fd) {
if (fd >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(fd) ? GUEST_EINVAL : GUEST_EBADF);
if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL);
#ifdef _WIN32
if (NativeSync(fd) != 0) return SceErrorFromErrno(errno);
#else
@ -820,7 +825,7 @@ int APS5_VABI sceKernelFsync(int fd) {
}
int APS5_VABI sceKernelFdatasync(int fd) {
if (fd >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(fd) ? GUEST_EINVAL : GUEST_EBADF);
if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL);
#ifdef _WIN32
if (NativeSync(fd) != 0) return SceErrorFromErrno(errno);
#else
@ -841,6 +846,7 @@ int APS5_VABI sceKernelWriteThrottlingStatus(std::uint64_t* status) {
#ifdef _WIN32
int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep) {
if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL);
if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT);
if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL);
if (basep != nullptr) *basep = 0;
@ -854,6 +860,7 @@ int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes) {
#else
int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep) {
if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL);
constexpr std::size_t GuestHeaderBytes = 8;
constexpr std::size_t GuestMaxName = 255;
if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT);
@ -984,7 +991,7 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, co
}
std::deque<GuestArena::HostWrite> destinations;
if (!write && !OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT);
if (offset == nullptr && d >= GuestSockets::FirstDescriptor) {
if (offset == nullptr && GuestSockets::IsOpen(d)) {
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;
@ -1052,7 +1059,7 @@ 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) {
if (GuestSockets::IsOpen(d)) {
const auto result = GuestSockets::Readv(d, reinterpret_cast<const GuestSockets::Iovec*>(iov), iovcnt);
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}
@ -1063,7 +1070,7 @@ 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) {
if (GuestSockets::IsOpen(d)) {
const auto result = GuestSockets::Writev(d, reinterpret_cast<const GuestSockets::Iovec*>(iov), iovcnt);
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}
@ -1159,7 +1166,7 @@ int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) {
}
int APS5_VABI sceKernelFchmod(int d, std::uint16_t mode) {
if (d >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(d) ? GUEST_EINVAL : GUEST_EBADF);
if (GuestSockets::IsOpen(d)) return SceErrorFromErrno(GUEST_EINVAL);
if (NativeFchmod(d, mode & 07777) != 0) return SceErrorFromErrno(errno);
return 0;
}
@ -1208,7 +1215,7 @@ int APS5_VABI utimes_nid_postfix(const char* path, const KernelTimeval* times) {
}
int APS5_VABI sceKernelFutimes(int d, const KernelTimeval* times) {
if (d >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(d) ? GUEST_EINVAL : GUEST_EBADF);
if (GuestSockets::IsOpen(d)) return SceErrorFromErrno(GUEST_EINVAL);
if (times != nullptr) {
for (int i = 0; i < 2; ++i) {
if (times[i].tv_usec < 0 || times[i].tv_usec >= 1000000) return SceErrorFromErrno(GUEST_EINVAL);

View File

@ -59,11 +59,8 @@ bool HostPipe(int descriptor) {
#endif
int TerminalError(int descriptor) {
if (descriptor >= GuestSockets::FirstDescriptor) {
const int family = GuestSockets::Family(descriptor);
if (family < 0) return BadDescriptor;
return family == InternetFamily ? AddressNotAvailable : OperationNotSupported;
}
const int family = GuestSockets::Family(descriptor);
if (family >= 0) return family == InternetFamily ? AddressNotAvailable : OperationNotSupported;
if (descriptor < 0 || !HostOpen(descriptor)) return BadDescriptor;
return NotTerminal;
}

View File

@ -25,3 +25,5 @@ extern "C" void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer clos
extern "C" bool KernelCloseSocket_nid_no_patch(int descriptor, int* error);
extern "C" void KernelSetSocketOptionSetter_nid_no_patch(KernelSocketPoll::OptionSetter setter);
extern "C" bool KernelSetSocketOption_nid_no_patch(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error);
extern "C" int KernelReserveSocketNumber_nid_no_patch(std::uintptr_t native);
extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number);

View File

@ -2,8 +2,8 @@
#include <cstddef>
#include <cstdint>
#include <vector>
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
namespace GuestSockets {
constexpr int FirstDescriptor = 0x10000000;
struct Iovec {
void* base;
std::size_t length;
@ -11,11 +11,13 @@ struct Iovec {
int Duplicate(int descriptor);
int DuplicateTo(int descriptor, int target);
int Close(int descriptor);
void Forget(int descriptor);
bool IsOpen(int descriptor);
int Family(int descriptor);
std::int64_t Read(int descriptor, void* buffer, std::size_t length);
std::int64_t Write(int descriptor, const void* buffer, std::size_t length);
bool Ready(int descriptor, bool write, std::int64_t* data, bool* eof);
int Poll(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds);
struct Interest {
int descriptor;
bool write;

View File

@ -47,6 +47,16 @@ void ClearDescriptor(void* set, int descriptor) {
if (set != nullptr) static_cast<std::uint64_t*>(set)[descriptor / 64] &= ~(std::uint64_t{1} << (descriptor % 64));
}
struct PollGroup {
KernelSocketPoll::Poller poll;
std::vector<KernelSocketPoll::Entry> entries;
std::vector<std::size_t> slots;
};
int KernelSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) {
return GuestSockets::Poll(entries, count, timeoutMilliseconds);
}
#ifndef _WIN32
short HostEventFlags(short events) {
@ -65,20 +75,17 @@ short KernelEventFlags(short revents) {
return flags;
}
int HostPoll(KernelSocketPoll::Entry* descriptors, std::size_t count, int timeoutMilliseconds) {
std::vector<struct pollfd> native(count);
for (std::size_t index = 0; index < count; ++index) {
int HostPoll(KernelSocketPoll::Entry* descriptors, int count, int timeoutMilliseconds) {
std::vector<struct pollfd> native(static_cast<std::size_t>(count));
for (std::size_t index = 0; index < native.size(); ++index) {
native[index].fd = descriptors[index].descriptor;
native[index].events = descriptors[index].events;
native[index].events = HostEventFlags(descriptors[index].events);
native[index].revents = 0;
}
const int result = ::poll(native.data(), static_cast<nfds_t>(count), timeoutMilliseconds);
if (result < 0) return -1;
for (std::size_t index = 0; index < count; ++index) {
if ((native[index].revents & POLLNVAL) != 0) {
errno = EBADF;
return -1;
}
const int result = ::poll(native.data(), static_cast<nfds_t>(native.size()), timeoutMilliseconds);
if (result < 0) return -errno;
for (std::size_t index = 0; index < native.size(); ++index) {
if ((native[index].revents & POLLNVAL) != 0) return -EBADF;
descriptors[index].revents = KernelEventFlags(native[index].revents);
}
return result;
@ -131,10 +138,8 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void*
*__error_nid_postfix() = GuestEinval;
return -1;
}
std::vector<KernelSocketPoll::Entry> sockets;
std::vector<std::size_t> socketSlots;
std::vector<KernelSocketPoll::Entry> hosts;
std::vector<std::size_t> hostSlots;
PollGroup kernel{&KernelSockets, {}, {}};
PollGroup net{nullptr, {}, {}};
std::vector<KernelSocketPoll::Entry> devices;
std::vector<KernelSocketPoll::Entry> requested;
for (int descriptor = 0; descriptor < nfds; ++descriptor) {
@ -143,57 +148,50 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void*
if (DescriptorSet(writefds, descriptor)) events |= KernelSocketPoll::Writable;
if (DescriptorSet(exceptfds, descriptor)) events |= KernelSocketPoll::Urgent;
if (events == 0) continue;
if (File::IsRandomDevice(descriptor)) devices.push_back({descriptor, events, static_cast<short>(events & (KernelSocketPoll::Readable | KernelSocketPoll::Writable))});
if (GuestSockets::IsOpen(descriptor)) {
kernel.entries.push_back({descriptor, events, 0});
kernel.slots.push_back(requested.size());
requested.push_back({descriptor, events, 0});
} else if (File::IsRandomDevice(descriptor)) devices.push_back({descriptor, events, static_cast<short>(events & (KernelSocketPoll::Readable | KernelSocketPoll::Writable))});
else if (descriptor <= 2) devices.push_back({descriptor, events, static_cast<short>(descriptor == 0 ? 0 : events & KernelSocketPoll::Writable)});
else {
sockets.push_back({descriptor, events, 0});
socketSlots.push_back(requested.size());
net.entries.push_back({descriptor, events, 0});
net.slots.push_back(requested.size());
requested.push_back({descriptor, events, 0});
}
}
for (const auto& device : devices) requested.push_back(device);
const bool devicesReady = std::any_of(devices.begin(), devices.end(), [](const auto& entry) { return entry.revents != 0; });
const auto poller = g_socketPoller.load();
if (!sockets.empty()) {
if (poller == nullptr) {
for (std::size_t index = 0; index < sockets.size();) {
const int descriptor = sockets[index].descriptor;
std::vector<PollGroup> groups;
if (!kernel.entries.empty()) groups.push_back(std::move(kernel));
if (!net.entries.empty()) {
PollGroup hosts{nullptr, {}, {}};
#ifndef _WIN32
if (descriptor < GuestSockets::FirstDescriptor) {
hosts.push_back({descriptor, HostEventFlags(sockets[index].events), 0});
hostSlots.push_back(socketSlots[index]);
sockets.erase(sockets.begin() + static_cast<std::ptrdiff_t>(index));
socketSlots.erase(socketSlots.begin() + static_cast<std::ptrdiff_t>(index));
continue;
}
hosts.poll = &HostPoll;
#endif
UnknownDescriptor(descriptor, false);
}
} else {
for (auto& socket : sockets) socket.revents = 0;
const int probe = poller(sockets.data(), static_cast<int>(sockets.size()), 0);
net.poll = g_socketPoller.load();
if (net.poll != nullptr) {
for (auto& socket : net.entries) socket.revents = 0;
const int probe = net.poll(net.entries.data(), static_cast<int>(net.entries.size()), 0);
if (probe < 0) {
*__error_nid_postfix() = -probe;
return -1;
}
for (std::size_t index = 0; index < sockets.size();) {
if ((sockets[index].revents & KernelSocketPoll::Unknown) == 0) {
++index;
continue;
}
const int descriptor = sockets[index].descriptor;
#ifndef _WIN32
if (descriptor < GuestSockets::FirstDescriptor) {
hosts.push_back({descriptor, HostEventFlags(sockets[index].events), 0});
hostSlots.push_back(socketSlots[index]);
sockets.erase(sockets.begin() + static_cast<std::ptrdiff_t>(index));
socketSlots.erase(socketSlots.begin() + static_cast<std::ptrdiff_t>(index));
continue;
}
#endif
UnknownDescriptor(descriptor, true);
}
}
for (std::size_t index = 0; index < net.entries.size();) {
if (net.poll != nullptr && (net.entries[index].revents & KernelSocketPoll::Unknown) == 0) {
++index;
continue;
}
const int descriptor = net.entries[index].descriptor;
if (hosts.poll == nullptr) UnknownDescriptor(descriptor, net.poll != nullptr);
hosts.entries.push_back({descriptor, net.entries[index].events, 0});
hosts.slots.push_back(net.slots[index]);
net.entries.erase(net.entries.begin() + static_cast<std::ptrdiff_t>(index));
net.slots.erase(net.slots.begin() + static_cast<std::ptrdiff_t>(index));
}
if (!net.entries.empty()) groups.push_back(std::move(net));
if (!hosts.entries.empty()) groups.push_back(std::move(hosts));
}
const auto start = std::chrono::steady_clock::now();
const auto deadline = limit == nullptr ? std::chrono::steady_clock::time_point::max()
@ -206,48 +204,25 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void*
waitMilliseconds = static_cast<int>(std::clamp<std::int64_t>(roundedMilliseconds, 0, 100));
}
if (devicesReady) waitMilliseconds = 0;
#ifndef _WIN32
if (!hosts.empty() && !sockets.empty()) {
if (poller == nullptr) UnknownDescriptor(sockets.front().descriptor, false);
if (groups.empty()) {
if (waitMilliseconds > 0) std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds));
} else {
const int slice = groups.size() == 1 ? waitMilliseconds : std::min(waitMilliseconds, SelectPollSliceMilliseconds);
const auto sliceEnd = std::chrono::steady_clock::now() + std::chrono::milliseconds(waitMilliseconds);
for (;;) {
for (auto& host : hosts) host.revents = 0;
const int hostReady = HostPoll(hosts.data(), hosts.size(), SelectPollSliceMilliseconds);
if (hostReady < 0) {
*__error_nid_postfix() = errno;
return -1;
int groupReady = 0;
for (auto& group : groups) {
for (auto& entry : group.entries) entry.revents = 0;
const int result = group.poll(group.entries.data(), static_cast<int>(group.entries.size()), slice);
if (result < 0) {
*__error_nid_postfix() = -result;
return -1;
}
for (std::size_t index = 0; index < group.entries.size(); ++index) requested[group.slots[index]].revents = group.entries[index].revents;
groupReady += result;
}
for (std::size_t index = 0; index < hosts.size(); ++index) requested[hostSlots[index]].revents = hosts[index].revents;
for (auto& socket : sockets) socket.revents = 0;
const int socketReady = poller(sockets.data(), static_cast<int>(sockets.size()), SelectPollSliceMilliseconds);
if (socketReady < 0) {
*__error_nid_postfix() = -socketReady;
return -1;
}
for (std::size_t index = 0; index < sockets.size(); ++index) requested[socketSlots[index]].revents = sockets[index].revents;
if (hostReady > 0 || socketReady > 0) break;
if (std::chrono::steady_clock::now() >= sliceEnd) break;
if (groups.size() == 1 || groupReady > 0 || std::chrono::steady_clock::now() >= sliceEnd) break;
}
} else if (!hosts.empty()) {
for (auto& host : hosts) host.revents = 0;
if (HostPoll(hosts.data(), hosts.size(), waitMilliseconds) < 0) {
*__error_nid_postfix() = errno;
return -1;
}
for (std::size_t index = 0; index < hosts.size(); ++index) requested[hostSlots[index]].revents = hosts[index].revents;
} else
#endif
if (!sockets.empty()) {
if (poller == nullptr) UnknownDescriptor(sockets.front().descriptor, false);
for (auto& socket : sockets) socket.revents = 0;
const int result = poller(sockets.data(), static_cast<int>(sockets.size()), waitMilliseconds);
if (result < 0) {
*__error_nid_postfix() = -result;
return -1;
}
for (std::size_t index = 0; index < sockets.size(); ++index) requested[socketSlots[index]].revents = sockets[index].revents;
} else if (waitMilliseconds > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds));
}
int ready = 0;
for (const auto& entry : requested) {

View File

@ -2,6 +2,7 @@
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <fcntl.h>
#include <io.h>
#else
#include <sys/socket.h>
@ -129,7 +130,56 @@ Waker& ThreadWaker() {
}
std::mutex socketsMutex;
std::map<int, std::shared_ptr<Socket>> sockets;
int nextDescriptor = GuestSockets::FirstDescriptor;
#ifdef _WIN32
int ReserveNumber(NativeSocket) {
const HANDLE placeholder = ::CreateFileW(L"NUL", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (placeholder == INVALID_HANDLE_VALUE)
throw std::runtime_error("socket: opening the NUL descriptor placeholder failed, error " + std::to_string(::GetLastError()));
const int number = ::_open_osfhandle(reinterpret_cast<std::intptr_t>(placeholder), _O_RDONLY);
if (number < 0) {
::CloseHandle(placeholder);
return Fail(24);
}
return number;
}
int CopyNumber(int number) {
const int copy = ::_dup(number);
return copy < 0 ? Fail(errno == EMFILE ? 24 : 9) : copy;
}
int CopyNumberTo(int number, int target) {
return ::_dup2(number, target) != 0 ? Fail(errno == EMFILE ? 24 : 9) : 0;
}
void ReleaseNumber(int number) {
::_close(number);
}
#else
int ReserveNumber(NativeSocket native) {
const int number = ::fcntl(native, F_DUPFD_CLOEXEC, 0);
return number < 0 ? Fail(NativeError()) : number;
}
int CopyNumber(int number) {
const int copy = ::fcntl(number, F_DUPFD_CLOEXEC, 0);
return copy < 0 ? Fail(NativeError()) : copy;
}
int CopyNumberTo(int number, int target) {
return ::dup2(number, target) < 0 ? Fail(NativeError()) : 0;
}
void ReleaseNumber(int number) {
::close(number);
}
#endif
int Install(std::shared_ptr<Socket> socket) {
const int number = ReserveNumber(socket->value);
if (number < 0) return -1;
try {
std::lock_guard lock(socketsMutex);
sockets.insert_or_assign(number, std::move(socket));
} catch (const std::bad_alloc&) {
ReleaseNumber(number);
return Fail(12);
}
return number;
}
std::shared_ptr<Socket> Lookup(int descriptor) {
std::lock_guard lock(socketsMutex);
const auto found = sockets.find(descriptor);
@ -221,14 +271,31 @@ void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* l
}
int GuestSockets::Close(int descriptor) {
std::shared_ptr<Socket> released;
{
std::lock_guard lock(socketsMutex);
if (!sockets.erase(descriptor)) return Fail(9);
const auto found = sockets.find(descriptor);
if (found == sockets.end()) return Fail(9);
released = std::move(found->second);
sockets.erase(found);
}
EqueueDescriptorClosed(descriptor);
ReleaseNumber(descriptor);
return 0;
}
void GuestSockets::Forget(int descriptor) {
std::shared_ptr<Socket> released;
{
std::lock_guard lock(socketsMutex);
const auto found = sockets.find(descriptor);
if (found == sockets.end()) return;
released = std::move(found->second);
sockets.erase(found);
}
EqueueDescriptorClosed(descriptor);
}
std::uintptr_t GuestSockets::CurrentWaker() {
return static_cast<std::uintptr_t>(ThreadWaker().value);
}
@ -299,6 +366,15 @@ extern "C" bool GuestSocketIsOpen_nid_no_patch(int descriptor) {
return GuestSockets::IsOpen(descriptor);
}
extern "C" int KernelReserveSocketNumber_nid_no_patch(std::uintptr_t native) {
const int number = ReserveNumber(static_cast<NativeSocket>(native));
return number < 0 ? -*__error_nid_postfix() : number;
}
extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number) {
ReleaseNumber(number);
}
namespace {
#ifdef _WIN32
using NativePollDescriptor = WSAPOLLFD;
@ -389,33 +465,43 @@ bool IsPipe(int descriptor, short events) {
}
int GuestSockets::Duplicate(int descriptor) {
std::lock_guard lock(socketsMutex);
const auto found = sockets.find(descriptor);
if (found == sockets.end()) return Fail(9);
const int duplicate = CopyNumber(descriptor);
if (duplicate < 0) return -1;
try {
std::lock_guard lock(socketsMutex);
const auto found = sockets.find(descriptor);
if (found == sockets.end()) return Fail(9);
if (nextDescriptor == INT_MAX) return Fail(24);
const int duplicate = nextDescriptor;
sockets.emplace(duplicate, found->second);
++nextDescriptor;
return duplicate;
sockets.insert_or_assign(duplicate, found->second);
} catch (const std::bad_alloc&) {
ReleaseNumber(duplicate);
return Fail(12);
}
return duplicate;
}
int GuestSockets::DuplicateTo(int descriptor, int target) {
try {
std::shared_ptr<Socket> replaced;
{
std::lock_guard lock(socketsMutex);
const auto found = sockets.find(descriptor);
if (found == sockets.end()) return Fail(9);
if (target == descriptor) return target;
if (target == INT_MAX) return Fail(9);
sockets.insert_or_assign(target, found->second);
if (target >= nextDescriptor) nextDescriptor = target + 1;
return target;
} catch (const std::bad_alloc&) {
return Fail(12);
if (CopyNumberTo(descriptor, target) != 0) return -1;
const auto previous = sockets.find(target);
if (previous != sockets.end()) {
replaced = std::move(previous->second);
previous->second = found->second;
} else {
try {
sockets.emplace(target, found->second);
} catch (const std::bad_alloc&) {
ReleaseNumber(target);
return Fail(12);
}
}
}
if (replaced) EqueueDescriptorClosed(target);
return target;
}
namespace {
@ -537,9 +623,54 @@ bool GuestSockets::Ready(int descriptor, bool write, std::int64_t* data, bool* e
return true;
}
int GuestSockets::Poll(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) {
std::vector<std::shared_ptr<Socket>> held;
std::vector<NativePollDescriptor> native;
#ifdef _WIN32
constexpr short NativeReadable = POLLRDNORM;
constexpr short NativeUrgent = POLLRDBAND;
constexpr short NativeWritable = POLLWRNORM;
#else
constexpr short NativeReadable = POLLIN;
constexpr short NativeUrgent = POLLPRI;
constexpr short NativeWritable = POLLOUT;
#endif
for (int index = 0; index < count; ++index) {
auto socket = Find(entries[index].descriptor);
if (!socket) return -9;
NativePollDescriptor request{};
request.fd = socket->value;
if (entries[index].events & KernelSocketPoll::Readable) request.events = static_cast<short>(request.events | NativeReadable);
if (entries[index].events & KernelSocketPoll::Urgent) request.events = static_cast<short>(request.events | NativeUrgent);
if (entries[index].events & KernelSocketPoll::Writable) request.events = static_cast<short>(request.events | NativeWritable);
native.push_back(request);
held.push_back(std::move(socket));
}
if (native.empty()) return 0;
#ifdef _WIN32
const int result = WSAPoll(native.data(), static_cast<ULONG>(native.size()), timeoutMilliseconds);
#else
const int result = ::poll(native.data(), static_cast<nfds_t>(native.size()), timeoutMilliseconds);
#endif
if (result < 0) return -NativeError();
int ready = 0;
for (int index = 0; index < count; ++index) {
const short events = native[static_cast<std::size_t>(index)].revents;
short revents = 0;
if (events & NativeReadable) revents = static_cast<short>(revents | KernelSocketPoll::Readable);
if (events & NativeUrgent) revents = static_cast<short>(revents | KernelSocketPoll::Urgent);
if (events & NativeWritable) revents = static_cast<short>(revents | KernelSocketPoll::Writable);
if (events & (POLLERR | POLLNVAL)) revents = static_cast<short>(revents | KernelSocketPoll::Error);
if (events & POLLHUP) revents = static_cast<short>(revents | KernelSocketPoll::HangUp);
entries[index].revents = revents;
if (revents != 0) ++ready;
}
return ready;
}
extern "C" {
int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) {
if (descriptor < GuestSockets::FirstDescriptor) {
if (!GuestSockets::IsOpen(descriptor)) {
const auto type = File::FcntlArgumentOf(command);
std::intptr_t argument = 0;
if (type != File::FcntlArgument::None) {
@ -589,7 +720,7 @@ int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) {
int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option,
const void* value, std::uint32_t length) {
int netError = 0;
if (descriptor < GuestSockets::FirstDescriptor && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) {
if (!GuestSockets::IsOpen(descriptor) && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) {
return netError == 0 ? 0 : Fail(netError);
}
const auto socket = Lookup(descriptor);
@ -636,11 +767,7 @@ int APS5_VABI socket_nid_postfix(int family, int type, int protocol) {
auto socket = std::make_shared<Socket>(native, family, plainType);
guard.value = Invalid;
if (nonblocking && SetNonBlocking(*socket)) return -1;
std::lock_guard lock(socketsMutex);
if (nextDescriptor == INT_MAX) return Fail(24);
const int descriptor = nextDescriptor++;
sockets.emplace(descriptor, std::move(socket));
return descriptor;
return Install(std::move(socket));
} catch (const std::bad_alloc&) {
return Fail(12);
}
@ -669,16 +796,13 @@ int APS5_VABI socketpair_nid_postfix(int family, int type, int protocol, int* de
auto second = std::make_shared<Socket>(native[1], GuestUnix, plainType);
secondGuard.value = Invalid;
if (nonblocking && (SetNonBlocking(*first) || SetNonBlocking(*second))) return -1;
std::lock_guard lock(socketsMutex);
if (nextDescriptor >= INT_MAX - 1) return Fail(24);
const int firstDescriptor = nextDescriptor++;
const int secondDescriptor = nextDescriptor++;
sockets.emplace(firstDescriptor, std::move(first));
try {
sockets.emplace(secondDescriptor, std::move(second));
} catch (const std::bad_alloc&) {
sockets.erase(firstDescriptor);
return Fail(12);
const int firstDescriptor = Install(std::move(first));
if (firstDescriptor < 0) return -1;
const int secondDescriptor = Install(std::move(second));
if (secondDescriptor < 0) {
const int error = *__error_nid_postfix();
GuestSockets::Close(firstDescriptor);
return Fail(error);
}
descriptors[0] = firstDescriptor;
descriptors[1] = secondDescriptor;
@ -724,10 +848,8 @@ int APS5_VABI accept_nid_postfix(int descriptor, void* address, std::uint32_t* l
inheritNonblocking = listener->nonblocking;
}
if (inheritNonblocking && SetNonBlocking(*accepted)) return -1;
std::lock_guard lock(socketsMutex);
if (nextDescriptor == INT_MAX) return Fail(24);
const int acceptedDescriptor = nextDescriptor++;
sockets.emplace(acceptedDescriptor, std::move(accepted));
const int acceptedDescriptor = Install(std::move(accepted));
if (acceptedDescriptor < 0) return -1;
if (address) GuestAddress(peer, address, length);
return acceptedDescriptor;
} catch (const std::bad_alloc&) {
@ -803,7 +925,7 @@ int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32
return 0;
}
int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) {
if (descriptor < GuestSockets::FirstDescriptor) return File::Ioctl(descriptor, request, argument);
if (!GuestSockets::IsOpen(descriptor)) return File::Ioctl(descriptor, request, argument);
const auto socket = Lookup(descriptor);
if (!socket) return -1;
if (!argument) return Fail(14);
@ -1060,7 +1182,8 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c
if (entry.descriptor < 0) continue;
if ((entry.events & ~GuestPollAccepted) != 0)
throw std::runtime_error("poll: unsupported event flags " + std::to_string(entry.events));
if (entry.descriptor < GuestSockets::FirstDescriptor) {
auto socket = Find(entry.descriptor);
if (!socket) {
if (!DescriptorIsOpen_nid_no_patch(entry.descriptor)) {
entry.revents = GuestPollInvalid;
++ready;
@ -1076,12 +1199,6 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c
pipes.push_back(i);
continue;
}
auto socket = Find(entry.descriptor);
if (!socket) {
entry.revents = GuestPollInvalid;
++ready;
continue;
}
#ifdef _WIN32
if (entry.events & GuestPollPriority) throw std::runtime_error("poll: POLLPRI is not supported by WSAPoll");
#endif

View File

@ -24,7 +24,8 @@ static void Require(bool value) { if (!value) std::abort(); }
constexpr int GuestEbadf = 9;
constexpr int Unix = 1;
constexpr int Stream = 1;
constexpr int FirstSocket = 0x10000000;
constexpr int DescriptorSetSize = 1024;
constexpr int Unopened = 0x1ffffff0;
static void RequireFailure(int result, int error) {
Require(result == -1);
@ -60,7 +61,7 @@ static void DupOutlivesOriginal() {
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
const int duplicate = dup_nid_postfix(pipe[1]);
Require(duplicate >= 0 && duplicate < FirstSocket && duplicate != pipe[1]);
Require(duplicate >= 0 && duplicate != pipe[1]);
Require(close_nid_postfix(pipe[1]) == 0);
RequirePipeCarries(duplicate, pipe[0], "dup");
Require(close_nid_postfix(duplicate) == 0);
@ -97,8 +98,8 @@ static void BadDescriptors() {
RequireFailure(dup2_nid_postfix(closed, closed), GuestEbadf);
RequireFailure(dup2_nid_postfix(pipe[0], -1), GuestEbadf);
RequireFailure(dup2_nid_postfix(-1, pipe[0]), GuestEbadf);
RequireFailure(dup_nid_postfix(FirstSocket + 0x0ffffff0), GuestEbadf);
RequireFailure(dup2_nid_postfix(FirstSocket + 0x0ffffff0, FirstSocket + 0x0ffffff1), GuestEbadf);
RequireFailure(dup_nid_postfix(Unopened), GuestEbadf);
RequireFailure(dup2_nid_postfix(Unopened, Unopened + 1), GuestEbadf);
#ifdef _WIN32
const auto previous = _set_invalid_parameter_handler(RecordInvalidParameter);
#endif
@ -115,25 +116,29 @@ static void SocketDuplicates() {
int pair[2];
Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0);
const int duplicate = dup_nid_postfix(pair[0]);
Require(duplicate >= FirstSocket && duplicate != pair[0] && duplicate != pair[1]);
Require(duplicate >= 0 && duplicate < DescriptorSetSize && duplicate != pair[0] && duplicate != pair[1]);
Require(close_nid_postfix(pair[0]) == 0);
RequireSocketCarries(duplicate, pair[1], "socket");
const int target = duplicate + 0x1000;
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
const int target = pipe[0];
Require(dup2_nid_postfix(duplicate, target) == target);
Require(close_nid_postfix(duplicate) == 0);
RequireSocketCarries(target, pair[1], "moved");
Require(dup2_nid_postfix(target, target) == target);
int next[2];
Require(socketpair_nid_postfix(Unix, Stream, 0, next) == 0);
Require(next[0] > target && next[1] > target);
Require(next[0] < DescriptorSetSize && next[1] < DescriptorSetSize);
RequireSocketCarries(target, pair[1], "kept");
Require(dup2_nid_postfix(next[0], target) == target);
RequireSocketCarries(target, next[1], "over");
Require(Throws([&] { dup2_nid_postfix(target, 1); }));
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(Throws([&] { dup2_nid_postfix(pipe[0], target); }));
for (const int descriptor : {target, pair[1], next[0], next[1], pipe[0], pipe[1]}) Require(close_nid_postfix(descriptor) == 0);
int other[2];
Require(pipe_nid_postfix(other) == 0);
Require(dup2_nid_postfix(other[1], target) == target);
RequirePipeCarries(target, other[0], "file");
Require(send_nid_postfix(target, "x", 1, 0) == -1);
for (const int descriptor : {target, pair[1], next[0], next[1], pipe[1], other[0], other[1]}) Require(close_nid_postfix(descriptor) == 0);
}
static void RandomDeviceDuplicates() {
@ -171,7 +176,7 @@ static void RandomDeviceDuplicates() {
Require(read_nid_postfix(secondRandom, randomBuf4, sizeof(randomBuf4)) == sizeof(randomBuf4));
Require(std::memcmp(randomBuf4, randomBuf3, sizeof(randomBuf4)) != 0);
const int invalidFd = FirstSocket - 100;
const int invalidFd = Unopened - 100;
RequireFailure(dup2_nid_postfix(invalidFd, secondRandom), GuestEbadf);
unsigned char randomBuf5[16] = {};
Require(read_nid_postfix(secondRandom, randomBuf5, sizeof(randomBuf5)) == sizeof(randomBuf5));

View File

@ -57,7 +57,7 @@ static constexpr std::uint32_t StatfsVersion = 0x20030518;
static constexpr int GuestEbadf = 9;
static constexpr int GuestEfault = 14;
static constexpr int GuestEinval = 22;
static constexpr int FirstSocket = 0x10000000;
static constexpr int DescriptorSetSize = 1024;
static void RequireFailure(int descriptor, int error) {
GuestStatfs fs;
@ -116,7 +116,7 @@ int main() {
int sockets[2];
Require(socketpair_nid_postfix(1, 1, 0, sockets) == 0);
Require(sockets[0] >= FirstSocket);
Require(sockets[0] >= 0 && sockets[0] < DescriptorSetSize);
RequireFailure(sockets[0], GuestEinval);
Require(close_nid_postfix(sockets[0]) == 0);
Require(close_nid_postfix(sockets[1]) == 0);

View File

@ -21,6 +21,7 @@ int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI open_nid_postfix(const char*, int, int);
int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int);
int APS5_VABI pipe_nid_postfix(int*);
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
@ -110,7 +111,7 @@ static void TestPipes(int socket) {
}
int main() {
const int listener = socket_nid_postfix(2, 1, 0);
Require(listener >= 0);
Require(listener >= 0 && listener < 1024);
std::array<unsigned char, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(listener, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
@ -145,5 +146,16 @@ int main() {
Require(poll_nid_postfix(&single, 1, -2) == -1 && *__error_nid_postfix() == 22);
TestPipes(listener);
Require(close_nid_postfix(server) == 0);
const int file = open_nid_postfix("/dev/urandom", 0, 0);
Require(file >= 0);
PollDescriptor mixed[2]{{listener, In, 0}, {file, In, 0}};
bool unsupported = false;
try {
poll_nid_postfix(mixed, 2, 0);
} catch (const std::runtime_error&) {
unsupported = true;
}
Require(unsupported);
Require(close_nid_postfix(file) == 0);
Require(close_nid_postfix(listener) == 0);
}

View File

@ -1,5 +1,4 @@
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include <array>
#include <cstdio>
#include <cstdlib>
@ -111,7 +110,7 @@ int main() {
Require(close_nid_postfix(pipe) == 0);
}
const int socket = socket_nid_postfix(2, 2, 0);
Require(socket >= GuestSockets::FirstDescriptor);
Require(socket >= 0 && socket < 1024);
Require(isatty_nid_postfix(socket) == 0 && *__error_nid_postfix() == 25);
Require(close_nid_postfix(socket) == 0);
Require(isatty_nid_postfix(socket) == 0 && *__error_nid_postfix() == 9);

View File

@ -59,6 +59,8 @@ int APS5_VABI sceNetResolverGetError(int, int*);
int APS5_VABI sceNetCtlGetState(int*);
int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*);
int APS5_VABI open_nid_postfix(const char*, int, int);
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t);
int* APS5_VABI __error_nid_postfix();
@ -578,7 +580,7 @@ int main() {
const int udp_receiver = sceNetSocket(nullptr, 2, 2, 17);
const int udp_sender = sceNetSocket(nullptr, 2, 2, 17);
Require(udp_receiver >= 0 && udp_sender >= 0);
Require(udp_receiver >= 0 && udp_sender >= 0 && udp_receiver < 1024 && udp_sender < 1024);
address = {16, 2, 0, 0, 127, 0, 0, 1};
Require(sceNetBind_nid_postfix(udp_receiver, address.data(), address.size()) == 0);
address_size = address.size();
@ -621,6 +623,17 @@ int main() {
Require(((random_readable[udp_receiver / 64] >> (udp_receiver % 64)) & 1u) == 0);
Require(random_urgent[random / 64] == 0);
Require(close_nid_postfix(random) == 0);
const int kernel_socket = socket_nid_postfix(2, 2, 0);
Require(kernel_socket >= 0 && kernel_socket < 1024 && kernel_socket != udp_receiver && kernel_socket != udp_sender);
std::array<std::uint8_t, 16> kernel_address{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(kernel_socket, kernel_address.data(), kernel_address.size()) == 0);
std::uint64_t mixed_writable[16]{};
mixed_writable[kernel_socket / 64] |= std::uint64_t{1} << (kernel_socket % 64);
mixed_writable[udp_sender / 64] |= std::uint64_t{1} << (udp_sender % 64);
Require(select_nid_postfix(std::max(kernel_socket, udp_sender) + 1, nullptr, mixed_writable, nullptr, wait_second) == 2);
Require(((mixed_writable[kernel_socket / 64] >> (kernel_socket % 64)) & 1u) != 0);
Require(((mixed_writable[udp_sender / 64] >> (udp_sender % 64)) & 1u) != 0);
Require(close_nid_postfix(kernel_socket) == 0);
std::uint64_t stdio_readable[16]{1};
std::uint64_t stdio_writable[16]{6};
Require(select_nid_postfix(3, stdio_readable, stdio_writable, nullptr, poll_now) == 2);

View File

@ -25,6 +25,12 @@ extern "C" {
int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*);
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t);
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
int* APS5_VABI __error_nid_postfix();
@ -106,6 +112,44 @@ int main() {
Require(*__error_nid_postfix() == GuestEinval);
}
{
int pipe[2]{-1, -1};
Require(pipe_nid_postfix(pipe) == 0);
const int receiver = socket_nid_postfix(2, 2, 0);
const int sender = socket_nid_postfix(2, 2, 0);
Require(receiver >= 0 && receiver < GuestDescriptorLimit && sender >= 0 && sender < GuestDescriptorLimit);
std::array<unsigned char, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0);
Require(connect_nid_postfix(sender, address.data(), address.size()) == 0);
const int highest = receiver > pipe[0] ? receiver : pipe[0];
GuestDescriptorSet idle{};
idle.Add(receiver);
idle.Add(pipe[0]);
GuestTimeval none{0, 0};
Require(select_nid_postfix(highest + 1, idle.words.data(), nullptr, nullptr, &none) == 0);
Require(!idle.Has(receiver) && !idle.Has(pipe[0]));
const char byte = 'm';
Require(send_nid_postfix(sender, &byte, 1, 0) == 1);
Require(write_nid_postfix(pipe[1], &byte, 1) == 1);
GuestDescriptorSet read{};
GuestTimeval limit{2, 0};
const auto start = std::chrono::steady_clock::now();
int ready = 0;
while (ready < 2 && std::chrono::steady_clock::now() - start < std::chrono::seconds(2)) {
read = {};
read.Add(receiver);
read.Add(pipe[0]);
ready = select_nid_postfix(highest + 1, read.words.data(), nullptr, nullptr, &limit);
}
Require(ready == 2 && read.Has(receiver) && read.Has(pipe[0]));
char buffer[1]{};
Require(recv_nid_postfix(receiver, buffer, sizeof(buffer), 0) == 1 && buffer[0] == byte);
Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 1 && buffer[0] == byte);
for (const int descriptor : {receiver, sender, pipe[0], pipe[1]}) Require(close_nid_postfix(descriptor) == 0);
}
Require(close_nid_postfix(reader) == 0);
Require(close_nid_postfix(writer) == 0);

View File

@ -0,0 +1,189 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <stdexcept>
#include <vector>
struct GuestTimeval {
std::int64_t seconds;
std::int64_t microseconds;
};
struct GuestDescriptorSet {
std::array<std::uint64_t, 1024 / 64> words{};
void Add(int descriptor) {
words[descriptor / 64] |= (std::uint64_t{1} << (descriptor % 64));
}
bool Has(int descriptor) const {
return ((words[descriptor / 64] >> (descriptor % 64)) & 1u) != 0;
}
};
struct PollDescriptor {
int descriptor;
short events;
short revents;
};
extern "C" {
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t);
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI sceKernelClose(int);
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
int APS5_VABI dup_nid_postfix(int);
int APS5_VABI open_nid_postfix(const char*, int, int);
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*);
int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int);
int* APS5_VABI __error_nid_postfix();
}
static void Check(bool value, int line) {
if (!value) {
std::fprintf(stderr, "socket descriptor check failed at line %d, errno %d\n", line, *__error_nid_postfix());
std::abort();
}
}
#define Require(value) Check((value), __LINE__)
constexpr int DescriptorSetSize = 1024;
constexpr int Inet = 2;
constexpr int Datagram = 2;
constexpr short In = 0x1;
constexpr int KernelBadDescriptor = static_cast<int>(0x80020009u);
template <typename TCall> static bool Throws(TCall call) {
try {
call();
} catch (const std::runtime_error&) {
return true;
}
return false;
}
static int Socket() {
const int descriptor = socket_nid_postfix(Inet, Datagram, 0);
Require(descriptor >= 0 && descriptor < DescriptorSetSize);
return descriptor;
}
static int BoundSocket(std::array<unsigned char, 16>& address) {
const int descriptor = Socket();
address = {16, 2, 0, 0, 127, 0, 0, 1};
Require(bind_nid_postfix(descriptor, address.data(), address.size()) == 0);
std::uint32_t size = address.size();
Require(getsockname_nid_postfix(descriptor, address.data(), &size) == 0);
return descriptor;
}
static int ConnectedSocket(const std::array<unsigned char, 16>& address) {
const int descriptor = Socket();
Require(connect_nid_postfix(descriptor, address.data(), address.size()) == 0);
return descriptor;
}
static void WaitReadable(int descriptor) {
GuestDescriptorSet read{};
read.Add(descriptor);
GuestTimeval limit{2, 0};
Require(select_nid_postfix(descriptor + 1, read.words.data(), nullptr, nullptr, &limit) == 1);
Require(read.Has(descriptor));
}
static void SelectSocketAndFile() {
std::array<unsigned char, 16> address{};
const int receiver = BoundSocket(address);
const int sender = ConnectedSocket(address);
const int file = open_nid_postfix("/dev/urandom", 0, 0);
Require(file >= 0 && file < DescriptorSetSize && file != receiver && file != sender);
const int highest = std::max({receiver, sender, file});
{
GuestDescriptorSet read{};
read.Add(receiver);
GuestTimeval none{0, 0};
Require(select_nid_postfix(receiver + 1, read.words.data(), nullptr, nullptr, &none) == 0);
Require(!read.Has(receiver));
}
{
GuestDescriptorSet read{};
read.Add(receiver);
read.Add(file);
GuestTimeval none{0, 0};
Require(select_nid_postfix(highest + 1, read.words.data(), nullptr, nullptr, &none) == 1);
Require(read.Has(file) && !read.Has(receiver));
}
const char byte = 'd';
Require(send_nid_postfix(sender, &byte, 1, 0) == 1);
WaitReadable(receiver);
{
GuestDescriptorSet read{};
GuestDescriptorSet write{};
read.Add(receiver);
read.Add(file);
write.Add(sender);
GuestTimeval none{0, 0};
Require(select_nid_postfix(highest + 1, read.words.data(), write.words.data(), nullptr, &none) == 3);
Require(read.Has(receiver) && read.Has(file) && write.Has(sender));
}
char received = 0;
Require(recv_nid_postfix(receiver, &received, 1, 0) == 1 && received == byte);
for (const int descriptor : {receiver, sender, file}) Require(close_nid_postfix(descriptor) == 0);
}
static void CloseReleasesNumber() {
const int socket = Socket();
Require(close_nid_postfix(socket) == 0);
FileStat status{};
Require(fstat_nid_disambig1_nid_postfix(socket, &status) == -1 && *__error_nid_postfix() == 9);
std::vector<int> files;
while (files.size() < 8 && (files.empty() || files.back() != socket)) {
const int file = open_nid_postfix("/dev/urandom", 0, 0);
Require(file >= 0);
files.push_back(file);
}
Require(files.back() == socket);
unsigned char bytes[4]{};
Require(read_nid_postfix(socket, bytes, sizeof(bytes)) == sizeof(bytes));
PollDescriptor entry{socket, In, 0};
Require(Throws([&] { poll_nid_postfix(&entry, 1, 0); }));
for (const int file : files) Require(close_nid_postfix(file) == 0);
const int kernelClosed = Socket();
Require(sceKernelClose(kernelClosed) == 0);
Require(sceKernelClose(kernelClosed) == KernelBadDescriptor);
entry = {kernelClosed, In, 0};
Require(poll_nid_postfix(&entry, 1, 0) == 1 && entry.revents == 0x20);
}
static void DuplicateKeepsSocket() {
std::array<unsigned char, 16> address{};
const int receiver = BoundSocket(address);
const int sender = ConnectedSocket(address);
const int duplicate = dup_nid_postfix(receiver);
Require(duplicate >= 0 && duplicate < DescriptorSetSize && duplicate != receiver && duplicate != sender);
Require(close_nid_postfix(receiver) == 0);
const char byte = 'u';
Require(send_nid_postfix(sender, &byte, 1, 0) == 1);
WaitReadable(duplicate);
char received = 0;
Require(recv_nid_postfix(duplicate, &received, 1, 0) == 1 && received == byte);
Require(close_nid_postfix(duplicate) == 0);
Require(close_nid_postfix(sender) == 0);
}
int main() {
SelectSocketAndFile();
CloseReleasesNumber();
DuplicateKeepsSocket();
return 0;
}

View File

@ -51,7 +51,7 @@ constexpr int CloseOnExec = 0x10000000;
constexpr int NonBlocking = 0x20000000;
constexpr int NoSignal = 0x20000;
constexpr int Peek = 2;
constexpr int FirstSocket = 0x10000000;
constexpr int DescriptorSetSize = 1024;
constexpr short In = 0x1;
constexpr short Out = 0x4;
constexpr short Hup = 0x10;
@ -90,7 +90,7 @@ static bool Unnamed(int descriptor, int (APS5_VABI *query)(int, void*, std::uint
int main() {
int pair[2] = {-1, -1};
Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0);
Require(pair[0] >= FirstSocket && pair[1] >= FirstSocket && pair[0] != pair[1]);
Require(pair[0] >= 0 && pair[0] < DescriptorSetSize && pair[1] >= 0 && pair[1] < DescriptorSetSize && pair[0] != pair[1]);
Require(Exchange(pair[0], pair[1], "one way"));
Require(Exchange(pair[1], pair[0], "other way"));
Require(Unnamed(pair[0], getsockname_nid_postfix) && Unnamed(pair[1], getpeername_nid_postfix));

View File

@ -53,7 +53,7 @@
- [Map hints inside a reservation](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMapDirectMemory` and the flexible and anonymous maps place a mapping without `MAP_FIXED` at its hint address when the whole range lies in a range still reserved by `sceKernelReserveVirtualRange`, as if `MAP_FIXED` were set. Taken from PPSA01474, which reserves 64 GiB and then maps its heaps inside it with hints and no `MAP_FIXED`, and uses the hint addresses afterwards; not measured on the console. Hints outside a reservation, or over a range that is partly mapped, stay hints
- [_sceKernelRtldThreadAtexitIncrement, _sceKernelRtldThreadAtexitDecrement](../../../core/libs/prx/libkernel/System/src/Rtld.cpp) (libkernel) - the argument is the `dso` address a title's `libc.prx` passes to `__cxa_thread_atexit` (PPSA14632), which fails when Increment returns nonzero. As in the [fpPS4](https://github.com/red-prig/fpPS4) reimplementation, Increment takes a reference on the loaded image containing the address and Decrement releases one; the variants without the leading underscore are assumed to match
- [tcgetattr, tcsetattr](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) - every file and pipe descriptor is taken as not a terminal (`ENOTTY`), and a socket gets `EADDRNOTAVAIL` for `AF_INET` and `EOPNOTSUPP` otherwise, as FreeBSD's `in_control` and `pru_control_notsupp` answer a termios ioctl; what descriptors 0 to 2 of a title are on the console, and whether any of them is a terminal there, is unknown
- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - descriptor 0 is never reported readable and descriptors 1 and 2 are always writable, as for a console title with no input; reads from descriptor 0 still go to the host stdin. `/dev/random` and `/dev/urandom` are always readable and writable, as FreeBSD reports a seeded random device. Other descriptors that are not libSceNet sockets throw. Dungeons (PPSA16064) selects on descriptor 0
- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - descriptor 0 is never reported readable and descriptors 1 and 2 are always writable, as for a console title with no input; reads from descriptor 0 still go to the host stdin. `/dev/random` and `/dev/urandom` are always readable and writable, as FreeBSD reports a seeded random device. Guest sockets and libSceNet sockets are polled; other descriptors throw on Windows and are polled through the host on Linux. Dungeons (PPSA16064) selects on descriptor 0
- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - a descriptor the socket poller reports as unknown that is below `GuestSockets::FirstDescriptor` is polled on the host instead of throwing, except on Windows where it still throws; an invalid host descriptor fails with `EBADF`, and mixed host and socket sets are polled in short alternating slices so neither side waits out the full slice alone. Wobbly Life (PPSA29413) selects on file descriptors while streaming its bundles
- [__progname](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export
- [environ](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - data export pointing to a fixed empty environment so titles that import it load; it is not the environment libc's `getenv` reads, so it lists none of those variables and `setenv`/`putenv` changes do not appear in it
@ -125,7 +125,7 @@
- [Thread cancellation](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) follows FreeBSD libthr (states and types 0/1 and 0/2, `PTHREAD_CANCELED` 1, the `_pthread_cleanup_info` node layout), not measured on the console. A request is acted on only in `scePthreadTestcancel`, the condition variable, `sem` and `scePthreadSem` waits, `scePthreadJoin` and the sleeps, and their POSIX names; file and socket calls are not cancellation points. A sleep is not interrupted: the thread exits when it ends. The asynchronous type acts like the deferred one, except at the two set calls.
- [cpuset_setaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled. Bits outside `sceKernelGetAvailableCpumask` are dropped, and a mask with none of those bits fails with `EDEADLK`, as FreeBSD does when the new set has no CPU in common with the parent set
- [cpuset_getaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled. For the process the mask is the union of the affinities of the live guest threads, as [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L1150-L1156) ORs the mask of every thread in the process; threads the guest did not create through libkernel are not included, and the console result is unverified
- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` between a guest socket and a host descriptor in either direction. Guest sockets share one socket object between the duplicates and the descriptor numbers come from the guest socket counter, not the lowest free number as in FreeBSD `kern_dup`. The errno values for descriptor exhaustion are not verified against the console.
- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` of a guest socket onto descriptors 0 to 2, whose host streams libc writes without the socket table, and on Windows duplicating a directory descriptor. Guest sockets share one socket object between the duplicates; the numbers come from the host descriptor table like those of files. The errno values for descriptor exhaustion are not verified against the console.
- [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`.
- [execv](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported, the same as `execvp`. PPSA97358 imports it and fails to load without it.
- [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified.
@ -137,9 +137,9 @@
- [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`
- [UDP recv/recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) and [sceNetRecv/sceNetRecvfrom](../../../core/libs/prx/libSceNet/Export.cpp) translate Windows WSAEMSGSIZE into the copied byte count for datagram sockets, including MSG_PEEK, and retain the sender address. This follows FreeBSD 11.4 recv(2) and Winsock's documented truncated-datagram behavior; console behavior and existing title impact are unverified.
- [send, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - send and sendto without an address on an unconnected datagram socket map Windows `WSAENOTCONN` to FreeBSD's `EDESTADDRREQ` (39).
- [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, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. Guest socket descriptors start at `0x10000000` and are allocated upward, so the descriptor range selects the guest socket table or the host descriptor table. `EPIPE` from a write raises the host's `SIGPIPE` on Linux, which the guest can neither ignore nor suppress: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; `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 on guest sockets and pipes, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A pipe follows FreeBSD's `pipe_poll`: the read end is readable when data is buffered, either end reports `POLLHUP` once the other end is closed, with the requested `POLLIN` and `POLLRDNORM` bits and without `POLLOUT`, which `pollscan` clears, and the write end is writable when `PIPE_BUF` (512) bytes are free on Windows, or when the host kernel says so on Linux. `POLLOUT` on a read end throws: FreeBSD opens both ends for reading and writing and reports it writable, and host pipes are one-way. Windows pipes cannot be waited on, so while a pipe is polled `poll` checks it with `NtQueryInformationFile` and waits on the sockets in slices of 1 ms. A closed descriptor below the socket range reports `POLLNVAL`, and any other descriptor there (files, 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 or pipe to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked
- [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. A nonblocking `connect` in progress reports `EINPROGRESS` on Linux but `EAGAIN` on Windows (`WSAEWOULDBLOCK`), where FreeBSD reports `EINPROGRESS`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0
- [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, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. `EPIPE` from a write raises the host's `SIGPIPE` on Linux, which the guest can neither ignore nor suppress: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; `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
- [Socket descriptor numbers](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel, libSceNet) - `socket`, `accept`, `socketpair`, `dup`, `sceNetSocket` and `sceNetAccept` take the lowest free number of the host descriptor table that files use, as FreeBSD 11.4 `fdalloc` takes the lowest free entry of the one process table (`kern_descrip.c`), so every number stays below `FD_SETSIZE` (1024) while fewer than 1024 descriptors are open. The number holds a zero-access `NUL` handle on Windows and a duplicate of the host socket on Linux, and the socket tables map it to the socket. The host table also holds the runtime's own files, so the numbers differ from the console's; the first number `socket` and `sceNetSocket` return on a console and its `getdtablesize` are not measured. Paths that bypass the socket tables see the placeholder: libc `fdopen` on a socket opens it, libkernel `fstat` on a socket throws, and libkernel `close` of a libSceNet socket releases the number but leaves the libSceNet socket open. Palworld (PPSA23126), The Mound (PPSA26223) and Dying Light 2 (PPSA02262) set socket bits in a 1024-bit `fd_set`
- [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits on guest sockets and pipes, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. An open descriptor that is neither a guest socket nor a pipe (files, stdio, libSceNet sockets) 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- [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. A nonblocking `connect` in progress reports `EINPROGRESS` on Linux but `EAGAIN` on Windows (`WSAEWOULDBLOCK`), where FreeBSD reports `EINPROGRESS`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0
- [connect to AF_UNSPEC](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:<port>` stays `127.0.0.1:<port>`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had
- [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
- [sigtimedwait](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) waits on a process-wide pending set instead of throwing: a signal raised while blocked stays pending and is taken by `sigwait`, `sigwaitinfo` or `sigtimedwait`, delivered to the handler when `_sigprocmask` unblocks it, or delivered by `pause`. The set covers all 128 signals and the lowest number that is pending and in the set is selected (FreeBSD specifies the lowest number only among real-time signals). Selection, `EAGAIN` on timeout, `EINVAL` for a `tv_nsec` outside 0 to 1000000000 while nothing is pending and `NULL` timeout blocking indefinitely follow FreeBSD's [sigtimedwait(2)](https://man.freebsd.org/cgi/man.cgi?query=sigtimedwait). `sigwait` returns 0 with the number in its output, as POSIX specifies, and `sigwaitinfo` and `sigtimedwait` return the number, as FreeBSD does. The guest `siginfo_t` follows FreeBSD 11's `sys/sys/siginfo.h`: 80 bytes, `si_signo` at 0, `si_code` at 8, `si_pid` at 12, `si_uid` at 16, `si_addr` at 24, `si_value` at 32 and `si_band` at 40; only `si_signo`, `si_code` (`SI_USER`) and `si_pid` (the guest pid) are written, `si_uid` stays 0 because no uid is modelled, and the console's layout is not verified. PPSA85300 imports `sigtimedwait` and fails to load without it; its only call site is VLC's `vlc_writev` ([filesystem.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/posix/filesystem.c#L243-L278)), which polls with a zero timeout to drain a `SIGPIPE` only after `writev` fails with `EPIPE`. The waits are not cancellation points and an unblocked caught signal does not interrupt them with `EINTR`, where FreeBSD's `sigwaitinfo` and `sigtimedwait` may return it. `SIGPIPE` has no host equivalent and is never raised by a host write here (see `read, write and vector I/O`), so VLC's drain finds nothing