Merge pull request #3651 from MarcZX100/codex/libc-posix-spawn-3624

feat(libSceLibcInternal): implement posix_spawn actions
This commit is contained in:
Dean 2026-10-14 00:37:44 +13:00 committed by GitHub
commit e09ee165d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 305 additions and 1 deletions

View File

@ -1,4 +1,4 @@
add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp FileTime.cpp Getopt.cpp UserContext.cpp)
add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp FileTime.cpp Getopt.cpp Spawn.cpp UserContext.cpp)
target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS})
target_sources(libSceLibcInternal PRIVATE Directory.cpp)
@ -55,6 +55,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_utime PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibcInternal>")
endif()
add_test_executable(guest_posix_spawn_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSpawn.cpp)
target_include_directories(guest_posix_spawn_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_posix_spawn_tests PRIVATE libSceLibcInternal libc)
configure_windows_unwind(guest_posix_spawn_tests)
add_test(NAME guest_posix_spawn COMMAND guest_posix_spawn_tests)
set_tests_properties(guest_posix_spawn PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_posix_spawn PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibcInternal>")
endif()
add_test_executable(guest_program_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestProgramName.cpp)
target_include_directories(guest_program_name_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_program_name_tests PRIVATE libSceLibcInternal libkernel libc)

View File

@ -0,0 +1,119 @@
#include "prx/libSceLibcInternal/include/Spawn.hpp"
#include "prx/libc/include/General.hpp"
#include <cstdint>
#include <new>
#include <stdexcept>
#include <string>
#include <utility>
namespace {
constexpr int BadDescriptor = 9;
constexpr int OutOfMemory = 12;
PosixSpawnAttributes& Attributes(PosixSpawnAttributes* const* attributes, const char* function) {
if (attributes == nullptr || *attributes == nullptr) throw std::invalid_argument(std::string(function) + ": uninitialized attributes");
return **attributes;
}
PosixSpawnFileActions& FileActions(PosixSpawnFileActions* const* actions, const char* function) {
if (actions == nullptr || *actions == nullptr) throw std::invalid_argument(std::string(function) + ": uninitialized file actions");
return **actions;
}
int Append(PosixSpawnFileActions& actions, PosixSpawnFileAction action) {
try {
actions.actions.push_back(std::move(action));
} catch (const std::bad_alloc&) {
return OutOfMemory;
}
return 0;
}
}
extern "C" {
int APS5_VABI posix_spawnattr_init_nid_postfix(PosixSpawnAttributes** attributes) {
if (attributes == nullptr) throw std::invalid_argument("posix_spawnattr_init: null attributes");
auto* created = new (std::nothrow) PosixSpawnAttributes{};
if (created == nullptr) return OutOfMemory;
*attributes = created;
return 0;
}
int APS5_VABI posix_spawnattr_destroy_nid_postfix(PosixSpawnAttributes** attributes) {
if (attributes == nullptr) throw std::invalid_argument("posix_spawnattr_destroy: null attributes");
delete *attributes;
return 0;
}
int APS5_VABI posix_spawnattr_setflags_nid_postfix(PosixSpawnAttributes** attributes, std::int16_t flags) {
Attributes(attributes, "posix_spawnattr_setflags").flags = flags;
return 0;
}
int APS5_VABI posix_spawnattr_setsigdefault_nid_postfix(PosixSpawnAttributes** attributes, const GuestSignalSet* signalDefault) {
auto& target = Attributes(attributes, "posix_spawnattr_setsigdefault");
if (signalDefault == nullptr) throw std::invalid_argument("posix_spawnattr_setsigdefault: null signal set");
target.signalDefault = *signalDefault;
return 0;
}
int APS5_VABI posix_spawn_file_actions_init_nid_postfix(PosixSpawnFileActions** actions) {
if (actions == nullptr) throw std::invalid_argument("posix_spawn_file_actions_init: null file actions");
auto* created = new (std::nothrow) PosixSpawnFileActions{};
if (created == nullptr) return -1;
*actions = created;
return 0;
}
int APS5_VABI posix_spawn_file_actions_destroy_nid_postfix(PosixSpawnFileActions** actions) {
delete &FileActions(actions, "posix_spawn_file_actions_destroy");
return 0;
}
int APS5_VABI posix_spawn_file_actions_addopen_nid_postfix(PosixSpawnFileActions** actions, int descriptor, const char* path, int openFlags, std::uint16_t mode) {
if (descriptor < 0) return BadDescriptor;
if (path == nullptr) throw std::invalid_argument("posix_spawn_file_actions_addopen: null path");
auto& target = FileActions(actions, "posix_spawn_file_actions_addopen");
try {
return Append(target, {PosixSpawnFileActionKind::Open, descriptor, path, openFlags, mode, 0});
} catch (const std::bad_alloc&) {
return OutOfMemory;
}
}
int APS5_VABI posix_spawn_file_actions_adddup2_nid_postfix(PosixSpawnFileActions** actions, int descriptor, int newDescriptor) {
if (descriptor < 0 || newDescriptor < 0) return BadDescriptor;
return Append(FileActions(actions, "posix_spawn_file_actions_adddup2"), {PosixSpawnFileActionKind::Duplicate, descriptor, {}, 0, 0, newDescriptor});
}
int APS5_VABI posix_spawn_file_actions_addclose_nid_postfix(PosixSpawnFileActions** actions, int descriptor) {
if (descriptor < 0) return BadDescriptor;
return Append(FileActions(actions, "posix_spawn_file_actions_addclose"), {PosixSpawnFileActionKind::Close, descriptor, {}, 0, 0, 0});
}
int APS5_VABI posix_spawn_nid_postfix(std::int32_t* processId, const char* path, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes, char* const* arguments, char* const* environment) {
(void)processId;
(void)path;
(void)actions;
(void)attributes;
(void)arguments;
(void)environment;
NotImplemented_nid_no_patch("posix_spawn: process creation");
return -1;
}
int APS5_VABI posix_spawnp_nid_postfix(std::int32_t* processId, const char* file, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes, char* const* arguments, char* const* environment) {
(void)processId;
(void)file;
(void)actions;
(void)attributes;
(void)arguments;
(void)environment;
NotImplemented_nid_no_patch("posix_spawnp: process creation");
return -1;
}
}

View File

@ -0,0 +1,37 @@
#ifndef CORE_LIBS_PRX_LIBSCELIBCINTERNAL_INCLUDE_SPAWN_HPP
#define CORE_LIBS_PRX_LIBSCELIBCINTERNAL_INCLUDE_SPAWN_HPP
#include <cstdint>
#include <string>
#include <vector>
struct GuestSignalSet {
std::uint32_t bits[4];
};
static_assert(sizeof(GuestSignalSet) == 16);
struct PosixSpawnAttributes {
std::int16_t flags;
std::int32_t processGroup;
std::int32_t schedulingPriority;
std::int32_t schedulingPolicy;
GuestSignalSet signalDefault;
GuestSignalSet signalMask;
};
enum class PosixSpawnFileActionKind { Open, Duplicate, Close };
struct PosixSpawnFileAction {
PosixSpawnFileActionKind kind;
int descriptor;
std::string path;
int openFlags;
std::uint16_t mode;
int newDescriptor;
};
struct PosixSpawnFileActions {
std::vector<PosixSpawnFileAction> actions;
};
#endif

View File

@ -0,0 +1,137 @@
#include "prx/libSceLibcInternal/include/Spawn.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <string>
extern "C" {
int APS5_VABI posix_spawnattr_init_nid_postfix(PosixSpawnAttributes**);
int APS5_VABI posix_spawnattr_destroy_nid_postfix(PosixSpawnAttributes**);
int APS5_VABI posix_spawnattr_setflags_nid_postfix(PosixSpawnAttributes**, std::int16_t);
int APS5_VABI posix_spawnattr_setsigdefault_nid_postfix(PosixSpawnAttributes**, const GuestSignalSet*);
int APS5_VABI posix_spawn_file_actions_init_nid_postfix(PosixSpawnFileActions**);
int APS5_VABI posix_spawn_file_actions_destroy_nid_postfix(PosixSpawnFileActions**);
int APS5_VABI posix_spawn_file_actions_addopen_nid_postfix(PosixSpawnFileActions**, int, const char*, int, std::uint16_t);
int APS5_VABI posix_spawn_file_actions_adddup2_nid_postfix(PosixSpawnFileActions**, int, int);
int APS5_VABI posix_spawn_file_actions_addclose_nid_postfix(PosixSpawnFileActions**, int);
int APS5_VABI posix_spawn_nid_postfix(std::int32_t*, const char*, PosixSpawnFileActions* const*, PosixSpawnAttributes* const*, char* const*, char* const*);
int APS5_VABI posix_spawnp_nid_postfix(std::int32_t*, const char*, PosixSpawnFileActions* const*, PosixSpawnAttributes* const*, char* const*, char* const*);
}
static void Require(bool condition, int line) {
if (!condition) {
std::fprintf(stderr, "Posix spawn check failed at line %d\n", line);
std::abort();
}
}
#define Check(value) Require((value), __LINE__)
constexpr int BadDescriptor = 9;
template<typename TCall>
static bool ThrowsInvalidArgument(TCall call) {
try {
call();
} catch (const std::invalid_argument&) {
return true;
}
return false;
}
template<typename TCall>
static bool ReportsNotImplemented(TCall call, const std::string& message) {
try {
call();
} catch (const std::runtime_error& error) {
return std::string(error.what()) == message;
}
return false;
}
static void CheckAttributes() {
PosixSpawnAttributes* attributes = nullptr;
Check(posix_spawnattr_init_nid_postfix(&attributes) == 0 && attributes != nullptr);
Check(attributes->flags == 0 && attributes->processGroup == 0 && attributes->schedulingPriority == 0 && attributes->schedulingPolicy == 0);
const GuestSignalSet empty{};
Check(std::memcmp(&attributes->signalDefault, &empty, sizeof(empty)) == 0 && std::memcmp(&attributes->signalMask, &empty, sizeof(empty)) == 0);
Check(posix_spawnattr_setflags_nid_postfix(&attributes, 0x30) == 0 && attributes->flags == 0x30);
Check(posix_spawnattr_setflags_nid_postfix(&attributes, -1) == 0 && attributes->flags == -1);
Check(posix_spawnattr_setflags_nid_postfix(&attributes, 0) == 0 && attributes->flags == 0);
const GuestSignalSet defaults{{0x00004002u, 0x80000000u, 1u, 0xffffffffu}};
Check(posix_spawnattr_setsigdefault_nid_postfix(&attributes, &defaults) == 0);
Check(std::memcmp(&attributes->signalDefault, &defaults, sizeof(defaults)) == 0);
Check(std::memcmp(&attributes->signalMask, &empty, sizeof(empty)) == 0);
PosixSpawnAttributes* uninitialized = nullptr;
Check(ThrowsInvalidArgument([&] { posix_spawnattr_setflags_nid_postfix(&uninitialized, 1); }));
Check(ThrowsInvalidArgument([&] { posix_spawnattr_setsigdefault_nid_postfix(&uninitialized, &defaults); }));
Check(ThrowsInvalidArgument([&] { posix_spawnattr_setsigdefault_nid_postfix(&attributes, nullptr); }));
Check(ThrowsInvalidArgument([] { posix_spawnattr_init_nid_postfix(nullptr); }));
Check(posix_spawnattr_destroy_nid_postfix(&attributes) == 0);
Check(posix_spawnattr_destroy_nid_postfix(&uninitialized) == 0);
}
static void CheckFileActions() {
PosixSpawnFileActions* actions = nullptr;
Check(posix_spawn_file_actions_init_nid_postfix(&actions) == 0 && actions != nullptr && actions->actions.empty());
char path[] = "/app0/log.txt";
Check(posix_spawn_file_actions_addopen_nid_postfix(&actions, 3, path, 0x601, 0644) == 0);
std::strcpy(path, "/changed");
Check(posix_spawn_file_actions_adddup2_nid_postfix(&actions, 3, 1) == 0);
Check(posix_spawn_file_actions_addclose_nid_postfix(&actions, 3) == 0);
Check(posix_spawn_file_actions_adddup2_nid_postfix(&actions, 0, 0) == 0);
Check(posix_spawn_file_actions_addopen_nid_postfix(&actions, -1, "/app0/other", 0, 0) == BadDescriptor);
Check(posix_spawn_file_actions_adddup2_nid_postfix(&actions, -1, 1) == BadDescriptor);
Check(posix_spawn_file_actions_adddup2_nid_postfix(&actions, 1, -1) == BadDescriptor);
Check(posix_spawn_file_actions_addclose_nid_postfix(&actions, -2) == BadDescriptor);
const auto& recorded = actions->actions;
Check(recorded.size() == 4);
Check(recorded[0].kind == PosixSpawnFileActionKind::Open && recorded[0].descriptor == 3);
Check(recorded[0].path == "/app0/log.txt" && recorded[0].openFlags == 0x601 && recorded[0].mode == 0644);
Check(recorded[1].kind == PosixSpawnFileActionKind::Duplicate && recorded[1].descriptor == 3 && recorded[1].newDescriptor == 1);
Check(recorded[2].kind == PosixSpawnFileActionKind::Close && recorded[2].descriptor == 3);
Check(recorded[3].kind == PosixSpawnFileActionKind::Duplicate && recorded[3].descriptor == 0 && recorded[3].newDescriptor == 0);
PosixSpawnFileActions* uninitialized = nullptr;
Check(posix_spawn_file_actions_addclose_nid_postfix(&uninitialized, -1) == BadDescriptor);
Check(ThrowsInvalidArgument([&] { posix_spawn_file_actions_addclose_nid_postfix(&uninitialized, 1); }));
Check(ThrowsInvalidArgument([&] { posix_spawn_file_actions_adddup2_nid_postfix(&uninitialized, 1, 2); }));
Check(ThrowsInvalidArgument([&] { posix_spawn_file_actions_addopen_nid_postfix(&uninitialized, 1, "/app0/a", 0, 0); }));
Check(ThrowsInvalidArgument([&] { posix_spawn_file_actions_addopen_nid_postfix(&actions, 1, nullptr, 0, 0); }));
Check(ThrowsInvalidArgument([&] { posix_spawn_file_actions_destroy_nid_postfix(&uninitialized); }));
Check(ThrowsInvalidArgument([] { posix_spawn_file_actions_init_nid_postfix(nullptr); }));
Check(actions->actions.size() == 4);
Check(posix_spawn_file_actions_destroy_nid_postfix(&actions) == 0);
}
static void CheckSpawn() {
PosixSpawnAttributes* attributes = nullptr;
PosixSpawnFileActions* actions = nullptr;
Check(posix_spawnattr_init_nid_postfix(&attributes) == 0 && posix_spawn_file_actions_init_nid_postfix(&actions) == 0);
char program[] = "/app0/tool.elf";
char* arguments[] = {program, nullptr};
char* environment[] = {nullptr};
std::int32_t processId = 77;
Check(ReportsNotImplemented([&] { posix_spawn_nid_postfix(&processId, program, &actions, &attributes, arguments, environment); },
"posix_spawn: process creation not implemented"));
Check(ReportsNotImplemented([&] { posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr); },
"posix_spawnp: process creation not implemented"));
Check(processId == 77);
Check(posix_spawn_file_actions_destroy_nid_postfix(&actions) == 0 && posix_spawnattr_destroy_nid_postfix(&attributes) == 0);
}
int main() {
CheckAttributes();
CheckFileActions();
CheckSpawn();
}

View File

@ -12,4 +12,5 @@
- [Thread exit destructors](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) run when a libkernel thread finishes; on the main or a host thread only `_sceLibcInternalThreadDtors` runs them, as `exit` does, else they are dropped
- [fdopendir](../../../core/libs/prx/libSceLibcInternal/Directory.cpp) (libSceLibcInternal) - the directory stream starts at the directory's first entry, while FreeBSD's [`fdopendir`](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/lib/libc/gen/opendir.c#L67-L74) reads from the descriptor's current offset, and `FD_CLOEXEC` is not set on the descriptor. As in FreeBSD, a descriptor that is not open fails with `EBADF`, one that is not a directory with `ENOTDIR` (both leave it open), and `closedir` closes it
- [fstatvfs](../../../core/libs/prx/libSceLibcInternal/FilesystemStatistics.cpp) (libSceLibcInternal) converts `_fstatfs` to the 88-byte `struct statvfs` as FreeBSD 9.2's `sfs2svfs` does (`f_bsize` from the transfer size, `f_frsize` from the block size, `f_favail` equal to `f_ffree`, `f_fsid` 0, `ST_RDONLY` and `ST_NOSUID` only). Differences: `f_namemax` is the `statfs` value, not `fpathconf(_PC_NAME_MAX)`; negative `f_bavail` and `f_ffree` wrap to unsigned without `EOVERFLOW`, as the same-width copy in FreeBSD does; inode counts are 0 on Windows, inherited from `_fstatfs`; a null output returns `EFAULT` after the descriptor is validated, where FreeBSD would fault. Layout and conversion are unverified against the PS5 library (PS5MC imports `fstatvfs` but was not run)
- [posix_spawn and posix_spawnp](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) throw: creating a child process is not supported. PPSA99305 (Dusklight) and PPSA99309 (Wind Waker) stop on `main` at unresolved import `44hlATrd47U` (`posix_spawnattr_init`); a run with this PR has not established how far either title gets, and process creation still throws. The attribute and file action calls keep FreeBSD 11.4's objects, so a real `posix_spawn` would have to apply them in the child. Null or uninitialized objects, which FreeBSD dereferences, throw.
- [getopt, getopt_long](../../../core/libs/prx/libSceLibcInternal/Getopt.cpp) (libSceLibcInternal) follow FreeBSD 11 [getopt.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt.c) and [getopt_long.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdlib/getopt_long.c) with `GNU_COMPATIBLE`: `getopt` does not permute, `getopt_long` permutes unless `POSIXLY_CORRECT` is set in the guest environment or the option string starts with `+`, and the exported `opterr`, `optind`, `optopt`, `optreset` and `optarg` start at 1, 1, 0, 0 and null. Warnings go to the guest's current `stderr` prefixed with `__progname`, where FreeBSD's `warnx` keeps the `stderr` of its first call. A null `getopt` option string, a null argument vector or argument, a negative `optind` and an `optind` moved inside a pending permutation throw where FreeBSD faults. `getopt_long_only` is not exported. That the console's `libSceLibcInternal` uses this FreeBSD code is unverified