Files
AnyPS5/core/libs/tests/GuestHostStandardError.cpp
Maz fb0b5ca919 fix(libc): send perror through the guest stderr stream
After a guest dup2 onto descriptor 2, host stderr is a separate FILE, so
perror must use the guest stream like the other stdio writers.
2026-10-20 13:38:07 +04:00

175 lines
6.8 KiB
C++

#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <string>
#include <sys/wait.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/prctl.h>
#endif
extern "C" {
FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*);
int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t);
std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*);
int APS5_VABI fflush_nid_postfix(FileStream*);
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI dup2_nid_postfix(int, int);
std::int64_t APS5_VABI write_nid_postfix(int, const void*, std::size_t);
int APS5_VABI printf_nid_postfix(const char*, ...);
int APS5_VABI puts_nid_postfix(const char*);
int APS5_VABI fprintf_nid_postfix(FileStream*, const char*, ...);
void APS5_VABI perror_nid_postfix(const char*);
int* APS5_VABI __error_nid_postfix();
extern FileStream* __stderrp_nid_postfix;
extern FileStream* __stdoutp_nid_postfix;
}
static void Require(bool value) { if (!value) std::abort(); }
static void SkipCrashHandler() {
#ifdef __linux__
Require(::prctl(PR_SET_DUMPABLE, 0) == 0);
#endif
}
enum class Redirect { Duplicate, Reopen };
struct Outcome {
std::string host;
int status = 0;
};
static void RedirectGuest(Redirect redirect, const std::filesystem::path& probe) {
if (redirect == Redirect::Duplicate) {
int guest[2] = {-1, -1};
Require(pipe_nid_postfix(guest) == 0);
Require(dup2_nid_postfix(guest[1], 2) == 2);
Require(write_nid_postfix(2, "guest", 5) == 5);
return;
}
Require(freopen_nid_postfix(probe.c_str(), "w", __stderrp_nid_postfix) == __stderrp_nid_postfix);
Require(setvbuf_nid_postfix(__stderrp_nid_postfix, nullptr, 0, 0) == 0);
Require(fwrite_nid_postfix("guest", 1, 5, __stderrp_nid_postfix) == 5);
}
static Outcome Run(Redirect redirect, const std::filesystem::path& probe, bool terminate) {
int host[2] = {-1, -1};
Require(::pipe(host) == 0);
const pid_t child = ::fork();
Require(child >= 0);
if (child == 0) {
::close(host[0]);
if (::dup2(host[1], 2) != 2) ::_exit(70);
::close(host[1]);
RedirectGuest(redirect, probe);
std::fputs("host\n", stderr);
if (terminate) {
SkipCrashHandler();
throw std::runtime_error("report kept");
}
if (redirect == Redirect::Reopen && fflush_nid_postfix(__stderrp_nid_postfix) != 0) ::_exit(71);
::_exit(0);
}
::close(host[1]);
Outcome outcome;
char buffer[4096];
for (ssize_t count; (count = ::read(host[0], buffer, sizeof(buffer))) > 0;) outcome.host.append(buffer, static_cast<std::size_t>(count));
::close(host[0]);
Require(::waitpid(child, &outcome.status, 0) == child);
return outcome;
}
static std::string Contents(const std::filesystem::path& path) {
std::ifstream stream(path, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
}
int main() {
const std::filesystem::path probe = "host_stderr_probe_" + std::to_string(::getpid()) + ".txt";
for (const auto redirect : {Redirect::Duplicate, Redirect::Reopen}) {
const auto written = Run(redirect, probe, false);
Require(WIFEXITED(written.status) && WEXITSTATUS(written.status) == 0);
Require(written.host == "host\n");
if (redirect == Redirect::Reopen) Require(Contents(probe) == "guest");
const auto terminated = Run(redirect, probe, true);
Require(WIFSIGNALED(terminated.status) && WTERMSIG(terminated.status) == SIGABRT);
Require(terminated.host.rfind("host\n", 0) == 0);
Require(terminated.host.find(" what(): report kept\n") != std::string::npos);
if (redirect == Redirect::Reopen) Require(Contents(probe).find("report kept") == std::string::npos);
}
std::filesystem::remove(probe);
int host[2] = {-1, -1};
Require(::pipe(host) == 0);
const pid_t child = ::fork();
Require(child >= 0);
if (child == 0) {
::close(host[0]);
if (::dup2(host[1], 1) != 1) ::_exit(70);
::close(host[1]);
int guest[2] = {-1, -1};
if (pipe_nid_postfix(guest) != 0 || dup2_nid_postfix(guest[1], 1) != 1) ::_exit(72);
std::fputs("host\n", stdout);
std::fflush(stdout);
::_exit(0);
}
::close(host[1]);
std::string output;
char buffer[64];
for (ssize_t count; (count = ::read(host[0], buffer, sizeof(buffer))) > 0;) output.append(buffer, static_cast<std::size_t>(count));
::close(host[0]);
int status = 0;
Require(::waitpid(child, &status, 0) == child);
Require(WIFEXITED(status) && WEXITSTATUS(status) == 0);
Require(output == "host\n");
for (const int standard : {1, 2}) {
int host[2] = {-1, -1};
Require(::pipe(host) == 0);
const pid_t child = ::fork();
Require(child >= 0);
if (child == 0) {
::close(host[0]);
int guest[2] = {-1, -1};
if (pipe_nid_postfix(guest) != 0 || dup2_nid_postfix(guest[1], standard) != standard) ::_exit(72);
const char* expected = "guest-fprintf\n";
if (standard == 1) {
if (printf_nid_postfix("guest-printf\n") < 0 || puts_nid_postfix("guest-puts") < 0 || fflush_nid_postfix(__stdoutp_nid_postfix) != 0) ::_exit(73);
expected = "guest-printf\nguest-puts\n";
} else {
if (fprintf_nid_postfix(__stderrp_nid_postfix, "guest-fprintf\n") < 0) ::_exit(73);
*__error_nid_postfix() = 2;
perror_nid_postfix("guest-perror");
if (fflush_nid_postfix(__stderrp_nid_postfix) != 0) ::_exit(73);
expected = "guest-fprintf\nguest-perror: No such file or directory\n";
}
std::string got;
char buffer[64];
const auto length = std::char_traits<char>::length(expected);
for (ssize_t count; got.size() < length && (count = ::read(guest[0], buffer, sizeof(buffer))) > 0;) got.append(buffer, static_cast<std::size_t>(count));
if (got != expected) ::_exit(75);
if (::write(host[1], "ok", 2) != 2) ::_exit(76);
::_exit(0);
}
::close(host[1]);
std::string marker;
char buffer[8];
for (ssize_t count; (count = ::read(host[0], buffer, sizeof(buffer))) > 0;) marker.append(buffer, static_cast<std::size_t>(count));
::close(host[0]);
int status = 0;
Require(::waitpid(child, &status, 0) == child);
Require(WIFEXITED(status) && WEXITSTATUS(status) == 0);
Require(marker == "ok");
}
return 0;
}