Merge pull request #3555 from lisn0/feat/linux-named-shared-memory

feat(libkernel): support named shared memory on Linux
This commit is contained in:
Adrià 2026-10-11 23:55:30 +02:00 committed by GitHub
commit 72e67c0e50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1626 additions and 35 deletions

View File

@ -1,6 +1,7 @@
target_sources(libc PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/General.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/CreationMask.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/gcc/StackGuard.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/windows/CrashReport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/linux/DlIteratePhdr.cpp

View File

@ -0,0 +1,13 @@
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_CREATIONMASK_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_CREATIONMASK_HPP
#include <cstdint>
namespace File {
extern "C" std::uint16_t ExchangeCreationMask_nid_no_patch(std::uint16_t mask);
extern "C" std::uint16_t GetCreationMask_nid_no_patch();
}
#endif

View File

@ -0,0 +1,21 @@
#include "prx/libc/include/CreationMask.hpp"
#include <atomic>
namespace {
std::atomic<std::uint16_t> creationMask{022};
}
namespace File {
std::uint16_t ExchangeCreationMask_nid_no_patch(std::uint16_t mask) {
return creationMask.exchange(static_cast<std::uint16_t>(mask & 07777), std::memory_order_relaxed);
}
std::uint16_t GetCreationMask_nid_no_patch() {
return creationMask.load(std::memory_order_relaxed);
}
}

View File

@ -11,6 +11,7 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/SharedMemory.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#ifndef _WIN32
@ -80,7 +81,8 @@ std::int64_t NativePwrite(std::int32_t fd, const void* buf, std::size_t nbyte, s
}
return NativePositioned_nid_no_patch(fd, const_cast<void*>(buf), nbyte, offset, true);
#else
return static_cast<std::int64_t>(::pwrite(fd, buf, nbyte, static_cast<off_t>(offset)));
const auto result = File::PwriteSharedMemory_nid_no_patch(fd, buf, nbyte, offset);
return result == -2 ? static_cast<std::int64_t>(::pwrite(fd, buf, nbyte, static_cast<off_t>(offset))) : result;
#endif
}

View File

@ -765,6 +765,38 @@ if(BUILD_TESTING)
target_link_libraries(guest_shm_open_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_shm_open_tests)
add_test(NAME guest_shm_open COMMAND guest_shm_open_tests)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_test_executable(guest_named_shm_open_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmOpen.cpp)
target_include_directories(guest_named_shm_open_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(guest_named_shm_open_tests PRIVATE libkernel libc rt)
add_test(NAME guest_named_shm_open COMMAND guest_named_shm_open_tests)
set_tests_properties(guest_named_shm_open PROPERTIES TIMEOUT 20)
add_test_executable(guest_named_shm_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmLifecycle.cpp)
target_include_directories(guest_named_shm_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(guest_named_shm_lifecycle_tests PRIVATE libkernel libc rt ${CMAKE_DL_LIBS})
add_test(NAME guest_named_shm_lifecycle COMMAND guest_named_shm_lifecycle_tests)
set_tests_properties(guest_named_shm_lifecycle PROPERTIES TIMEOUT 20)
add_test_executable(guest_named_shm_descriptor_reuse_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmDescriptorReuse.cpp)
target_include_directories(guest_named_shm_descriptor_reuse_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(guest_named_shm_descriptor_reuse_tests PRIVATE libkernel libc rt ${CMAKE_DL_LIBS})
add_test(NAME guest_named_shm_descriptor_reuse COMMAND guest_named_shm_descriptor_reuse_tests)
set_tests_properties(guest_named_shm_descriptor_reuse PROPERTIES TIMEOUT 20)
add_test_executable(guest_named_shm_permissions_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmPermissions.cpp)
target_include_directories(guest_named_shm_permissions_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(guest_named_shm_permissions_tests PRIVATE libkernel_sys libkernel libc rt)
add_test(NAME guest_named_shm_permissions COMMAND guest_named_shm_permissions_tests)
set_tests_properties(guest_named_shm_permissions PROPERTIES TIMEOUT 20)
add_test_executable(guest_named_shm_io_descriptor_reuse_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmIoDescriptorReuse.cpp)
target_include_directories(guest_named_shm_io_descriptor_reuse_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(guest_named_shm_io_descriptor_reuse_tests PRIVATE libkernel libc rt)
add_test(NAME guest_named_shm_io_descriptor_reuse COMMAND guest_named_shm_io_descriptor_reuse_tests)
set_tests_properties(guest_named_shm_io_descriptor_reuse PROPERTIES TIMEOUT 20)
add_test_executable(guest_named_shm_concurrency_tests ${LIBS_INCLUDE_DIR}/tests/GuestNamedShmConcurrency.cpp)
target_include_directories(guest_named_shm_concurrency_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(guest_named_shm_concurrency_tests PRIVATE libkernel libc rt ${CMAKE_DL_LIBS})
add_test(NAME guest_named_shm_concurrency COMMAND guest_named_shm_concurrency_tests)
set_tests_properties(guest_named_shm_concurrency PROPERTIES TIMEOUT 20)
endif()
if(WIN32)
set_tests_properties(guest_shm_open PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")

View File

@ -830,6 +830,45 @@ int DoMapAnon(void** addr, size_t len, int prot, int flags, size_t alignment) {
return 0;
}
int DoMapFileBacked_nid_no_patch(void** addr, size_t len, int prot, int flags, int descriptor, std::uint64_t offset, size_t alignment) {
ValidateOutput(addr);
if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0 || (offset & (PS5_PAGE_SIZE - 1)) != 0 || len > static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max()) || offset > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) - len) return SCE_KERNEL_ERROR_EINVAL;
#if defined(__linux__)
const bool shared = (flags & 3) == 1;
GuestAllocations::Mutation mutation;
const int placementFlags = flags & (GuestMapFixedFlag | 0x80 | 0x400000);
flags = PlaceHintInReservation(*addr, len, placementFlags);
if (FixedNoOverwriteConflict(mutation, *addr, len, flags)) return SCE_KERNEL_ERROR_ENOMEM;
ReplaceFixedOverlap(mutation, *addr, len, flags);
void* mapped = MapPlaced(*addr, len, PROT_NONE, flags, alignment);
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(mapped, len);
const int nativeProtection = LinuxProtFromSce(prot);
const int nativeFlags = (shared ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
if (::mmap(mapped, len, nativeProtection, nativeFlags, descriptor, static_cast<off_t>(offset)) == MAP_FAILED) {
const int error = errno;
Unmap(mapped, len);
throw std::system_error(error, std::generic_category(), "map guest shared memory");
}
try {
mutation.Add(mapped, len, (prot & 3) != 0, (prot & 2) != 0, false);
} catch (...) {
Unmap(mapped, len);
throw;
}
*addr = mapped;
EraseReservations(mapped, len);
RecordProtection(mapped, len, prot);
Trace("map file %p+0x%zx offset=0x%llx prot=0x%x flags=0x%x", mapped, len, static_cast<unsigned long long>(offset), prot, flags);
return 0;
#else
static_cast<void>(prot);
static_cast<void>(flags);
static_cast<void>(descriptor);
static_cast<void>(offset);
throw std::system_error(ENOTSUP, std::generic_category(), "file-backed guest mmap is not supported on this host");
#endif
}
#ifdef _WIN32
static bool IsGuestModuleImage(const void* base) {
std::wstring path(MAX_PATH, L'\0');

View File

@ -30,6 +30,7 @@ bool DirectMemoryLargestFree(int64_t searchStart, int64_t searchEnd, size_t alig
size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit);
int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, size_t alignment);
int DoMapAnon(void** addr, size_t len, int prot, int flags, size_t alignment = PS5_PAGE_SIZE);
int DoMapFileBacked_nid_no_patch(void** addr, size_t len, int prot, int flags, int descriptor, std::uint64_t offset, size_t alignment);
int DoMprotect(const void* addr, size_t len, int prot);
int DoMtypeprotect(const void* addr, size_t len, int type, int prot);
int DoMunmap(void* addr, size_t len);

View File

@ -2,9 +2,16 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/File/include/SharedMemory.hpp"
#include <limits>
#include <cerrno>
#include <new>
#include <stdexcept>
#include <system_error>
#if defined(__linux__)
#include <fcntl.h>
#include <unistd.h>
#endif
extern "C" int* APS5_VABI __error_nid_postfix();
extern "C" int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length);
@ -15,9 +22,16 @@ namespace {
// FreeBSD/PS5 ABI values, independent of the host's errno and mmap constants.
constexpr int GuestInvalid = 22;
constexpr int GuestNoMemory = 12;
constexpr int GuestBadDescriptor = 9;
constexpr int GuestAccess = 13;
constexpr int GuestIoError = 5;
constexpr int GuestNotSupported = 45;
constexpr int GuestShared = 0x1;
constexpr int GuestPrivate = 0x2;
constexpr int GuestAnonymous = 0x1000;
constexpr int GuestMapFixed = 0x10;
constexpr int GuestMapNoOverwrite = 0x80;
constexpr int GuestMapNoCoalesce = 0x400000;
constexpr int GuestSyncAsync = 0x1;
constexpr int GuestSyncInvalidate = 0x2;
constexpr int GuestAdviceCore = 9;
@ -26,6 +40,25 @@ constexpr std::uintptr_t GuestUserAddressEnd = 0x800000000000;
constexpr int GuestAlignedSuper = 0x1000000;
constexpr std::size_t SuperpageSize = std::size_t{1} << 21;
#if defined(__linux__)
class ScopedDescriptor {
public:
explicit ScopedDescriptor(int value) noexcept : descriptor(fcntl(value, F_DUPFD_CLOEXEC, 0)), error(descriptor < 0 ? errno : 0) {}
~ScopedDescriptor() {
const int savedError = errno;
if (descriptor >= 0) close(descriptor);
errno = savedError;
}
ScopedDescriptor(const ScopedDescriptor&) = delete;
ScopedDescriptor& operator=(const ScopedDescriptor&) = delete;
int Get() const noexcept { return descriptor; }
int Error() const noexcept { return error; }
private:
int descriptor = -1;
int error = 0;
};
#endif
bool RoundLength(std::size_t length, std::size_t& rounded) {
constexpr auto mask = PS5_PAGE_SIZE - 1;
if (length == 0 || length > std::numeric_limits<std::size_t>::max() - mask)
@ -55,17 +88,60 @@ void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protecti
std::size_t rounded;
if (!RoundLength(length, rounded) || (protection & ~7) != 0)
return failed(GuestInvalid);
if ((flags & ~GuestAlignedSuper) != (GuestPrivate | GuestAnonymous))
const int mappingFlags = flags & ~GuestAlignedSuper;
if (mappingFlags == (GuestPrivate | GuestAnonymous)) {
if (descriptor != -1 || offset != 0) return failed(GuestInvalid);
void* mapped = nullptr;
try {
const auto result = DoMapAnon(&mapped, rounded, protection, 0, (flags & GuestAlignedSuper) != 0 ? SuperpageSize : PS5_PAGE_SIZE);
if (result != 0) return failed(GuestNoMemory);
return mapped;
} catch (const std::bad_alloc&) {
return failed(GuestNoMemory);
} catch (const std::exception&) {
return failed(GuestNoMemory);
}
}
const int kind = mappingFlags & (GuestShared | GuestPrivate);
int placement = mappingFlags & (GuestMapFixed | GuestMapNoOverwrite | GuestMapNoCoalesce);
if ((mappingFlags & GuestAnonymous) != 0 ||
(kind != GuestShared && kind != GuestPrivate) ||
(mappingFlags & ~(GuestShared | GuestPrivate | GuestMapFixed | GuestMapNoOverwrite | GuestMapNoCoalesce)) != 0)
return failed(GuestNotSupported);
if (descriptor != -1 || offset != 0)
if (descriptor < 0) return failed(GuestBadDescriptor);
if (offset < 0 || (static_cast<std::uint64_t>(offset) & (PS5_PAGE_SIZE - 1)) != 0 ||
rounded > static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max()) ||
static_cast<std::uint64_t>(offset) > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) - rounded)
return failed(GuestInvalid);
// A non-fixed address is a hint; the kernel may choose another address.
(void)address;
#if defined(__linux__)
const ScopedDescriptor pinnedDescriptor(descriptor);
if (pinnedDescriptor.Get() < 0) return failed(pinnedDescriptor.Error());
const int mappingDescriptor = pinnedDescriptor.Get();
const int descriptorFlags = fcntl(mappingDescriptor, F_GETFL);
if (descriptorFlags < 0) return failed(errno);
const int accessMode = descriptorFlags & O_ACCMODE;
if ((protection & 1) != 0 && accessMode == O_WRONLY) return failed(GuestAccess);
if (kind == GuestShared && (protection & 2) != 0 && accessMode == O_RDONLY) return failed(GuestAccess);
if (!File::IsSharedMemoryDescriptor_nid_no_patch(mappingDescriptor)) return failed(GuestNotSupported);
#else
const int mappingDescriptor = descriptor;
if (!File::IsSharedMemoryDescriptor_nid_no_patch(mappingDescriptor)) return failed(GuestNotSupported);
#endif
const std::size_t alignment = (flags & GuestAlignedSuper) != 0 ? SuperpageSize : PS5_PAGE_SIZE;
void* mapped = nullptr;
try {
const auto result = DoMapAnon(&mapped, rounded, protection, 0, (flags & GuestAlignedSuper) != 0 ? SuperpageSize : PS5_PAGE_SIZE);
if (result != 0) return failed(GuestNoMemory);
mapped = address;
const auto result = DoMapFileBacked_nid_no_patch(&mapped, rounded, protection, kind | placement, mappingDescriptor, static_cast<std::uint64_t>(offset), alignment);
if (result != 0) return failed(result == SCE_KERNEL_ERROR_EINVAL ? GuestInvalid : GuestNoMemory);
return mapped;
} catch (const std::invalid_argument&) {
return failed(GuestInvalid);
} catch (const std::system_error& error) {
const int hostError = error.code().value();
if (hostError == ENOTSUP || hostError == EOPNOTSUPP) return failed(GuestNotSupported);
if (hostError == EACCES || hostError == EPERM) return failed(GuestAccess);
if (hostError == EBADF) return failed(GuestBadDescriptor);
return failed(hostError > 0 && hostError <= 34 ? hostError : GuestIoError);
} catch (const std::bad_alloc&) {
return failed(GuestNoMemory);
} catch (const std::exception&) {
@ -109,6 +185,9 @@ int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protec
return 0;
} catch (const std::bad_alloc&) {
return failed(GuestNoMemory);
} catch (const std::system_error& error) {
const int hostError = error.code().value();
return failed(hostError == EACCES || hostError == EPERM ? GuestAccess : GuestInvalid);
} catch (const std::exception&) {
return failed(GuestInvalid);
}

View File

@ -0,0 +1,24 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_FILE_SHAREDMEMORY_HPP
#define CORE_LIBS_PRX_LIBKERNEL_FILE_SHAREDMEMORY_HPP
#include <cstddef>
#include <cstdint>
#ifndef _WIN32
#include <sys/uio.h>
#endif
namespace File {
bool IsSharedMemoryDescriptor_nid_no_patch(int descriptor);
std::int64_t WriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::size_t size);
std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::size_t size, std::int64_t offset);
std::int64_t SeekSharedMemory_nid_no_patch(int descriptor, std::int64_t offset, int whence);
int TruncateSharedMemory_nid_no_patch(int descriptor, std::int64_t length);
#ifndef _WIN32
std::int64_t WritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count);
std::int64_t PwritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count, std::int64_t offset);
#endif
}
#endif

View File

@ -4,6 +4,7 @@
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/SharedMemory.hpp"
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include "prx/libkernel/File/include/FileLock.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
@ -72,7 +73,8 @@ static int NativeOpen(const std::filesystem::path& p, int nativeFlags, std::uint
return descriptor;
}
static std::int64_t NativeLseek(int fd, std::int64_t offset, int whence) {
return ::_lseeki64(fd, offset, whence);
const auto result = File::SeekSharedMemory_nid_no_patch(fd, offset, whence);
return result == -2 ? ::_lseeki64(fd, offset, whence) : result;
}
static int NativeRead(int fd, void* buf, std::size_t n) {
if (n > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
@ -84,7 +86,8 @@ static int NativeWrite(int fd, const void* buf, std::size_t n) {
if (n > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
throw std::runtime_error("sceKernelWrite: nbytes exceeds platform limit");
}
return ::_write(fd, buf, static_cast<unsigned int>(n));
const auto result = File::WriteSharedMemory_nid_no_patch(fd, buf, n);
return result == -2 ? ::_write(fd, buf, static_cast<unsigned int>(n)) : static_cast<int>(result);
}
extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler);
static void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {}
@ -126,13 +129,15 @@ static int NativeOpen(const std::filesystem::path& p, int nativeFlags, std::uint
return ::open(p.c_str(), nativeFlags, static_cast<mode_t>(mode));
}
static std::int64_t NativeLseek(int fd, std::int64_t offset, int whence) {
return ::lseek(fd, static_cast<off_t>(offset), whence);
const auto result = File::SeekSharedMemory_nid_no_patch(fd, offset, whence);
return result == -2 ? ::lseek(fd, static_cast<off_t>(offset), whence) : result;
}
static std::int64_t NativeRead(int fd, void* buf, std::size_t n) {
return ::read(fd, buf, n);
}
static std::int64_t NativeWrite(int fd, const void* buf, std::size_t n) {
return ::write(fd, buf, n);
const auto result = File::WriteSharedMemory_nid_no_patch(fd, buf, n);
return result == -2 ? ::write(fd, buf, n) : result;
}
static int NativeClose(int fd) { return ::close(fd); }
static int NativeUnlink(const std::filesystem::path& p) {

View File

@ -1,26 +1,85 @@
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include "prx/libkernel/File/include/SharedMemory.hpp"
#include "prx/libc/include/CreationMask.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include <cerrno>
#include <algorithm>
#include <string_view>
#include <vector>
#include <stdexcept>
#include <string>
#include <cstdint>
#include <limits>
#include <optional>
#include <atomic>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#include <share.h>
#include <sys/stat.h>
#include <atomic>
#include <filesystem>
#include <random>
#else
#include "nid/Sha1.hpp"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <unistd.h>
#include <cstdio>
#endif
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
bool IsHostSharedMemoryDescriptor(int descriptor) {
#ifdef __linux__
char descriptorPath[64];
std::snprintf(descriptorPath, sizeof(descriptorPath), "/proc/self/fd/%d", descriptor);
char objectPath[4096];
const auto length = ::readlink(descriptorPath, objectPath, sizeof(objectPath) - 1);
if (length < 0) return false;
objectPath[length] = '\0';
return std::string_view(objectPath).starts_with("/dev/shm/anyps5-shm-") ||
std::string_view(objectPath).starts_with("/memfd:anyps5 shm_open");
#else
(void)descriptor;
return false;
#endif
}
class HostShmLock {
public:
HostShmLock() {
#ifdef __linux__
descriptor = ::open("/dev/shm", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (descriptor < 0 || ::flock(descriptor, LOCK_EX) != 0) error = errno;
#endif
}
~HostShmLock() {
#ifdef __linux__
const int savedError = errno;
if (descriptor >= 0) {
if (!error) ::flock(descriptor, LOCK_UN);
::close(descriptor);
}
errno = savedError;
#endif
}
int Error() const { return error; }
private:
int descriptor = -1;
int error = 0;
};
constexpr int GuestNoMemory = 12;
constexpr int GuestInvalid = 22;
constexpr int GuestTooManyFilesInSystem = 23;
@ -48,6 +107,88 @@ int GuestError(int error) {
}
}
#ifndef _WIN32
int NamedGuestError(int error) {
switch (error) {
case ENOENT:
return 2;
case EINTR:
return 4;
case EACCES:
return 13;
case EFAULT:
return 14;
case EEXIST:
return 17;
case EINVAL:
return GuestInvalid;
case ENOSPC:
return 28;
case EROFS:
return 30;
case ENAMETOOLONG:
return 63;
default:
return GuestError(error);
}
}
std::string NamedHostPath(const char* path, int& error) {
if (path == nullptr) { error = EFAULT; return {}; }
std::size_t length = 0;
while (length < 1024 && path[length] != '\0') ++length;
if (length == 1024) { error = ENAMETOOLONG; return {}; }
if (length == 0 || path[0] != '/') { error = EINVAL; return {}; }
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(path, path + length));
std::string hostName = "/anyps5-shm-";
for (const auto byte : digest) {
hostName += "0123456789abcdef"[byte >> 4];
hostName += "0123456789abcdef"[byte & 15];
}
error = 0;
return hostName;
}
int OpenNamedFile(const char* path, int flags, int mode) {
int validationError = 0;
const auto hostName = NamedHostPath(path, validationError);
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
HostShmLock namespaceLock;
if (namespaceLock.Error() != 0) {
errno = namespaceLock.Error();
if (const int guest = NamedGuestError(errno)) return Fail(guest);
throw std::runtime_error("shm_open: locking the host shared-memory namespace failed, errno=" + std::to_string(errno));
}
int hostFlags = (flags & SCE_KERNEL_O_ACCMODE) == SCE_KERNEL_O_RDWR ? O_RDWR : O_RDONLY;
if (flags & SCE_KERNEL_O_CREAT) hostFlags |= O_CREAT;
if (flags & SCE_KERNEL_O_EXCL) hostFlags |= O_EXCL;
if ((flags & SCE_KERNEL_O_TRUNC) && (flags & SCE_KERNEL_O_ACCMODE) == SCE_KERNEL_O_RDWR) hostFlags |= O_TRUNC;
const auto permissions = (mode & 0777) & ~File::GetCreationMask_nid_no_patch();
bool created = false;
int descriptor;
if (flags & SCE_KERNEL_O_CREAT) {
descriptor = ::shm_open(hostName.c_str(), hostFlags | O_CLOEXEC | O_EXCL, permissions);
created = descriptor >= 0;
if (descriptor < 0 && errno == EEXIST && !(flags & SCE_KERNEL_O_EXCL)) {
descriptor = ::shm_open(hostName.c_str(), (hostFlags & ~(O_CREAT | O_EXCL)) | O_CLOEXEC, 0);
}
} else {
descriptor = ::shm_open(hostName.c_str(), hostFlags | O_CLOEXEC, 0);
}
if (descriptor >= 0) {
if (!created || ::fchmod(descriptor, permissions) == 0) return descriptor;
const int error = errno;
::close(descriptor);
::shm_unlink(hostName.c_str());
if (const int guest = NamedGuestError(error)) return Fail(guest);
throw std::runtime_error("shm_open: setting guest creation permissions failed, errno=" + std::to_string(error));
}
const int error = errno;
if (const int guest = NamedGuestError(error)) return Fail(guest);
throw std::runtime_error("shm_open: opening the host named object failed, errno=" + std::to_string(error));
}
#endif
int CreateAnonymousFile() {
#ifdef _WIN32
static std::atomic<unsigned> sequence{0};
@ -68,17 +209,258 @@ int CreateAnonymousFile() {
errno = EEXIST;
return -1;
#else
return memfd_create("anyps5 shm_open", MFD_CLOEXEC);
const int descriptor = memfd_create("anyps5 shm_open", MFD_CLOEXEC);
return descriptor;
#endif
}
}
extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int) {
namespace File {
bool IsSharedMemoryDescriptor_nid_no_patch(int descriptor) {
return IsHostSharedMemoryDescriptor(descriptor);
}
namespace {
class SharedMemoryLock {
public:
explicit SharedMemoryLock(int value) {
#ifdef __linux__
struct stat originalStatus{};
if (::fstat(value, &originalStatus) != 0) { shared = true; error = errno; return; }
#endif
if (!IsHostSharedMemoryDescriptor(value)) {
#ifdef __linux__
struct stat currentStatus{};
if (::fstat(value, &currentStatus) != 0) { shared = true; error = errno; return; }
if (currentStatus.st_dev != originalStatus.st_dev || currentStatus.st_ino != originalStatus.st_ino) {
shared = true;
error = EBADF;
}
#endif
return;
}
shared = true;
hostLock.emplace();
if (hostLock->Error() != 0) { error = hostLock->Error(); return; }
descriptor = Duplicate(value);
if (descriptor < 0) { error = errno; return; }
if (!IsHostSharedMemoryDescriptor(descriptor)) { error = EBADF; return; }
if (!Refresh()) { error = errno; return; }
#ifdef __linux__
if (status.st_dev != originalStatus.st_dev || status.st_ino != originalStatus.st_ino) error = EBADF;
#endif
}
~SharedMemoryLock() {
if (descriptor >= 0) {
const int savedError = errno;
#ifdef _WIN32
::_close(descriptor);
#else
::close(descriptor);
#endif
errno = savedError;
}
}
bool IsShared() const { return shared; }
int Error() const { return error; }
int Descriptor() const { return descriptor; }
bool Refresh() {
#ifdef _WIN32
struct _stat64 nativeStatus{};
if (::_fstat64(descriptor, &nativeStatus) != 0) return false;
status.st_size = nativeStatus.st_size;
return true;
#else
return ::fstat(descriptor, &status) == 0;
#endif
}
struct stat status{};
private:
static int Duplicate(int value) {
#ifdef _WIN32
return ::_dup(value);
#else
return ::fcntl(value, F_DUPFD_CLOEXEC, 0);
#endif
}
int descriptor = -1;
std::optional<HostShmLock> hostLock;
bool shared = false;
int error = 0;
};
}
std::int64_t WriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::size_t size) {
SharedMemoryLock guard(descriptor);
if (!guard.IsShared()) return -2;
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
if (!guard.Refresh()) return -1;
const auto fd = guard.Descriptor();
#ifndef _WIN32
const int access = ::fcntl(fd, F_GETFL);
if (access < 0) return -1;
if ((access & O_ACCMODE) == O_RDONLY) { errno = EBADF; return -1; }
#endif
const auto& status = guard.status;
#ifdef _WIN32
const auto position = ::_lseeki64(fd, 0, SEEK_CUR);
#else
const auto position = ::lseek(fd, 0, SEEK_CUR);
#endif
if (position < 0) return -1;
if (position >= status.st_size) return 0;
const auto count = std::min<std::uint64_t>(size, static_cast<std::uint64_t>(status.st_size - position));
#ifdef _WIN32
const auto result = ::_write(fd, buffer, static_cast<unsigned int>(std::min<std::uint64_t>(count, std::numeric_limits<unsigned int>::max())));
#else
const auto result = ::write(fd, buffer, static_cast<std::size_t>(count));
#endif
return static_cast<std::int64_t>(result);
}
std::int64_t PwriteSharedMemory_nid_no_patch(int descriptor, const void* buffer, std::size_t size, std::int64_t offset) {
SharedMemoryLock guard(descriptor);
if (!guard.IsShared()) return -2;
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
if (!guard.Refresh()) return -1;
const auto fd = guard.Descriptor();
#ifndef _WIN32
const int access = ::fcntl(fd, F_GETFL);
if (access < 0) return -1;
if ((access & O_ACCMODE) == O_RDONLY) { errno = EBADF; return -1; }
#endif
const auto& status = guard.status;
if (offset < 0) { errno = EINVAL; return -1; }
if (offset >= status.st_size) return 0;
const auto count = std::min<std::uint64_t>(size, static_cast<std::uint64_t>(status.st_size - offset));
#ifdef _WIN32
const auto result = NativePositioned_nid_no_patch(fd, const_cast<void*>(buffer), static_cast<std::size_t>(count), offset, true);
#else
const auto result = ::pwrite(fd, buffer, static_cast<std::size_t>(count), static_cast<off_t>(offset));
#endif
return static_cast<std::int64_t>(result);
}
std::int64_t SeekSharedMemory_nid_no_patch(int descriptor, std::int64_t offset, int whence) {
SharedMemoryLock guard(descriptor);
if (!guard.IsShared()) return -2;
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
if (!guard.Refresh()) return -1;
const auto fd = guard.Descriptor();
const auto& status = guard.status;
std::int64_t base = 0;
if (whence == SEEK_CUR) {
#ifdef _WIN32
base = ::_lseeki64(fd, 0, SEEK_CUR);
#else
base = ::lseek(fd, 0, SEEK_CUR);
#endif
if (base < 0) return -1;
} else if (whence == SEEK_END) {
base = static_cast<std::int64_t>(status.st_size);
} else if (whence != SEEK_SET) {
errno = EINVAL;
return -1;
}
if ((offset > 0 && base > std::numeric_limits<std::int64_t>::max() - offset) ||
(offset < 0 && base < std::numeric_limits<std::int64_t>::min() - offset)) {
errno = EOVERFLOW;
return -1;
}
const auto target = base + offset;
if (target < 0 || target > status.st_size) {
errno = EINVAL;
return -1;
}
#ifdef _WIN32
return ::_lseeki64(fd, target, SEEK_SET);
#else
return ::lseek(fd, static_cast<off_t>(target), SEEK_SET);
#endif
}
#ifndef _WIN32
static std::int64_t TransferSharedMemoryVectors(int descriptor, const struct iovec* vectors, int count,
std::int64_t offset, bool positioned) {
SharedMemoryLock guard(descriptor);
if (!guard.IsShared()) return -2;
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
const auto fd = guard.Descriptor();
const int access = ::fcntl(fd, F_GETFL);
if (access < 0) return -1;
if ((access & O_ACCMODE) == O_RDONLY) { errno = EBADF; return -1; }
if (positioned && offset < 0) { errno = EINVAL; return -1; }
struct stat status{};
if (::fstat(fd, &status) != 0) return -1;
std::uint64_t total = 0;
constexpr auto maxTransfer = static_cast<std::uint64_t>(std::numeric_limits<ssize_t>::max());
for (int i = 0; i < count; ++i) {
if (vectors[i].iov_len > maxTransfer - total) { errno = EINVAL; return -1; }
total += vectors[i].iov_len;
}
std::uint64_t available = 0;
if (positioned) {
if (offset < status.st_size) available = static_cast<std::uint64_t>(status.st_size - offset);
if (total > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max() - offset)) { errno = EINVAL; return -1; }
} else {
const auto position = ::lseek(fd, 0, SEEK_CUR);
if (position < 0) return -1;
if (position < status.st_size) available = static_cast<std::uint64_t>(status.st_size - position);
}
std::vector<struct iovec> bounded;
bounded.reserve(static_cast<std::size_t>(count));
for (int i = 0; i < count && available > 0; ++i) {
const auto length = static_cast<std::size_t>(std::min<std::uint64_t>(vectors[i].iov_len, available));
if (length != 0) bounded.push_back({vectors[i].iov_base, length});
available -= length;
}
if (bounded.empty()) return 0;
return positioned ? ::pwritev(fd, bounded.data(), static_cast<int>(bounded.size()), static_cast<off_t>(offset))
: ::writev(fd, bounded.data(), static_cast<int>(bounded.size()));
}
std::int64_t WritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count) {
return TransferSharedMemoryVectors(descriptor, vectors, count, 0, false);
}
std::int64_t PwritevSharedMemory_nid_no_patch(int descriptor, const struct iovec* vectors, int count, std::int64_t offset) {
return TransferSharedMemoryVectors(descriptor, vectors, count, offset, true);
}
#endif
int TruncateSharedMemory_nid_no_patch(int descriptor, std::int64_t length) {
SharedMemoryLock guard(descriptor);
if (!guard.IsShared()) return -2;
if (guard.Error() != 0) { errno = guard.Error(); return -1; }
const auto fd = guard.Descriptor();
#ifdef _WIN32
const int result = static_cast<int>(::_chsize_s(fd, length));
#else
const int result = ::ftruncate(fd, static_cast<off_t>(length));
#endif
return result;
}
}
extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int mode) {
const int accessMode = flags & SCE_KERNEL_O_ACCMODE;
if (accessMode != SCE_KERNEL_O_RDONLY && accessMode != SCE_KERNEL_O_RDWR) return Fail(GuestInvalid);
if ((flags & ~AcceptedFlags) != 0) return Fail(GuestInvalid);
if (path != anonymousObject) NotImplemented_nid_no_patch("shm_open of a named object");
if (path != anonymousObject) {
#ifdef _WIN32
NotImplemented_nid_no_patch("shm_open of a named object");
#else
return OpenNamedFile(path, flags, mode);
#endif
}
if (accessMode == SCE_KERNEL_O_RDONLY) return Fail(GuestInvalid);
const int descriptor = CreateAnonymousFile();
if (descriptor >= 0) return descriptor;
@ -86,3 +468,30 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int)
if (const int guest = GuestError(error)) return Fail(guest);
throw std::runtime_error("shm_open: creating the host anonymous file failed, errno=" + std::to_string(error));
}
extern "C" int APS5_VABI shm_unlink_nid_postfix(const char* path) {
#ifdef _WIN32
(void)path;
NotImplemented_nid_no_patch("shm_unlink of a named object");
#else
int validationError = 0;
const auto hostName = NamedHostPath(path, validationError);
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
HostShmLock namespaceLock;
if (namespaceLock.Error() != 0) {
errno = namespaceLock.Error();
if (const int guest = NamedGuestError(errno)) return Fail(guest);
throw std::runtime_error("shm_unlink: locking the host shared-memory namespace failed, errno=" + std::to_string(errno));
}
const auto objectPath = "/dev/shm" + hostName;
if (::faccessat(AT_FDCWD, objectPath.c_str(), R_OK | W_OK, AT_EACCESS) != 0) {
const int error = errno;
if (const int guest = NamedGuestError(error)) return Fail(guest);
throw std::runtime_error("shm_unlink: checking shared-memory permissions failed, errno=" + std::to_string(error));
}
if (::shm_unlink(hostName.c_str()) == 0) return 0;
const int error = errno;
if (const int guest = NamedGuestError(error)) return Fail(guest);
throw std::runtime_error("shm_unlink: removing the host named object failed, errno=" + std::to_string(error));
#endif
}

View File

@ -7,6 +7,7 @@
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/SharedMemory.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include "prx/libkernel/File/include/NativeStat.hpp"
#include "prx/libkernel/File/include/KernelStatfs.hpp"
@ -90,7 +91,8 @@ static int NativeFchmod(int descriptor, int mode) {
return path ? NativeChmod(*path, mode) : -1;
}
static int NativeFtruncate(int descriptor, std::int64_t length) {
return static_cast<int>(::_chsize_s(descriptor, length));
const int result = File::TruncateSharedMemory_nid_no_patch(descriptor, length);
return result == -2 ? static_cast<int>(::_chsize_s(descriptor, length)) : result;
}
static int NativeTruncate(const std::filesystem::path& path, std::int64_t length) {
const int descriptor = ::_wopen(path.c_str(), _O_WRONLY | _O_BINARY);
@ -158,6 +160,10 @@ std::int64_t NativePositioned_nid_no_patch(int descriptor, void* buf, std::size_
if (nbytes > static_cast<std::size_t>(std::numeric_limits<DWORD>::max())) {
throw std::runtime_error("NativePositioned: nbytes exceeds platform limit");
}
if (write) {
const auto result = File::PwriteSharedMemory_nid_no_patch(descriptor, buf, nbytes, offset);
if (result != -2) return result;
}
const auto handle = reinterpret_cast<HANDLE>(::_get_osfhandle(descriptor));
if (handle == INVALID_HANDLE_VALUE) {
errno = EBADF;
@ -189,6 +195,10 @@ static std::int64_t NativeTransfer(int descriptor, void* buf, std::size_t nbytes
if (nbytes > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
throw std::runtime_error("NativeTransfer: nbytes exceeds platform limit");
}
if (write) {
const auto result = File::WriteSharedMemory_nid_no_patch(descriptor, buf, nbytes);
if (result != -2) return result;
}
return write ? ::_write(descriptor, buf, static_cast<unsigned int>(nbytes)) : ::_read(descriptor, buf, static_cast<unsigned int>(nbytes));
}
static bool NativeIsDisk(int descriptor) {
@ -226,7 +236,8 @@ static int NativeFchmod(int descriptor, int mode) {
return ::fchmod(descriptor, static_cast<mode_t>(mode));
}
static int NativeFtruncate(int descriptor, std::int64_t length) {
return ::ftruncate(descriptor, static_cast<off_t>(length));
const int result = File::TruncateSharedMemory_nid_no_patch(descriptor, length);
return result == -2 ? ::ftruncate(descriptor, static_cast<off_t>(length)) : result;
}
static int NativeTruncate(const std::filesystem::path& path, std::int64_t length) {
return ::truncate(path.c_str(), static_cast<off_t>(length)) == 0 ? 0 : errno;
@ -250,7 +261,8 @@ static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, s
return static_cast<std::int64_t>(::pread(descriptor, buf, nbytes, static_cast<off_t>(offset)));
}
static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) {
return static_cast<std::int64_t>(::pwrite(descriptor, buf, nbytes, static_cast<off_t>(offset)));
const auto result = File::PwriteSharedMemory_nid_no_patch(descriptor, buf, nbytes, offset);
return result == -2 ? static_cast<std::int64_t>(::pwrite(descriptor, buf, nbytes, static_cast<off_t>(offset))) : result;
}
static_assert(sizeof(KernelIovec) == sizeof(struct iovec));
static_assert(offsetof(KernelIovec, base) == offsetof(struct iovec, iov_base));
@ -643,18 +655,12 @@ int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t off
}
int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset) {
if (buf == nullptr) {
APS5_INVALID_ARG_EX;
}
if (offset < 0) {
APS5_INVALID_ARG_EX;
}
if (buf == nullptr && nbytes != 0) return PosixFailure(GUEST_EFAULT);
if (offset < 0) return PosixFailure(GUEST_EINVAL);
if (FailPosixPositionedGuestSocket(d)) return -1;
auto n = NativePwrite(d, buf, nbytes, offset);
if (n < 0) {
throw std::runtime_error(std::string(__func__) + ": pwrite failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
}
return n;
char empty = 0;
const auto result = NativePwrite(d, buf == nullptr ? &empty : buf, nbytes, offset);
return result < 0 ? PosixFailure(SceErrorFromErrno(errno) & 0xffff) : result;
}
int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) {
@ -980,6 +986,8 @@ int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* iov, int iovcnt) {
const auto result = GuestSockets::Writev(d, reinterpret_cast<const GuestSockets::Iovec*>(iov), iovcnt);
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}
const auto sharedResult = File::WritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt);
if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult;
const auto result = static_cast<std::int64_t>(::writev(d, NativeIovecs(iov), iovcnt));
return result < 0 ? SceErrorFromErrno(errno) : result;
}
@ -999,6 +1007,8 @@ int64_t APS5_VABI sceKernelPwritev(int d, const KernelIovec* iov, int iovcnt, in
if (const int error = CheckIovecs(iov, iovcnt)) return error;
if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL);
if (const auto error = PositionedGuestSocketError(d); error != 0) return error;
const auto sharedResult = File::PwritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt, offset);
if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult;
const auto result = static_cast<std::int64_t>(::pwritev(d, NativeIovecs(iov), iovcnt, static_cast<off_t>(offset)));
return result < 0 ? SceErrorFromErrno(errno) : result;
}

View File

@ -1,4 +1,3 @@
#include <atomic>
#include <algorithm>
#include <cstdint>
#include <cstddef>
@ -17,6 +16,7 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include "prx/libc/include/CreationMask.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
@ -44,8 +44,6 @@ constexpr int GuestEnametoolong = 63;
constexpr int GuestEnotempty = 66;
constexpr int GuestEoverflow = 84;
std::atomic<std::uint16_t> creationMask{022};
std::int64_t Fail(int error) {
*__error_nid_postfix() = error;
return -1;
@ -76,7 +74,7 @@ int APS5_VABI setsid_nid_postfix(void) {
}
std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t mask) {
return creationMask.exchange(static_cast<std::uint16_t>(mask & 07777));
return File::ExchangeCreationMask_nid_no_patch(mask);
}
int APS5_VABI _fstatfs_nid_postfix(int d, void* buf);

View File

@ -0,0 +1,167 @@
#include "SceTypes.hpp"
#include "nid/Sha1.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <dlfcn.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
extern "C" {
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
int APS5_VABI fchmod_nid_postfix(int, int);
int APS5_VABI close_nid_postfix(int);
int* APS5_VABI __error_nid_postfix();
}
namespace {
using Unlink = int (*)(const char*);
constexpr std::int64_t Page = 0x4000;
constexpr int ReadWriteCreate = 0xa02;
struct Object {
std::string name = "/anyps5-concurrent-" + std::to_string(getpid());
std::string hostName = "/anyps5-shm-";
Object() {
constexpr char hex[] = "0123456789abcdef";
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
for (const auto byte : digest) {
hostName += hex[byte >> 4];
hostName += hex[byte & 15];
}
::shm_unlink(hostName.c_str());
}
~Object() { ::shm_unlink(hostName.c_str()); }
};
bool Race(int descriptor) {
for (int iteration = 0; iteration < 500; ++iteration) {
const auto size = (iteration & 1) ? 0 : Page;
if (ftruncate_nid_postfix(descriptor, size) != 0) return false;
const auto written = pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, Page / 2);
if (written < 0) return false;
FileStat info{};
if (fstat_nid_disambig1_nid_postfix(descriptor, &info) != 0 ||
(info.st_size != 0 && info.st_size != Page)) return false;
}
return true;
}
bool NoDescriptorFallback(int descriptor) {
if (ftruncate_nid_postfix(descriptor, Page) != 0) return false;
const pid_t child = fork();
if (child < 0) return false;
if (child == 0) {
const struct rlimit limit{0, 0};
if (setrlimit(RLIMIT_NOFILE, &limit) != 0) _exit(2);
std::int64_t written = -2;
try {
written = pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, Page);
} catch (const std::exception& exception) {
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite threw=%s guest_errno=%d native_errno=%d\n",
descriptor, exception.what(), *__error_nid_postfix(), errno);
_exit(3);
} catch (...) {
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite threw unknown exception guest_errno=%d native_errno=%d\n",
descriptor, *__error_nid_postfix(), errno);
_exit(3);
}
const int guestError = *__error_nid_postfix();
const int nativeError = errno;
struct stat info{};
if (::fstat(descriptor, &info) != 0) {
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite=%lld guest_errno=%d native_errno=%d fstat_errno=%d\n",
descriptor, static_cast<long long>(written), guestError, nativeError, errno);
_exit(4);
}
if (written != 0 && !(written == -1 && guestError == 24)) {
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite=%lld guest_errno=%d native_errno=%d st_size=%lld\n",
descriptor, static_cast<long long>(written), guestError, nativeError, static_cast<long long>(info.st_size));
_exit(5);
}
if (info.st_size != Page) {
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite=%lld guest_errno=%d native_errno=%d st_size=%lld expected=%lld\n",
descriptor, static_cast<long long>(written), guestError, nativeError,
static_cast<long long>(info.st_size), static_cast<long long>(Page));
_exit(6);
}
_exit(0);
}
int status = 0;
return waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0;
}
bool ChildrenSucceeded(const pid_t* children, int count) {
bool okay = true;
for (int index = 0; index < count; ++index) {
int status = 0;
const auto result = waitpid(children[index], &status, 0);
okay = result == children[index] && WIFEXITED(status) && WEXITSTATUS(status) == 0 && okay;
}
return okay;
}
void Run() {
Object object;
const auto unlinkObject = reinterpret_cast<Unlink>(dlsym(RTLD_DEFAULT, "shm_unlink_nid_postfix"));
if (unlinkObject == nullptr) throw std::runtime_error("guest shm_unlink export is missing");
const int descriptor = shm_open_nid_postfix(object.name.c_str(), ReadWriteCreate, 0600);
if (descriptor < 0) throw std::runtime_error("shared object creation failed");
pid_t children[4]{};
int childCount = 0;
bool okay = true;
for (int index = 0; index < 4; ++index) {
const pid_t child = fork();
if (child < 0) {
okay = false;
break;
}
if (child == 0) {
const int own = (index & 1) ? shm_open_nid_postfix(object.name.c_str(), 2, 0600) : descriptor;
const bool childOkay = own >= 0 && Race(own);
if ((index & 1) && own >= 0) close_nid_postfix(own);
_exit(childOkay ? 0 : 1);
}
children[childCount++] = child;
}
okay = Race(descriptor) && okay;
okay = ChildrenSucceeded(children, childCount) && okay;
if (!okay) throw std::runtime_error("interleaved resize/write produced an invalid shared-object size");
if (!NoDescriptorFallback(descriptor))
throw std::runtime_error("EOF write with no spare descriptors grew the shared object or returned an unexpected error");
if (fchmod_nid_postfix(descriptor, 0400) != 0)
throw std::runtime_error("changing shared-object permissions to read-only failed");
*__error_nid_postfix() = 0;
if (unlinkObject(object.name.c_str()) != -1 || *__error_nid_postfix() != 13)
throw std::runtime_error("unlink of a read-only shared object did not return EACCES");
if (fchmod_nid_postfix(descriptor, 0600) != 0)
throw std::runtime_error("restoring shared-object permissions failed");
if (unlinkObject(object.name.c_str()) != 0)
throw std::runtime_error("unlink after restoring write permission failed");
if (close_nid_postfix(descriptor) != 0)
throw std::runtime_error("shared-object close failed");
std::puts("cross-process truncate/write, descriptor sharing and unlink permissions passed");
}
}
int main() {
try {
Run();
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}

View File

@ -0,0 +1,177 @@
#include "SceTypes.hpp"
#include "nid/Sha1.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <vector>
extern "C" {
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
int* APS5_VABI __error_nid_postfix();
}
namespace {
constexpr int ReadWrite = 2;
constexpr int Create = 0x200;
constexpr int Exclusive = 0x800;
constexpr int Shared = 0x1;
constexpr std::size_t Page = 0x4000;
int armedDescriptor = -1;
int replacementDescriptor = -1;
bool armed = false;
bool swapped = false;
int CreateOrdinaryFile() {
char path[] = "/tmp/anyps5-descriptor-reuse-XXXXXX";
const int descriptor = ::mkstemp(path);
if (descriptor < 0) throw std::runtime_error("ordinary file creation failed");
if (::unlink(path) != 0) {
::close(descriptor);
throw std::runtime_error("ordinary file unlink failed");
}
constexpr char contents[] = "ordinary file contents";
if (::write(descriptor, contents, sizeof(contents)) != static_cast<ssize_t>(sizeof(contents)) ||
::lseek(descriptor, 0, SEEK_SET) != 0) {
::close(descriptor);
throw std::runtime_error("ordinary file initialization failed");
}
return descriptor;
}
void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")");
}
struct Object {
std::string name = "/anyps5-descriptor-reuse-" + std::to_string(getpid());
std::string hostName = "/anyps5-shm-";
Object() {
constexpr char hex[] = "0123456789abcdef";
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
for (const auto byte : digest) {
hostName += hex[byte >> 4];
hostName += hex[byte & 15];
}
::shm_unlink(hostName.c_str());
}
~Object() { ::shm_unlink(hostName.c_str()); }
int Open(int flags) const { return shm_open_nid_postfix(name.c_str(), flags, 0600); }
};
}
extern "C" int fcntl(int descriptor, int command, ...) {
long argument = 0;
switch (command) {
case F_GETFD:
case F_GETFL:
case F_GETOWN:
#ifdef F_GETSIG
case F_GETSIG:
#endif
#ifdef F_GETLEASE
case F_GETLEASE:
#endif
#ifdef F_GETPIPE_SZ
case F_GETPIPE_SZ:
#endif
#ifdef F_GET_SEALS
case F_GET_SEALS:
#endif
break;
#ifdef F_GETLK
case F_GETLK:
case F_SETLK:
case F_SETLKW: {
va_list args;
va_start(args, command);
argument = reinterpret_cast<long>(va_arg(args, void*));
va_end(args);
break;
}
#endif
case F_DUPFD:
case F_DUPFD_CLOEXEC:
case F_SETFD:
case F_SETFL:
case F_SETOWN:
#ifdef F_SETSIG
case F_SETSIG:
#endif
#ifdef F_SETLEASE
case F_SETLEASE:
#endif
#ifdef F_NOTIFY
case F_NOTIFY:
#endif
#ifdef F_SETPIPE_SZ
case F_SETPIPE_SZ:
#endif
#ifdef F_ADD_SEALS
case F_ADD_SEALS:
#endif
{
va_list args;
va_start(args, command);
argument = va_arg(args, int);
va_end(args);
break;
}
default:
errno = EINVAL;
return -1;
}
const long result = ::syscall(SYS_fcntl, descriptor, command, argument);
if (command == F_GETFL && armed && !swapped) {
swapped = true;
if (::syscall(SYS_dup2, replacementDescriptor, armedDescriptor) != armedDescriptor) return -1;
}
return static_cast<int>(result);
}
int main() {
try {
Object object;
const int descriptor = object.Open(ReadWrite | Create | Exclusive);
Require(descriptor >= 0 && ftruncate_nid_postfix(descriptor, Page) == 0, "shared-memory setup failed");
Require(pwrite_nid_disambig1_nid_postfix(descriptor, "original", 8, 0) == 8, "shared-memory seed failed");
const int verifier = object.Open(ReadWrite);
Require(verifier >= 0, "shared-memory verifier open failed");
const int replacement = CreateOrdinaryFile();
Require(replacement >= 0, "ordinary-file setup failed");
armedDescriptor = descriptor;
replacementDescriptor = replacement;
armed = true;
auto* mapped = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, descriptor, 0));
armed = false;
Require(swapped, "native F_GETFL interposer did not replace the guest descriptor");
Require(mapped != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest mmap failed after descriptor replacement");
Require(std::memcmp(mapped, "original", 8) == 0, "mapping followed the replacement descriptor instead of the pinned shared-memory object");
mapped[0] = 0x70;
char byte = 0;
Require(pread_nid_postfix(verifier, &byte, 1, 0) == 1 && byte == 0x70, "mapping did not update the original shared-memory object");
Require(munmap_nid_postfix(mapped, Page) == 0, "guest mapping cleanup failed");
Require(close_nid_postfix(descriptor) == 0 && close_nid_postfix(verifier) == 0, "guest descriptor cleanup failed");
Require(::close(replacement) == 0, "replacement descriptor cleanup failed");
std::puts("named shared-memory descriptor reuse checks passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}

View File

@ -0,0 +1,139 @@
#include "nid/Sha1.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>
#include <thread>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
struct GuestIovec { void* base; std::size_t length; };
extern "C" {
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI dup2_nid_postfix(int, int);
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int);
std::int64_t APS5_VABI writev_nid_postfix(int, const GuestIovec*, int);
std::int64_t APS5_VABI pwritev_nid_postfix(int, const GuestIovec*, int, std::int64_t);
int* APS5_VABI __error_nid_postfix();
}
namespace {
int original = -1;
int replacement = -1;
bool armed = false;
bool replaceAtProbe = false;
bool replaceBeforeProbe = false;
bool swapped = false;
void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
struct Object {
explicit Object(const char* suffix) : name("/anyps5-io-reuse-" + std::to_string(getpid()) + suffix) {
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
for (const auto byte : digest) {
hostName += "0123456789abcdef"[byte >> 4];
hostName += "0123456789abcdef"[byte & 15];
}
descriptor = shm_open_nid_postfix(name.c_str(), 2 | 0x200 | 0x800, 0600);
Require(descriptor >= 0, "object creation failed");
Require(::ftruncate(descriptor, 0x4000) == 0, "object sizing failed");
Require(::pwrite(descriptor, "safe", 4, 0) == 4, "object seeding failed");
}
~Object() { if (descriptor >= 0) ::close(descriptor); ::shm_unlink(hostName.c_str()); }
Object(const Object&) = delete;
Object& operator=(const Object&) = delete;
std::string name;
std::string hostName = "/anyps5-shm-";
int descriptor = -1;
};
void Run(int operation, int boundary) {
replaceAtProbe = boundary == 1;
replaceBeforeProbe = boundary == 2;
Object first(("-first-" + std::to_string(operation)).c_str());
Object second(("-second-" + std::to_string(operation)).c_str());
if (replaceBeforeProbe) {
char path[] = "/tmp/anyps5-io-reuse-XXXXXX";
const int temporary = ::mkstemp(path);
Require(temporary >= 0, "ordinary replacement creation failed");
Require(::unlink(path) == 0, "ordinary replacement unlink failed");
Require(::ftruncate(temporary, 0x4000) == 0 && ::pwrite(temporary, "safe", 4, 0) == 4,
"ordinary replacement initialization failed");
Require(::dup2(temporary, second.descriptor) == second.descriptor, "ordinary replacement installation failed");
::close(temporary);
}
original = first.descriptor;
replacement = second.descriptor;
swapped = false;
armed = true;
char payload[] = "bad!";
GuestIovec vector{payload, 4};
std::int64_t result = 0;
switch (operation) {
case 0: result = write_nid_postfix(original, payload, 4); break;
case 1: result = pwrite_nid_disambig1_nid_postfix(original, payload, 4, 0); break;
case 2: result = ftruncate_nid_postfix(original, 7); break;
case 3: result = lseek_nid_postfix(original, 7, SEEK_SET); break;
case 4: result = writev_nid_postfix(original, &vector, 1); break;
case 5: result = pwritev_nid_postfix(original, &vector, 1, 0); break;
}
armed = false;
Require(swapped, "descriptor replacement did not run at the selected acquisition boundary");
struct stat status{};
char bytes[4]{};
Require(::fstat(replacement, &status) == 0 && status.st_size == 0x4000, "replacement object size changed");
Require(::pread(replacement, bytes, 4, 0) == 4 && std::memcmp(bytes, "safe", 4) == 0, "replacement object data changed");
Require(::lseek(replacement, 0, SEEK_CUR) == 0, "replacement object offset changed");
Require(result == -1 && *__error_nid_postfix() == 9, "descriptor identity change was not rejected with EBADF");
}
}
static bool ReplaceDescriptor() {
swapped = true;
bool replaced = false;
std::thread reuse([&] {
replaced = close_nid_postfix(original) == 0 && dup2_nid_postfix(replacement, original) == original;
});
reuse.join();
return replaced;
}
extern "C" ssize_t readlink(const char* path, char* buffer, std::size_t length) {
if (armed && replaceBeforeProbe && !swapped) {
if (!ReplaceDescriptor()) return -1;
}
const auto result = static_cast<ssize_t>(::syscall(SYS_readlink, path, buffer, length));
if (result >= 0 && armed && replaceAtProbe && !swapped) {
if (!ReplaceDescriptor()) return -1;
}
return result;
}
extern "C" int flock(int descriptor, int operation) {
const int result = static_cast<int>(::syscall(SYS_flock, descriptor, operation));
if (result == 0 && operation == LOCK_EX && armed && !replaceAtProbe && !replaceBeforeProbe && !swapped) {
if (!ReplaceDescriptor()) return -1;
}
return result;
}
int main() {
int failures = 0;
for (int scenario = 0; scenario < 18; ++scenario) {
const int operation = scenario % 6;
try {
Run(operation, scenario / 6);
} catch (const std::exception& error) {
std::fprintf(stderr, "scenario %d: %s\n", scenario, error.what());
++failures;
}
}
if (failures != 0) return 1;
std::puts("guest shared-memory I/O descriptor reuse passed");
return 0;
}

View File

@ -0,0 +1,228 @@
#include "SceTypes.hpp"
#include "nid/Sha1.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <unistd.h>
extern "C" {
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int);
int APS5_VABI dup2_nid_postfix(int, int);
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int);
int APS5_VABI open_nid_postfix(const char*, int, int);
std::int64_t APS5_VABI writev_nid_postfix(int, const struct GuestIovec*, int);
std::int64_t APS5_VABI pwritev_nid_postfix(int, const struct GuestIovec*, int, std::int64_t);
int* APS5_VABI __error_nid_postfix();
}
struct GuestIovec {
void* base;
std::size_t length;
};
namespace {
constexpr int ReadOnly = 0;
constexpr int ReadWrite = 2;
constexpr int Create = 0x200;
constexpr int Exclusive = 0x800;
constexpr int Shared = 0x1;
constexpr int Private = 0x2;
constexpr std::size_t Page = 0x4000;
using Unlink = int (*)(const char*);
int CreateOrdinaryFile() {
char path[] = "/tmp/anyps5-lifecycle-XXXXXX";
const int descriptor = ::mkstemp(path);
if (descriptor < 0) throw std::runtime_error("ordinary file creation failed");
if (::unlink(path) != 0) {
::close(descriptor);
throw std::runtime_error("ordinary file unlink failed");
}
constexpr char contents[] = "ordinary file contents";
if (::write(descriptor, contents, sizeof(contents)) != static_cast<ssize_t>(sizeof(contents)) ||
::lseek(descriptor, 0, SEEK_SET) != 0) {
::close(descriptor);
throw std::runtime_error("ordinary file initialization failed");
}
return descriptor;
}
void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")");
}
struct Object {
std::string name = "/anyps5-lifecycle-" + std::to_string(getpid());
std::string hostName = "/anyps5-shm-";
Object() {
constexpr char hex[] = "0123456789abcdef";
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
for (const auto byte : digest) {
hostName += hex[byte >> 4];
hostName += hex[byte & 15];
}
::shm_unlink(hostName.c_str());
}
~Object() { ::shm_unlink(hostName.c_str()); }
int Open(int flags) const { return shm_open_nid_postfix(name.c_str(), flags, 0600); }
};
void Run() {
Object object;
const char* anonymousName = reinterpret_cast<const char*>(1);
const int anonymous = shm_open_nid_postfix(anonymousName, 0x100e02, 0600);
Require(anonymous >= 0 && ftruncate_nid_postfix(anonymous, Page) == 0, "anonymous shared-memory setup failed");
auto* anonymousMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, anonymous, 0));
Require(anonymousMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "anonymous MAP_SHARED failed");
anonymousMap[0] = 'a';
char anonymousByte = 0;
Require(pread_nid_postfix(anonymous, &anonymousByte, 1, 0) == 1 && anonymousByte == 'a',
"anonymous MAP_SHARED write was not visible through the descriptor");
Require(munmap_nid_postfix(anonymousMap, Page) == 0 && close_nid_postfix(anonymous) == 0, "anonymous shared-memory cleanup failed");
const int descriptor = object.Open(ReadWrite | Create | Exclusive);
Require(descriptor >= 0, "named object creation failed");
Require(ftruncate_nid_postfix(descriptor, Page) == 0, "guest resize failed");
Require(pwrite_nid_disambig1_nid_postfix(descriptor, "old", 4, 0) == 4, "guest positioned write failed");
char bytes[8]{};
Require(pread_nid_postfix(descriptor, bytes, sizeof(bytes), Page) == 0, "read at EOF was not empty");
Require(pwrite_nid_disambig1_nid_postfix(descriptor, nullptr, 0, Page) == 0, "zero-length write failed");
*__error_nid_postfix() = 0;
Require(lseek_nid_postfix(descriptor, Page + 2, SEEK_SET) == -1 && *__error_nid_postfix() == 22,
"seek past EOF did not fail with EINVAL");
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "seek to EOF returned the wrong position");
Require(write_nid_postfix(descriptor, "z", 1) == 0, "write at EOF grew the shared object");
Require(lseek_nid_postfix(descriptor, Page - 1, SEEK_SET) == static_cast<std::int64_t>(Page - 1), "seek near EOF failed");
Require(write_nid_postfix(descriptor, "xy", 2) == 1, "cross-EOF write did not return its short length");
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "cross-EOF write changed object size");
char vectorBytes[] = "uv";
GuestIovec vectorWrite{vectorBytes, 2};
Require(pwritev_nid_postfix(descriptor, nullptr, 0, Page) == 0, "zero-vector positioned write on a writable descriptor failed");
Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page) == 0, "positioned vector write at EOF grew the object");
Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page - 1) == 1, "positioned vector write past EOF was not short");
Require(lseek_nid_postfix(descriptor, Page - 1, SEEK_SET) == static_cast<std::int64_t>(Page - 1), "seek before vector write failed");
Require(writev_nid_postfix(descriptor, &vectorWrite, 1) == 1, "vector write past EOF was not short");
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "vector write changed object size");
*__error_nid_postfix() = 0;
Require(lseek_nid_postfix(descriptor, -1, SEEK_SET) == -1 && *__error_nid_postfix() == 22,
"negative seek did not return EINVAL");
Require(lseek_nid_postfix(descriptor, std::numeric_limits<std::int64_t>::max(), SEEK_CUR) == -1 &&
*__error_nid_postfix() == 84, "overflowing seek did not return EOVERFLOW");
const int second = object.Open(ReadWrite);
Require(second >= 0 && pread_nid_postfix(second, bytes, 4, 0) == 4 && std::memcmp(bytes, "old", 4) == 0,
"independent open did not see the shared object");
auto* firstMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, descriptor, 0));
Require(firstMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest shared mapping failed");
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(firstMap), Page),
"file-backed shared mapping was registered with write watch");
auto* secondMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, second, 0));
Require(secondMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "second guest shared mapping failed");
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(secondMap), Page),
"file-backed shared alias was registered with write watch");
*__error_nid_postfix() = 0;
Require(mmap_nid_postfix(nullptr, Page, 1, Shared, second, 1) == reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)) &&
*__error_nid_postfix() == 22, "unaligned shared mapping offset did not return EINVAL");
firstMap[0] = 'n';
Require(secondMap[0] == 'n', "shared mappings did not observe each other's writes");
auto* privateMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Private, second, 0));
Require(privateMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest private mapping failed");
firstMap[1] = 'v';
Require(privateMap[1] == 'v', "private mapping did not reflect backing changes before its first write");
privateMap[0] = 'p';
Require(firstMap[0] == 'n', "private mapping write escaped copy-on-write isolation");
Require(pread_nid_postfix(second, bytes, 1, 0) == 1 && bytes[0] == 'n', "private mapping write changed the backing object");
const int readOnly = object.Open(ReadOnly);
Require(readOnly >= 0, "read-only open failed");
*__error_nid_postfix() = 0;
Require(pwrite_nid_disambig1_nid_postfix(readOnly, "x", 1, 0) == -1 && *__error_nid_postfix() == 9,
"positioned write on a read-only descriptor did not return EBADF");
*__error_nid_postfix() = 0;
Require(pwrite_nid_disambig1_nid_postfix(readOnly, "x", 1, Page) == -1 && *__error_nid_postfix() == 9,
"positioned write at EOF on a read-only descriptor did not return EBADF");
*__error_nid_postfix() = 0;
Require(pwritev_nid_postfix(readOnly, nullptr, 0, 0) == -1 && *__error_nid_postfix() == 9,
"zero-vector positioned write on a read-only descriptor did not return EBADF");
FileStat readOnlyInfo{};
Require(fstat_nid_disambig1_nid_postfix(readOnly, &readOnlyInfo) == 0 && readOnlyInfo.st_size == Page,
"read-only EOF write changed the shared-object size");
void* rejected = mmap_nid_postfix(nullptr, Page, 3, Shared, readOnly, 0);
Require(rejected == reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)), "writable shared mapping accepted a read-only descriptor");
auto* writableMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 1, Shared, descriptor, 0));
Require(writableMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "read-protected writable-fd mapping failed");
Require(mprotect_nid_postfix(writableMap, Page, 3) == 0, "mprotect could not restore write access from a writable descriptor");
writableMap[2] = 'm';
Require(firstMap[2] == 'm', "mprotect-upgraded mapping did not remain shared");
Require(munmap_nid_postfix(writableMap, Page) == 0, "upgraded mapping unmap failed");
auto* readOnlyMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 1, Shared, readOnly, 0));
Require(readOnlyMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "read-only shared mapping failed");
*__error_nid_postfix() = 0;
Require(mprotect_nid_postfix(readOnlyMap, Page, 3) == -1 && *__error_nid_postfix() == 13,
"mprotect on a read-only shared mapping did not return EACCES");
Require(munmap_nid_postfix(readOnlyMap, Page) == 0, "read-only mapping unmap failed");
const int ordinary = CreateOrdinaryFile();
Require(ordinary >= 0, "ordinary file setup failed");
Require(dup2_nid_postfix(ordinary, second) == second, "dup2 onto shared descriptor failed");
Require(lseek_nid_postfix(second, 0, SEEK_SET) == 0 && read_nid_postfix(second, bytes, sizeof(bytes)) > 0,
"dup2 target did not become the ordinary file");
Require(close_nid_postfix(second) == 0, "close after dup2 failed");
Require(lseek_nid_postfix(ordinary, 0, SEEK_SET) == 0 && read_nid_postfix(ordinary, bytes, sizeof(bytes)) > 0,
"closing reused descriptor damaged the ordinary file");
Require(close_nid_postfix(ordinary) == 0, "ordinary file close failed");
const int reused = object.Open(ReadWrite);
Require(reused >= 0, "shared object reopen after descriptor close failed");
Require(dup2_nid_postfix(reused, second) == second, "guest dup2 into the freed descriptor slot failed");
Require(pread_nid_postfix(second, bytes, 3, 0) == 3 && std::memcmp(bytes, "nvm", 3) == 0,
"reused descriptor slot retained ordinary-file state");
Require(close_nid_postfix(second) == 0, "reused descriptor slot close failed");
if (reused != second) Require(close_nid_postfix(reused) == 0, "reopened descriptor close failed");
Require(close_nid_postfix(descriptor) == 0 && close_nid_postfix(readOnly) == 0, "descriptor close failed");
Require(firstMap[0] == 'n', "mapping did not survive descriptor close");
const auto unlinkObject = reinterpret_cast<Unlink>(dlsym(RTLD_DEFAULT, "shm_unlink_nid_postfix"));
Require(unlinkObject != nullptr, "guest shm_unlink export is missing");
Require(unlinkObject(object.name.c_str()) == 0, "guest unlink failed");
*__error_nid_postfix() = 0;
Require(unlinkObject(object.name.c_str()) == -1 && *__error_nid_postfix() == 2, "missing-name unlink did not return ENOENT");
const int recreated = object.Open(ReadWrite | Create | Exclusive);
Require(recreated >= 0, "name recreation after unlink failed");
Require(ftruncate_nid_postfix(recreated, Page) == 0, "recreated object resize failed");
Require(pread_nid_postfix(recreated, bytes, 4, 0) == 4 && std::memcmp(bytes, "\0\0\0\0", 4) == 0,
"recreated name still refers to the unlinked object");
Require(firstMap[0] == 'n', "unlink or recreation changed the old mapping");
Require(close_nid_postfix(recreated) == 0, "recreated descriptor close failed");
Require(munmap_nid_postfix(firstMap, Page) == 0 && munmap_nid_postfix(secondMap, Page) == 0 && munmap_nid_postfix(privateMap, Page) == 0,
"guest unmap failed");
std::puts("named shared-memory lifecycle checks passed");
}
}
int main() {
try {
Run();
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}

View File

@ -0,0 +1,126 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "nid/Sha1.hpp"
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
extern "C" int APS5_VABI shm_open_nid_postfix(const char*, int, int);
extern "C" int* APS5_VABI __error_nid_postfix();
static void Require(bool value, const char* message) {
if (!value) {
throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")");
}
}
struct Object {
std::string name;
std::string hostName = "/anyps5-shm-";
explicit Object(std::string suffix) : name("/anyps5-test-" + std::to_string(getpid()) + suffix) {
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
for (const auto byte : digest) {
hostName += "0123456789abcdef"[byte >> 4];
hostName += "0123456789abcdef"[byte & 15];
}
::shm_unlink(hostName.c_str());
}
~Object() { ::shm_unlink(hostName.c_str()); }
int Open(int flags, int mode = 0600) const { return shm_open_nid_postfix(name.c_str(), flags, mode); }
};
static void RequireError(const char* name, int flags, int error) {
*__error_nid_postfix() = 0;
Require(shm_open_nid_postfix(name, flags, 0600) == -1, "expected open failure");
Require(*__error_nid_postfix() == error, "incorrect guest errno");
}
static void Run() {
constexpr int readWrite = 2, create = 0x200, exclusive = 0x800, truncate = 0x400;
Object object("/nested");
RequireError(object.name.c_str(), readWrite, 2);
*__error_nid_postfix() = 73;
const int first = object.Open(readWrite | create | exclusive);
Require(first >= 0, "named creation failed");
Require(*__error_nid_postfix() == 73, "successful creation overwrote guest errno");
Require((fcntl(first, F_GETFD) & FD_CLOEXEC) != 0, "descriptor is not close-on-exec");
struct stat info{};
Require(fstat(first, &info) == 0 && info.st_size == 0, "new object is not empty");
Require(ftruncate(first, 0x4000) == 0, "resize failed");
auto* mapped = static_cast<char*>(::mmap(nullptr, 0x4000, PROT_READ | PROT_WRITE, MAP_SHARED, first, 0));
Require(mapped != MAP_FAILED, "host shared mapping failed");
std::memcpy(mapped, "shared", 7);
RequireError(object.name.c_str(), readWrite | create | exclusive, 17);
const int reader = object.Open(truncate);
Require(reader >= 0, "read-only reopen failed");
Require(fstat(reader, &info) == 0 && info.st_size == 0x4000, "read-only O_TRUNC truncated the object");
char bytes[7]{};
Require(pread(reader, bytes, sizeof(bytes), 0) == sizeof(bytes) && std::memcmp(bytes, "shared", 7) == 0, "reopened object has different contents");
Require(pwrite(reader, bytes, sizeof(bytes), 0) == -1, "read-only descriptor accepted a write");
Require(::mmap(nullptr, 0x4000, PROT_READ | PROT_WRITE, MAP_SHARED, reader, 0) == MAP_FAILED, "read-only descriptor accepted a writable shared mapping");
Object separate("/different");
const int different = separate.Open(readWrite | create | exclusive, 0400);
Require(different >= 0 && fstat(different, &info) == 0 && info.st_size == 0, "different names share an object");
Require(ftruncate(different, 0x4000) == 0 && pwrite(different, "y", 1, 0) == 1, "creation permissions incorrectly restrict the new descriptor");
Require(mapped[0] == 's', "different object write changed the first object");
Require(close(different) == 0, "different object close failed");
const auto child = fork();
Require(child >= 0, "fork failed");
if (child == 0) {
const int reopened = object.Open(readWrite);
char contents[7]{};
const bool okay = reopened >= 0 && pread(reopened, contents, sizeof(contents), 0) == sizeof(contents) && std::memcmp(contents, "shared", 7) == 0;
if (reopened >= 0) close(reopened);
_exit(okay ? 0 : 1);
}
int status = 0;
Require(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0, "cross-process reopen failed");
Require(::munmap(mapped, 0x4000) == 0, "host unmap failed");
Require(close(reader) == 0 && close(first) == 0, "close failed");
const int truncated = object.Open(readWrite | truncate);
Require(truncated >= 0 && fstat(truncated, &info) == 0 && info.st_size == 0, "read-write truncation failed");
Require(close(truncated) == 0, "close after truncation failed");
RequireError(nullptr, readWrite, 14);
RequireError("", readWrite, 22);
RequireError("relative", readWrite, 22);
RequireError(object.name.c_str(), 1, 22);
RequireError(object.name.c_str(), readWrite | 8, 22);
const std::string tooLong(1024, '/');
RequireError(tooLong.c_str(), readWrite, 63);
const auto prefixLength = std::string("/anyps5-test-").size() + std::to_string(getpid()).size();
Object longName(std::string(1023 - prefixLength, 'x'));
Require(longName.name.size() == 1023, "incorrect boundary test length");
const auto previousMask = umask(0077);
const int longDescriptor = longName.Open(create | readWrite, 07777);
umask(previousMask);
Require(longDescriptor >= 0 && fstat(longDescriptor, &info) == 0, "long name creation failed");
Require((info.st_mode & 07777) == 0755, "default guest umask was not applied independently of host umask");
Require(close(longDescriptor) == 0, "long name close failed");
const int longReader = longName.Open(0);
Require(longReader >= 0 && close(longReader) == 0, "maximum-length name reopen failed");
const auto* anonymous = reinterpret_cast<const char*>(1);
const int anonymousFirst = shm_open_nid_postfix(anonymous, readWrite, 0);
const int anonymousSecond = shm_open_nid_postfix(anonymous, readWrite | create | exclusive | truncate, 0400);
Require(anonymousFirst >= 0 && anonymousSecond >= 0, "anonymous creation regressed");
Require(ftruncate(anonymousFirst, 0x4000) == 0 && pwrite(anonymousFirst, "x", 1, 0) == 1, "anonymous write failed");
Require(pread(anonymousSecond, bytes, 1, 0) == 0, "anonymous objects share contents");
Require(close(anonymousFirst) == 0 && close(anonymousSecond) == 0, "anonymous close failed");
RequireError(anonymous, 0, 22);
RequireError(anonymous, 3, 22);
std::puts("named shared-memory checks passed");
}
int main() {
try {
Run();
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}

View File

@ -0,0 +1,83 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "nid/Sha1.hpp"
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
extern "C" int APS5_VABI shm_open_nid_postfix(const char*, int, int);
extern "C" std::uint16_t APS5_VABI umask_nid_postfix(std::uint16_t);
static void Require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
class Masks {
public:
Masks(std::uint16_t guest, mode_t host) : guest(umask_nid_postfix(guest)), host(::umask(host)) {}
~Masks() { ::umask(host); umask_nid_postfix(guest); }
Masks(const Masks&) = delete;
Masks& operator=(const Masks&) = delete;
private:
std::uint16_t guest;
mode_t host;
};
class Object {
public:
explicit Object(const char* suffix) : name("/anyps5-permissions-" + std::to_string(getpid()) + suffix) {
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
for (auto byte : digest) {
hostName += "0123456789abcdef"[byte >> 4];
hostName += "0123456789abcdef"[byte & 15];
}
::shm_unlink(hostName.c_str());
}
~Object() { if (descriptor >= 0) ::close(descriptor); ::shm_unlink(hostName.c_str()); }
Object(const Object&) = delete;
Object& operator=(const Object&) = delete;
std::string name;
int descriptor = -1;
private:
std::string hostName = "/anyps5-shm-";
};
static void CheckMode(const char* suffix, std::uint16_t guestMask, mode_t hostMask, int requested, mode_t expected) {
Masks masks(guestMask, hostMask);
Object object(suffix);
object.descriptor = shm_open_nid_postfix(object.name.c_str(), 2 | 0x200 | 0x800, requested);
Require(object.descriptor >= 0, "shared-memory creation failed");
struct stat info{};
Require(::fstat(object.descriptor, &info) == 0, "stat failed");
if ((info.st_mode & 07777) != expected) {
std::fprintf(stderr, "requested=%o guest-mask=%o host-mask=%o actual=%o expected=%o\n", requested, guestMask, hostMask, info.st_mode & 07777, expected);
throw std::runtime_error("shared-memory mode did not follow the guest umask");
}
const auto observedHostMask = ::umask(hostMask);
Require(observedHostMask == hostMask, "guest creation changed host umask");
if (expected == 0600) {
const int reopened = shm_open_nid_postfix(object.name.c_str(), 2 | 0x200, 0777);
Require(reopened >= 0, "existing-object reopen failed");
const int result = ::fstat(reopened, &info);
::close(reopened);
Require(result == 0 && (info.st_mode & 07777) == expected, "reopen changed existing permissions");
}
}
int main() {
try {
CheckMode("-guest-restricts", 0077, 0000, 0666, 0600);
CheckMode("-host-restricts", 0000, 0077, 0666, 0666);
CheckMode("-guest-denies", 0777, 0000, 0777, 0000);
CheckMode("-special-bits", 0022, 0077, 07777, 0755);
std::puts("guest shared-memory permissions passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}

View File

@ -37,6 +37,10 @@ int main() {
Require(pread_nid_postfix(first, &last, 1, 0x3fff) == 1 && last == 0);
Require(pread_nid_postfix(first, &last, 1, 0x4000) == 0);
Require(pread_nid_postfix(second, back, sizeof(back), 0) == 0);
#ifndef _WIN32
Require(pwrite_nid_disambig1_nid_postfix(third, text, sizeof(text), 0) == 0);
#endif
Require(ftruncate_nid_postfix(third, 0x4000) == 0);
Require(pwrite_nid_disambig1_nid_postfix(third, text, sizeof(text), 0) == static_cast<std::int64_t>(sizeof(text)));
Require(close_nid_postfix(first) == 0 && close_nid_postfix(second) == 0 && close_nid_postfix(third) == 0);
RequireInvalid(anonymous, 0x0);
@ -47,6 +51,7 @@ int main() {
RequireInvalid(anonymous, 0x2 | 0x4);
RequireInvalid("/anyps5-shm", 0x1);
RequireInvalid("/anyps5-shm", 0x202 | 0x8);
#ifdef _WIN32
bool threw = false;
try {
shm_open_nid_postfix("/anyps5-shm", 0x202, 0600);
@ -54,4 +59,7 @@ int main() {
threw = true;
}
Require(threw);
#else
RequireInvalid("", 0x202);
#endif
}

View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.22)
project(SharedMemoryTests LANGUAGES CXX)
enable_testing()
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "Standalone shared-memory tests require Linux")
endif()
set(LIBS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../..")
add_executable(guest_named_shm_open_tests
../GuestNamedShmOpen.cpp
Support.cpp
${LIBS_DIR}/prx/libkernel/File/src/SharedMemory.cpp
${LIBS_DIR}/prx/libc/src/CreationMask.cpp
${LIBS_DIR}/nid/src/Sha1.cpp)
target_include_directories(guest_named_shm_open_tests PRIVATE ${LIBS_DIR} ${LIBS_DIR}/nid/include)
target_compile_features(guest_named_shm_open_tests PRIVATE cxx_std_20)
target_link_libraries(guest_named_shm_open_tests PRIVATE rt)
add_test(NAME guest_named_shm_open COMMAND guest_named_shm_open_tests)
set_tests_properties(guest_named_shm_open PROPERTIES TIMEOUT 20)

View File

@ -0,0 +1,11 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <stdexcept>
extern "C" int* APS5_VABI __error_nid_postfix() {
static thread_local int error;
return &error;
}
extern "C" void NotImplemented_nid_no_patch(const char* name) {
throw std::runtime_error(name);
}

View File

@ -401,7 +401,7 @@ Throughout the project, every function at every stage either **does exactly what
- [Pending storage-image results](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970). `APS5_STORE_AT_FLIP=1` stores every pending result at each flip; storing them at the title's GPU waits (labels, fences) is unimplemented.
- Windows host-pointer imports of the guest mapping itself are compared instead of write-watched: a driver can mark imported pages dirty after the synchronous import window. Shared memory imported through a separate writable alias retains protection-based tracking on the guest views; driver access to the import alias does not disarm those views. [GuestMemory](../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) excludes every overlapping 64 KiB tracker block while its backing is retained. A full block replaced by fresh private backing regains tracking and gets a new generation; protection changes, partial replacements and shared remappings do not restore it. This preserves CPU edits through byte comparisons but disables snapshot reuse and unit shadows for excluded blocks; restoring tracking on import retirement without replacing the backing and measuring the comparison cost remain unimplemented.
- [Storage-image units under a Linux import window](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) that the window stamped as a possible CPU store (`BlockMaybeWritten`) are compared with the bytes the image was uploaded from or written back as, and are unchanged when those still match. A CPU store of the bytes already there within the window cannot be told apart from an import that resolves the write protection without storing, as the NVIDIA driver (610.57.04) does for every imported page, so such a store does not win over pending results. Images tracked by layer instead of by 64 KiB unit still read the window as a change and upload again.
- [shm_open](../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) creates only anonymous objects (`SHM_ANON`), each backed by a host anonymous file: a `memfd` on Linux, a delete-on-close temporary file on Windows. The flag checks follow FreeBSD 11's `kern_shm_open`; the mode is ignored. Named objects throw. The descriptor cannot be mapped yet: `mmap` accepts only private anonymous mappings and fails a descriptor mapping with `EOPNOTSUPP`, so the known caller (the bundled Mesa in PPSA59322, which truncates the object and maps it `MAP_SHARED`) gets that failure
- [Shared memory](../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) on Linux implements anonymous and named objects, bounded descriptor writes and seeks, and `shm_unlink` with effective read/write permission checks from FreeBSD 11. Named paths up to 1023 bytes use `/anyps5-shm-` plus SHA-1; collisions are not detected, and the namespace is shared across processes in the host mount namespace. Named creation applies the guest umask without changing the host mask. Linux procfs and `/dev/shm` are required. Operations use an advisory directory lock; external host operations bypass it, unrelated objects serialize, and descriptor operations require two spare host descriptors and compare device/inode with the initial descriptor identity on both the pinned-object and ordinary-file fallback paths. [Guest mappings](../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) support shared and private mappings of these descriptors through the allocation tracker and pin a duplicate descriptor during validation and mapping; mapping requires one spare host descriptor; file-backed mappings use the write-watch scan fallback because aliases and descriptor writes bypass per-mapping tracking. Named objects and file-backed mappings on Windows remain unsupported. Invalid non-null name pointers, console behaviour and title compatibility remain unverified.
- [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a write event reports the socket's send buffer size (`SO_SNDBUF`) in `data`, not the free space left in it, as Windows has no query for that space. Only guest sockets are supported; other descriptors and a low watermark above 1 throw
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
@ -437,7 +437,7 @@ Throughout the project, every function at every stage either **does exactly what
- [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.
- [execvp](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported. PPSA85300 imports it and fails to load without it; its `fork` and `vfork` fail with `ENOSYS` ([libc_missing.c](https://github.com/ZiZc3/VLC/blob/f3253020a64368153233ede29183014c0b6c8979/ps5/compat/libc_missing.c#L290-L292)), so the child process that would call it is never created.
- [fork](../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, but files are still created with the host process's mask
- [fork](../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask
- [MIMG BY2/BY4 loads](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_by2/by4`, opcodes 0x42, 0x43, 0x4a and 0x4b) were measured on RDNA2 (gfx1035) with the [hardware oracle](HW_ORACLE.md) for 2D images with identity swizzle. They read N texels (BY2: 2, BY4: 4) from `x & ~(N - 1)` on row y, texel by texel and component by component within each texel, with each texel converted like `image_load`. Every register is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when the N texels fit in one dword: R8 and R16 (DMASK 0x3) or RG8 (0xf) for BY2, and R8 (0xf) for BY4. With 32-bit texels (R32, RG32, RG16, RGBA8) the hardware returns zeros. Other formats, masks, dimensions, swizzles, D16/A16 and indirect descriptors throw. The stores (0x52, 0x53, 0x5a and 0x5b) decode but throw at translation. Measured on hardware, they write from the unaligned x up to the end of its dword, which is not implemented.
- [2D stores to 1D storage images](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp): an `image_store` with DIM 2D through a 1D T# writes texel x and ignores y, as measured on RDNA2 (gfx1035) for y from 0 to 4 and 0xffffffff, single- and four-component formats and a 1x1 8_8_8_8_UNORM T#: the T# type decides the addressing, DIM only how many address VGPRs are read. Storage loads, atomics and `image_store_mip` through such a pair still throw
- [MIMG PCK2/PCK4 loads](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_load[_mip]_pck2/pck4`, opcodes 0x70, 0x71, 0x73 and 0x74) were measured on RDNA2 (gfx1035) with the [hardware oracle](HW_ORACLE.md) for 2D images with identity swizzle. With data mask 0x1, they write one dword: the raw bits of the N texels (PCK2: 2, PCK4: 4) from `x & ~(N - 1)` on row y, concatenated (element k at bit `k * texel bits`) and zero-extended. The dword is zero when the mip level is past the last level, `x + N - 1` is outside the level's width (unaligned x, unsigned), or y is outside its height. Data only comes back when N texels fit in 32 bits: R8, R16 or RG8 for PCK2, R8 for PCK4. Wider texels (R32, RG16, RGBA8) and masks other than 0x1 write zeros on hardware. Those masks throw at decode, and formats outside UNORM/UINT/SINT R8, R16 and RG8 throw. #1314's earlier measurement saw only zeros because it used masks 0x3 and 0xf. The stores (0x76, 0x77, 0x79 and 0x7a) decode but throw at translation. They are not measured, and opcode 0x76 on 32_32_32_32_FLOAT hung the GPU (#1314).