#include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include #include #include #include #ifdef _WIN32 #include #else #include #endif using ErrExit = void (APS5_VABI*)(int); extern "C" { void APS5_VABI err_set_file_nid_postfix(FileStream* stream); void APS5_VABI err_set_exit_nid_postfix(ErrExit function); void APS5_VABI warn_nid_postfix(const char* format, ...); void APS5_VABI warnc_nid_postfix(int code, const char* format, ...); void APS5_VABI warnx_nid_postfix(const char* format, ...); [[noreturn]] void APS5_VABI err_nid_postfix(int status, const char* format, ...); [[noreturn]] void APS5_VABI errc_nid_postfix(int status, int code, const char* format, ...); [[noreturn]] void APS5_VABI errx_nid_postfix(int status, const char* format, ...); void APS5_VABI vwarn_nid_postfix(const char* format, VaList* args); void APS5_VABI vwarnc_nid_postfix(int code, const char* format, VaList* args); void APS5_VABI vwarnx_nid_postfix(const char* format, VaList* args); [[noreturn]] void APS5_VABI verr_nid_postfix(int status, const char* format, VaList* args); [[noreturn]] void APS5_VABI verrc_nid_postfix(int status, int code, const char* format, VaList* args); [[noreturn]] void APS5_VABI verrx_nid_postfix(int status, const char* format, VaList* args); char* APS5_VABI strerror_nid_postfix(int error); int* APS5_VABI __error_nid_postfix(); extern const char* __progname_nid_postfix; void APS5_VABI psignal_nid_postfix(unsigned int signal, const char* prefix); extern const char* const sys_signame_nid_postfix[32]; extern const char* const sys_siglist_nid_postfix[32]; } namespace { struct ExitRequested { int status; }; int g_hookStatus = -1; void APS5_VABI Hook(int status) { g_hookStatus = status; throw ExitRequested{status}; } void Require(bool value, const char* what) { if (value) return; std::fprintf(stderr, "failed: %s\n", what); std::abort(); } class Capture { FileStream stream; long consumed = 0; public: Capture() : stream(std::tmpfile()) { err_set_file_nid_postfix(&stream); } ~Capture() { err_set_file_nid_postfix(nullptr); stream.Close(); } std::string Take() { std::FILE* handle = stream.GetHandle(); std::fflush(handle); const long end = std::ftell(handle); std::fseek(handle, consumed, SEEK_SET); std::string text; for (int character; (character = std::fgetc(handle)) != EOF;) text.push_back(static_cast(character)); consumed = end; std::fseek(handle, end, SEEK_SET); return text; } }; template std::string DescriptorTwoOutput(TCall call) { std::fflush(stderr); std::FILE* capture = std::tmpfile(); const int saved = dup(2); dup2(fileno(capture), 2); call(); dup2(saved, 2); close(saved); std::rewind(capture); std::string text; for (int character; (character = std::fgetc(capture)) != EOF;) text.push_back(static_cast(character)); std::fclose(capture); return text; } template int ExitStatus(TCall call) { try { call(); } catch (const ExitRequested& request) { return request.status; } return -1; } enum class Variant { Warn, Warnc, Warnx, Err, Errc, Errx, WarnxSkipping }; void APS5_VABI CallVariant(Variant variant, int status, int code, const char* format, ...) { #ifdef _WIN32 __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); if (variant == Variant::WarnxSkipping) (void)__builtin_va_arg(args, int); #else std::va_list args; va_start(args, format); if (variant == Variant::WarnxSkipping) (void)va_arg(args, int); #endif auto* list = reinterpret_cast(args); switch (variant) { case Variant::Warn: vwarn_nid_postfix(format, list); break; case Variant::Warnc: vwarnc_nid_postfix(code, format, list); break; case Variant::Warnx: case Variant::WarnxSkipping: vwarnx_nid_postfix(format, list); break; case Variant::Err: verr_nid_postfix(status, format, list); case Variant::Errc: verrc_nid_postfix(status, code, format, list); case Variant::Errx: verrx_nid_postfix(status, format, list); } #ifdef _WIN32 __builtin_sysv_va_end(args); #else va_end(args); #endif } } int main() { const char* const savedName = __progname_nid_postfix; __progname_nid_postfix = "prog"; const std::string noEntry = strerror_nid_postfix(2); const std::string denied = strerror_nid_postfix(13); { Capture capture; *__error_nid_postfix() = 2; warn_nid_postfix("open %s", "a.txt"); Require(capture.Take() == "prog: open a.txt: " + noEntry + "\n", "warn"); *__error_nid_postfix() = 2; warn_nid_postfix(nullptr); Require(capture.Take() == "prog: " + noEntry + "\n", "warn without format"); warnc_nid_postfix(13, "%d-%s", 7, "x"); Require(capture.Take() == "prog: 7-x: " + denied + "\n", "warnc"); warnx_nid_postfix("plain %c", 'q'); Require(capture.Take() == "prog: plain q\n", "warnx"); warnx_nid_postfix(nullptr); Require(capture.Take() == "prog: \n", "warnx without format"); err_set_exit_nid_postfix(Hook); Require(ExitStatus([] { errx_nid_postfix(3, "bad %d", 1); }) == 3 && g_hookStatus == 3, "errx status"); Require(capture.Take() == "prog: bad 1\n", "errx text"); Require(ExitStatus([] { errc_nid_postfix(4, 13, "c"); }) == 4, "errc status"); Require(capture.Take() == "prog: c: " + denied + "\n", "errc text"); *__error_nid_postfix() = 2; Require(ExitStatus([] { err_nid_postfix(5, "e"); }) == 5, "err status"); Require(capture.Take() == "prog: e: " + noEntry + "\n", "err text"); *__error_nid_postfix() = 2; CallVariant(Variant::Warn, 0, 0, "open %s", "a.txt"); Require(capture.Take() == "prog: open a.txt: " + noEntry + "\n", "vwarn"); *__error_nid_postfix() = 2; CallVariant(Variant::Warnc, 0, 13, "%d-%s", 7, "x"); Require(capture.Take() == "prog: 7-x: " + denied + "\n", "vwarnc uses its code"); CallVariant(Variant::Warnc, 0, 13, nullptr); Require(capture.Take() == "prog: " + denied + "\n", "vwarnc without format"); CallVariant(Variant::Warnx, 0, 0, "%d %d %d %d %d %d %d %d %.2f %s %ld", 1, 2, 3, 4, 5, 6, 7, 8, 2.5, "s", 123456789L); Require(capture.Take() == "prog: 1 2 3 4 5 6 7 8 2.50 s 123456789\n", "vwarnx stack and floating arguments"); CallVariant(Variant::WarnxSkipping, 0, 0, "after %d", 1, 2); Require(capture.Take() == "prog: after 2\n", "vwarnx continues an advanced list"); *__error_nid_postfix() = 16; Require(ExitStatus([] { CallVariant(Variant::Err, 6, 0, nullptr); }) == 6, "verr status"); Require(capture.Take() == "prog: " + std::string(strerror_nid_postfix(16)) + "\n", "verr without format"); *__error_nid_postfix() = 2; Require(ExitStatus([] { CallVariant(Variant::Errc, 4, 13, "c%s", "!"); }) == 4, "verrc status"); Require(capture.Take() == "prog: c!: " + denied + "\n", "verrc text"); Require(ExitStatus([] { CallVariant(Variant::Errx, 0, 0, nullptr); }) == 0, "verrx status 0"); Require(capture.Take() == "prog: \n", "verrx without format"); err_set_exit_nid_postfix(nullptr); } __progname_nid_postfix = savedName; Require(std::string(sys_signame_nid_postfix[0]) == "Signal 0" && std::string(sys_signame_nid_postfix[15]) == "TERM", "sys_signame"); Require(std::string(sys_signame_nid_postfix[26]) == "VTALRM" && std::string(sys_signame_nid_postfix[31]) == "USR2", "sys_signame tail"); Require(DescriptorTwoOutput([] { psignal_nid_postfix(2, "tag"); }) == std::string("tag: ") + sys_siglist_nid_postfix[2] + "\n", "psignal with prefix"); Require(DescriptorTwoOutput([] { psignal_nid_postfix(11, ""); }) == std::string(sys_siglist_nid_postfix[11]) + "\n", "psignal empty prefix"); Require(DescriptorTwoOutput([] { psignal_nid_postfix(32, nullptr); }) == "Unknown signal\n", "psignal unknown signal"); return 0; }