Merge pull request #6233 from milescoviello/fix/spawn-missing-program

fix(libSceLibcInternal): fail posix_spawn with ENOENT when the program is not there
This commit is contained in:
Adrià authored and GitHub committed 2026-10-18 22:37:14 +02:00
commit 6e27fc8f07
3 files changed
+95 -13

No files matched your search

+40 -10
View File
@@ -1,15 +1,23 @@
#include "prx/libSceLibcInternal/include/Spawn.hpp"
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libc/include/General.hpp"
#include <cstdint>
#include <filesystem>
#include <new>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
extern "C" char* APS5_VABI getenv_nid_postfix(const char* name);
namespace {
constexpr int NoSuchFile = 2;
constexpr int BadDescriptor = 9;
constexpr int OutOfMemory = 12;
constexpr std::string_view DefaultSearchPath = "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin";
PosixSpawnAttributes& Attributes(PosixSpawnAttributes* const* attributes, const char* function) {
if (attributes == nullptr || *attributes == nullptr) throw std::invalid_argument(std::string(function) + ": uninitialized attributes");
@@ -21,6 +29,36 @@ PosixSpawnFileActions& FileActions(PosixSpawnFileActions* const* actions, const
return **actions;
}
bool GuestPathExists(const std::string& path) {
std::filesystem::path native;
if (ResolvePathError_nid_no_patch(path.c_str(), native) != 0) return false;
std::error_code error;
return std::filesystem::exists(native, error);
}
bool ProgramFound(std::string_view name, bool searchesPath) {
if (name.empty()) return false;
if (!searchesPath || name.find('/') != std::string_view::npos) return GuestPathExists(std::string(name));
const char* variable = getenv_nid_postfix("PATH");
std::string_view directories = variable != nullptr ? std::string_view(variable) : DefaultSearchPath;
while (true) {
const auto separator = directories.find(':');
const auto directory = directories.substr(0, separator);
if (GuestPathExists((directory.empty() ? std::string(".") : std::string(directory)) + '/' + std::string(name))) return true;
if (separator == std::string_view::npos) return false;
directories.remove_prefix(separator + 1);
}
}
int Spawn(const char* function, const char* file, bool searchesPath, PosixSpawnFileActions* const* actions, PosixSpawnAttributes* const* attributes) {
if (file == nullptr) throw std::invalid_argument(std::string(function) + ": null path");
if (actions != nullptr) FileActions(actions, function);
if (attributes != nullptr) Attributes(attributes, function);
if (!ProgramFound(file, searchesPath)) return NoSuchFile;
NotImplemented_nid_no_patch((std::string(function) + ": process creation").c_str());
return -1;
}
int Append(PosixSpawnFileActions& actions, PosixSpawnFileAction action) {
try {
actions.actions.push_back(std::move(action));
@@ -162,24 +200,16 @@ int APS5_VABI posix_spawn_file_actions_addclose_nid_postfix(PosixSpawnFileAction
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;
return Spawn("posix_spawn", path, false, actions, attributes);
}
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;
return Spawn("posix_spawnp", file, true, actions, attributes);
}
}
+54 -2
View File
@@ -1,9 +1,12 @@
#include "prx/libSceLibcInternal/include/Spawn.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include <string>
@@ -29,6 +32,9 @@ int APS5_VABI posix_spawn_file_actions_adddup2_nid_postfix(PosixSpawnFileActions
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*);
int APS5_VABI setenv_nid_postfix(const char*, const char*, int);
int APS5_VABI unsetenv_nid_postfix(const char*);
int APS5_VABI chdir_nid_postfix(const char*);
}
static void Require(bool condition, int line) {
@@ -39,6 +45,7 @@ static void Require(bool condition, int line) {
}
#define Check(value) Require((value), __LINE__)
constexpr int NoSuchFile = 2;
constexpr int BadDescriptor = 9;
template<typename TCall>
@@ -148,19 +155,64 @@ static void CheckFileActions() {
}
static void CheckSpawn() {
const auto name = "anyps5-spawn-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
const auto directory = std::filesystem::current_path() / name;
Check(std::filesystem::create_directories(directory / "empty"));
{ std::ofstream file(directory / "tool.elf"); file << "tool"; }
const std::string guest = "/" + name;
const std::string existing = guest + "/tool.elf";
const std::string missing = guest + "/missing.elf";
const std::string bothDirectories = guest + "/empty:" + guest;
const std::string emptyOnly = guest + "/empty";
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";
Check(posix_spawn_file_actions_addclose_nid_postfix(&actions, 3) == 0);
char program[] = "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); },
Check(ReportsNotImplemented([&] { posix_spawn_nid_postfix(&processId, existing.c_str(), &actions, &attributes, arguments, environment); },
"posix_spawn: process creation not implemented"));
Check(posix_spawn_nid_postfix(&processId, missing.c_str(), &actions, &attributes, arguments, environment) == NoSuchFile);
Check(posix_spawn_nid_postfix(&processId, "", nullptr, nullptr, arguments, nullptr) == NoSuchFile);
Check(posix_spawn_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr) == NoSuchFile);
Check(setenv_nid_postfix("PATH", bothDirectories.c_str(), 1) == 0);
Check(ReportsNotImplemented([&] { posix_spawnp_nid_postfix(&processId, "tool.elf", &actions, &attributes, arguments, nullptr); },
"posix_spawnp: process creation not implemented"));
Check(posix_spawnp_nid_postfix(&processId, "missing.elf", &actions, &attributes, arguments, nullptr) == NoSuchFile);
Check(posix_spawnp_nid_postfix(&processId, "", nullptr, nullptr, arguments, nullptr) == NoSuchFile);
Check(setenv_nid_postfix("PATH", emptyOnly.c_str(), 1) == 0);
Check(posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr) == NoSuchFile);
Check(ReportsNotImplemented([&] { posix_spawnp_nid_postfix(&processId, existing.c_str(), nullptr, nullptr, arguments, nullptr); },
"posix_spawnp: process creation not implemented"));
Check(posix_spawnp_nid_postfix(&processId, missing.c_str(), nullptr, nullptr, arguments, nullptr) == NoSuchFile);
Check(setenv_nid_postfix("PATH", (emptyOnly + ":").c_str(), 1) == 0);
Check(posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr) == NoSuchFile);
Check(chdir_nid_postfix(guest.c_str()) == 0);
Check(ReportsNotImplemented([&] { posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr); },
"posix_spawnp: process creation not implemented"));
Check(ReportsNotImplemented([&] { posix_spawn_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr); },
"posix_spawn: process creation not implemented"));
Check(chdir_nid_postfix("/") == 0);
Check(unsetenv_nid_postfix("PATH") == 0);
Check(posix_spawnp_nid_postfix(&processId, "tool.elf", nullptr, nullptr, arguments, nullptr) == NoSuchFile);
Check(processId == 77);
PosixSpawnAttributes* uninitializedAttributes = nullptr;
PosixSpawnFileActions* uninitializedActions = nullptr;
Check(ThrowsInvalidArgument([&] { posix_spawn_nid_postfix(&processId, missing.c_str(), &uninitializedActions, nullptr, arguments, nullptr); }));
Check(ThrowsInvalidArgument([&] { posix_spawnp_nid_postfix(&processId, "missing.elf", nullptr, &uninitializedAttributes, arguments, nullptr); }));
Check(ThrowsInvalidArgument([&] { posix_spawn_nid_postfix(&processId, nullptr, nullptr, nullptr, arguments, nullptr); }));
Check(ThrowsInvalidArgument([&] { posix_spawnp_nid_postfix(&processId, nullptr, nullptr, nullptr, arguments, nullptr); }));
Check(posix_spawn_file_actions_destroy_nid_postfix(&actions) == 0 && posix_spawnattr_destroy_nid_postfix(&attributes) == 0);
std::filesystem::remove_all(directory);
}
int main() {
+1 -1
View File
@@ -12,7 +12,7 @@
- [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.
- [posix_spawn and posix_spawnp](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) create no process. FreeBSD 11.4 returns the error the child's exec leaves ([`_posix_spawn_thr`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/posix_spawn.c#L227-L231), [`do_posix_spawn`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/posix_spawn.c#L295-L303)), and `posix_spawnp` searches `PATH`, or `_PATH_DEFPATH` when it is unset, ending with `ENOENT` when no directory holds the program ([`_execvpe`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/exec.c#L272-L281), [`execvPe`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/exec.c#L257-L260)). So when the program is not in the guest file system both return `ENOENT` and leave the process ID unwritten; when it is there they throw. PPSA99305 (Dusklight) reaches this when SDL looks for `zenity` to show a file dialog. The attributes and file actions are not applied, so one that would fail in the child is not reported, and whether the console's application sandbox lets the child be created at all (FreeBSD returns that error first) is unverified. The attribute and file action calls keep FreeBSD 11.4's objects. 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
- [posix_spawnattr_getflags, getpgroup, getschedparam, getschedpolicy, getsigdefault, getsigmask, setpgroup, setschedparam, setschedpolicy and setsigmask](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) read and write the 0x30-byte attribute object at the offsets of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8D860 to 0x8D910: flags at 0, process group at 4, scheduling parameter at 8, policy at 0xC, default and masked signal sets at 0x10 and 0x20), which never fail there and return 0; the layout matched on a PS5 (firmware 13.60). Uninitialized attributes and null pointers, which the console dereferences, throw as the existing setters do
- [sys_siglist and sys_nsig](../../../core/libs/prx/libSceLibcInternal/SignalList.cpp) (libSceLibcInternal) hold the 32 descriptions and the count of [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/siglist.c#L73-L107) `siglist.c` (`NSIG` 32, signal 0 to `SIGUSR2`); the console's table was not read