feat(libc): implement syslog and vsyslog

ps5-opengl (PPSA99005) and ProsperoRadio (PPSA99001) import syslog
from libSceLibcInternal. Messages go to stderr with the priority, and
the format follows FreeBSD's vsyslog: %m expansion, masking of unknown
priority bits, LOG_USER as the default facility, errno preserved.

Assisted by Claude Code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
john.maia 2026-10-08 18:43:18 +01:00 committed by Adrià Franch
parent d724cad38c
commit bcd1b0c2a0
4 changed files with 169 additions and 0 deletions

View File

@ -33,6 +33,7 @@ target_sources(libc PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/Directory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Filesystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Errors.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Syslog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp
)
@ -233,6 +234,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_errors PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_syslog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyslog.cpp)
target_include_directories(guest_syslog_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_syslog_tests PRIVATE libc)
configure_windows_unwind(guest_syslog_tests)
add_test(NAME guest_syslog COMMAND guest_syslog_tests)
if(WIN32)
set_tests_properties(guest_syslog PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(mspace_tests ${LIBS_INCLUDE_DIR}/tests/Mspace.cpp)
target_include_directories(mspace_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(mspace_tests PRIVATE libc)

View File

@ -0,0 +1,107 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "SceTypes.hpp"
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <mutex>
#include <string>
#ifdef _WIN32
#include "prx/libc/include/WindowsFormatting.hpp"
#endif
extern "C" {
int* APS5_VABI __error_nid_postfix();
int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length);
}
namespace {
constexpr int priorityMask = 0x07;
constexpr int facilityMask = 0x3f8;
constexpr int userFacility = 1 << 3;
constexpr int kernelFacility = 0;
constexpr int errorPriority = 3;
std::mutex& OutputMutex() {
static std::mutex mutex;
return mutex;
}
void Emit(int priority, const std::string& message) {
std::lock_guard lock(OutputMutex());
std::fprintf(stderr, "[syslog:%d] %s", priority, message.c_str());
if (message.empty() || message.back() != '\n') std::fputc('\n', stderr);
std::fflush(stderr);
}
std::string ExpandErrorText(const char* format, int error) {
std::string expanded;
for (const char* cursor = format; *cursor; ++cursor) {
if (cursor[0] == '%' && cursor[1] == 'm') {
char text[128];
strerror_r_nid_postfix(error, text, sizeof(text));
for (const char* character = text; *character; ++character) {
expanded += *character;
if (*character == '%') expanded += '%';
}
++cursor;
} else if (cursor[0] == '%' && cursor[1] == '%') {
expanded += "%%";
++cursor;
} else {
expanded += *cursor;
}
}
return expanded;
}
std::string Format(const char* format, VaList* args) {
#ifdef _WIN32
std::string buffer;
LibcDetail::FormatWindows(nullptr, 0, format, args, &buffer);
return buffer;
#else
std::va_list copy;
va_copy(copy, *reinterpret_cast<std::va_list*>(args));
const int size = std::vsnprintf(nullptr, 0, format, copy);
va_end(copy);
if (size <= 0) return {};
std::string buffer(static_cast<std::size_t>(size) + 1, '\0');
va_copy(copy, *reinterpret_cast<std::va_list*>(args));
std::vsnprintf(buffer.data(), buffer.size(), format, copy);
va_end(copy);
buffer.resize(static_cast<std::size_t>(size));
return buffer;
#endif
}
}
extern "C" {
void APS5_VABI vsyslog_nid_postfix(int priority, const char* format, VaList* args) {
const int saved = *__error_nid_postfix();
if (priority & ~(priorityMask | facilityMask)) {
char complaint[64];
std::snprintf(complaint, sizeof(complaint), "syslog: unknown facility/priority: %x", priority);
Emit(kernelFacility | errorPriority, complaint);
priority &= priorityMask | facilityMask;
}
if ((priority & facilityMask) == 0) priority |= userFacility;
Emit(priority, format ? Format(ExpandErrorText(format, saved).c_str(), args) : std::string());
*__error_nid_postfix() = saved;
}
void APS5_VABI syslog_nid_postfix(int priority, const char* format, ...) {
#ifdef _WIN32
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
#else
std::va_list args;
va_start(args, format);
#endif
vsyslog_nid_postfix(priority, format, reinterpret_cast<VaList*>(args));
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
}
}

View File

@ -0,0 +1,51 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
extern "C" {
void APS5_VABI syslog_nid_postfix(int, const char*, ...);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
auto* captured = std::tmpfile();
Require(captured != nullptr);
std::fflush(stderr);
#ifdef _WIN32
const int saved = ::_dup(::_fileno(stderr));
Require(saved >= 0 && ::_dup2(::_fileno(captured), ::_fileno(stderr)) == 0);
#else
const int saved = ::dup(::fileno(stderr));
Require(saved >= 0 && ::dup2(::fileno(captured), ::fileno(stderr)) == ::fileno(stderr));
#endif
*__error_nid_postfix() = 2;
syslog_nid_postfix(6, "station %s at %d kbps", "radio", 128);
const int afterFirst = *__error_nid_postfix();
syslog_nid_postfix(16 | 3, "open: %m, %%m kept, %d%%\n", 50);
syslog_nid_postfix(0x10000 | 4, "masked");
syslog_nid_postfix(7, "");
std::fflush(stderr);
#ifdef _WIN32
Require(::_dup2(saved, ::_fileno(stderr)) == 0 && ::_close(saved) == 0);
#else
Require(::dup2(saved, ::fileno(stderr)) == ::fileno(stderr) && ::close(saved) == 0);
#endif
Require(afterFirst == 2 && *__error_nid_postfix() == 2);
std::rewind(captured);
char text[512]{};
const auto length = std::fread(text, 1, sizeof(text) - 1, captured);
const char* expected =
"[syslog:14] station radio at 128 kbps\n"
"[syslog:19] open: No such file or directory, %m kept, 50%\n"
"[syslog:3] syslog: unknown facility/priority: 10004\n"
"[syslog:12] masked\n"
"[syslog:15] \n";
Require(length == std::strlen(expected) && std::strcmp(text, expected) == 0);
Require(std::fclose(captured) == 0);
}

View File

@ -358,3 +358,4 @@ Throughout the project, every function at every stage either **does exactly what
- [Static scalar calls](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) (`s_call_b64` and `s_swappc_b64`) require one call per ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and one later `s_setpc_b64` return reading that exact pair. Regions must nest or be disjoint; recursion, shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic `s_setpc_b64` in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented.
- [s_call_b64](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Native RDNA2/PS5 execution and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation.
- [sceVideodec2CreateDecoder](../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) decodes HEVC (codec type 974921) and VP9 (2382845) through FFmpeg, besides H.264. The tags come from SharpProspero's `Videodec2CodecType` and EVO-PLAYER-PS5's `sce_videodec2.h`, which records them from hardware runs. HEVC input is converted from length-prefixed NAL units to Annex B as for H.264, and VP9 frames are passed through. Output is the same 8-bit NV12 for every codec; a decoder for HEVC Main10 or VP9 Profile 2 is created, but a frame deeper than 8 bits throws, since the console's 10-bit output layout is unknown, and `QueryDecoderMemoryInfo` reports the 8-bit frame size for it. `sceVideodec2GetPictureInfo` and `sceVideodec2GetAvcPictureInfo` throw for HEVC and VP9 pictures, which use other layouts (`SceVideodec2HevcPictureInfo`, `SceVideodec2Vp9PictureInfo`), and `sceVideodec2GetHevcPictureInfo` and `sceVideodec2GetVp9PictureInfo` are not exported
- [syslog](../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:<priority>] <message>`, with a newline added when missing, instead of sending it to the console's system log. Priority and facility handling follow FreeBSD's `vsyslog`: `%m` expands to the guest errno's message, unknown bits are reported and masked, a missing facility becomes `LOG_USER`, and errno is preserved. `openlog`, `setlogmask` and `closelog` are not implemented, so the identity, options and mask they set are not applied