Files
AnyPS5/core/libs/tests/GuestHostStandardError.cpp
T
Hashaam ZafarandClaude Opus 5.5 42191c0807 test(libc): keep intentional aborts away from the system crash handler
guest_host_standard_error and exception_runtime fork children that end in
SIGABRT on purpose. When kernel.core_pattern pipes cores to a handler, as
Ubuntu's apport and systemd-coredump do, the kernel starts it for every
dumpable crash even with RLIMIT_CORE at 0. apport takes about 1.1 s per
crash here, so guest_host_standard_error spent 2.3 s waiting and timed out
at its 10 s limit under parallel ctest load. Marking those children
non-dumpable on Linux skips the handler: 2.29 s to 0.01 s and
1.17 s to 0.01 s, with the same SIGABRT and message checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:26:53 -07:00

106 lines
3.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);
extern FileStream* __stderrp_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);
return 0;
}