From 653c5449f9251460b68c3c370430aaef8e9e4b12 Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Tue, 13 Oct 2026 09:36:22 +0000 Subject: [PATCH] fix(libc): honor openlog identity and facility --- core/libs/prx/libc/src/Syslog.cpp | 34 +++++++++++++++++++++++++++++-- core/libs/tests/GuestSyslog.cpp | 27 +++++++++++++++++++++++- docs/dev/debt/libc.md | 2 +- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libc/src/Syslog.cpp b/core/libs/prx/libc/src/Syslog.cpp index eb5f721e5..a94f34e14 100644 --- a/core/libs/prx/libc/src/Syslog.cpp +++ b/core/libs/prx/libc/src/Syslog.cpp @@ -4,9 +4,13 @@ #include #include #include +#include #include #ifdef _WIN32 #include "prx/libc/include/WindowsFormatting.hpp" +#include +#else +#include #endif extern "C" { @@ -20,7 +24,11 @@ constexpr int priorityMask = 0x07; constexpr int facilityMask = 0x3f8; constexpr int userFacility = 1 << 3; constexpr int internalLogPriority = 3 | 0x02 | 0x20 | 0x01; +constexpr int logPid = 0x01; int logMask = 0xff; +int logOptions = 0; +int logFacility = userFacility; +std::optional logIdentity; std::mutex& OutputMutex() { static std::mutex mutex; @@ -28,7 +36,17 @@ std::mutex& OutputMutex() { } void Emit(int priority, const std::string& message) { - std::fprintf(stderr, "[syslog:%d] %s", priority, message.c_str()); + std::fprintf(stderr, "[syslog:%d] ", priority); + if (logIdentity) std::fputs(logIdentity->c_str(), stderr); + if (logOptions & logPid) { +#ifdef _WIN32 + std::fprintf(stderr, "[%d]", ::_getpid()); +#else + std::fprintf(stderr, "[%d]", static_cast(::getpid())); +#endif + } + if (logIdentity) std::fputs(": ", stderr); + std::fputs(message.c_str(), stderr); if (message.empty() || message.back() != '\n') std::fputc('\n', stderr); std::fflush(stderr); } @@ -71,6 +89,18 @@ std::string Format(const char* format, VaList* args) { } extern "C" { +void APS5_VABI openlog_nid_postfix(const char* identity, int options, int facility) { + std::lock_guard lock(OutputMutex()); + if (identity) logIdentity = identity; + logOptions = options; + if (facility != 0 && (facility & ~facilityMask) == 0) logFacility = facility; +} + +void APS5_VABI closelog_nid_postfix() { + std::lock_guard lock(OutputMutex()); + logIdentity.reset(); +} + int APS5_VABI setlogmask_nid_postfix(int mask) { std::lock_guard lock(OutputMutex()); const int previous = logMask; @@ -90,7 +120,7 @@ void APS5_VABI vsyslog_nid_postfix(int priority, const char* format, VaList* arg priority &= priorityMask | facilityMask; } if (logMask & (1 << (priority & priorityMask))) { - if ((priority & facilityMask) == 0) priority |= userFacility; + if ((priority & facilityMask) == 0) priority |= logFacility; Emit(priority, format ? Format(ExpandErrorText(format, saved).c_str(), args) : std::string()); } *__error_nid_postfix() = saved; diff --git a/core/libs/tests/GuestSyslog.cpp b/core/libs/tests/GuestSyslog.cpp index ad8adb881..ce4d90705 100644 --- a/core/libs/tests/GuestSyslog.cpp +++ b/core/libs/tests/GuestSyslog.cpp @@ -2,15 +2,19 @@ #include #include #include +#include #include #ifdef _WIN32 #include +#include #else #include #endif extern "C" { void APS5_VABI syslog_nid_postfix(int, const char*, ...); +void APS5_VABI openlog_nid_postfix(const char*, int, int); +void APS5_VABI closelog_nid_postfix(); int APS5_VABI setlogmask_nid_postfix(int); int* APS5_VABI __error_nid_postfix(); } @@ -60,6 +64,15 @@ int main() { Require(setlogmask_nid_postfix(0) == 0xff); syslog_nid_postfix(7, "mask shared across threads"); syslog_nid_postfix(7, "wide %ls %d %S", u"guest", 5, u"text"); + openlog_nid_postfix("worker", 1, 16 << 3); + syslog_nid_postfix(6, "configured default facility"); + openlog_nid_postfix(nullptr, 1, 0); + syslog_nid_postfix(6, "null identity keeps tag"); + openlog_nid_postfix("daemon", 0, 24 << 3); + syslog_nid_postfix(16 | 3, "explicit facility"); + openlog_nid_postfix("closing", 1, 0); + closelog_nid_postfix(); + syslog_nid_postfix(6, "identity cleared, options and facility retained"); std::fflush(stderr); #ifdef _WIN32 Require(::_dup2(saved, ::_fileno(stderr)) == 0 && ::_close(saved) == 0); @@ -86,6 +99,18 @@ int main() { "[syslog:22] level 6\n" "[syslog:15] mask shared across threads\n" "[syslog:15] wide guest 5 text\n"; - Require(length == std::strlen(expected) && std::strcmp(text, expected) == 0); + const std::string prefix = expected; + const std::string pid = std::to_string( +#ifdef _WIN32 + ::_getpid() +#else + ::getpid() +#endif + ); + const std::string configured = "[syslog:134] worker[" + pid + "]: configured default facility\n" + "[syslog:134] worker[" + pid + "]: null identity keeps tag\n" + "[syslog:19] daemon: explicit facility\n" + "[syslog:198] [" + pid + "] identity cleared, options and facility retained\n"; + Require(length == prefix.size() + configured.size() && std::string(text, length) == prefix + configured); Require(std::fclose(captured) == 0); } diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 114f60dba..4f3035996 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -57,7 +57,7 @@ - [localtime, mktime](../../../core/libs/prx/libc/src/Time.cpp) (libc) on Windows convert through the CRT's `localtime_s` and `mktime`, which accept about 1970 to January 3001 UTC and apply the CRT's daylight-saving rule. A time outside that range is converted a whole number of 400-year Gregorian cycles away, where the calendar repeats, so it gets the rule the CRT applies to that year and not the zone's historical one. `gmtime` converts every time whose year fits an `int` on both hosts. - [strftime](../../../core/libs/prx/libc/src/Time.cpp) (libc) expands FreeBSD's `%k`, `%l`, `%s`, `%v` and `%+` itself, ignoring a leading `E` or `O` as FreeBSD does. Unknown conversions, repeated modifiers and incomplete conversions emit literals as in FreeBSD 11; recognized conversions otherwise go to the host's `strftime`. On Windows the `%-`, `%_` and `%0` padding flags still produce nothing, including on the five expanded conversions (`%-k`, `%_k` and `%0k` go to the host), and a standalone `%z` or `%Z` uses the host's zone instead of the broken-down time's `tm_zone`. `%s` follows FreeBSD 9 and 11 and is the local-time `mktime` of the broken-down time (current FreeBSD uses `tm_gmtoff`), which is -1 on Windows outside the range above. `%+` is the C locale's `%a %b %e %H:%M:%S %Z %Y` with `%Z` taken from `tm_zone`, or from `tzname[tm_isdst != 0]` when it is null. - [wcsftime](../../../core/libs/prx/libc/src/Time.cpp) (libc) follows FreeBSD 11.4's [wcsftime.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/locale/wcsftime.c) over this library's `wcsrtombs`, `strftime` and `mbsrtowcs` in the single-byte C locale of [none.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/locale/none.c), with the guest's 16-bit `wchar_t`, without console verification. It has the `strftime` gaps above. A format unit above 255 returns 0 with `EILSEQ`; an empty result, a result that does not fit and a null buffer return 0 with `errno` unchanged, discarding the `ERANGE` or `EINVAL` that the Windows CRT's `strftime` sets. A null format or `tm` throws, where FreeBSD dereferences it (a null `tm` is read there only when the format has a conversion that needs it) -- [syslog](../../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:] `, 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 (as priority 0x23, since FreeBSD passes `LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID` as the priority) and masked, a missing facility becomes `LOG_USER`, and errno is preserved. `setlogmask` follows [FreeBSD 11](https://github.com/freebsd/freebsd-src/blob/8d2708de5d6112a492744d367cfdfb8fd219227a/lib/libc/gen/syslog.c): the process-wide mask defaults to 0xff, zero queries it, and nonzero values replace it; masked messages are skipped before formatting, including the unknown-priority diagnostic. The console's mask behaviour is unverified. `openlog` and `closelog` are not implemented, so their identity and options are not applied +- [syslog](../../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:] `, 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 (as priority 0x23, since FreeBSD passes `LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID` as the priority) and masked, a missing facility becomes `LOG_USER` or the facility set by `openlog`, and errno is preserved. `openlog` applies its identity and `LOG_PID`; a null identity retains the previous one, and a valid nonzero facility replaces the default. `closelog` clears the identity but retains the options and facility, as in FreeBSD 11. `LOG_PERROR` has no extra effect because stderr is the emulated log sink; `LOG_NDELAY`/`LOG_ODELAY`, `LOG_NOWAIT` and `LOG_CONS` have no daemon, child or console backend to control. `setlogmask` follows [FreeBSD 11](https://github.com/freebsd/freebsd-src/blob/8d2708de5d6112a492744d367cfdfb8fd219227a/lib/libc/gen/syslog.c): the process-wide mask defaults to 0xff, zero queries it, and nonzero values replace it; masked messages are skipped before formatting, including the unknown-priority diagnostic. The console's mask behaviour is unverified - [daemon](../../../core/libs/prx/libc/src/Process.cpp) (libc) throws: forking into the background is not supported. PPSA85300 imports it and fails to load without it. VLC 3.0 calls it only when its `daemon` option is set ([libvlc.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/libvlc.c#L190-L197)), and the title starts libVLC with `--ignore-config` and without `--daemon` ([player.cc](https://github.com/ZiZc3/VLC/blob/f3253020a64368153233ede29183014c0b6c8979/src/player.cc#L960-L981)) - [_WStoul](../../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../../core/libs/prx/libc/src/RuneLocale.cpp) and [swscanf_s](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. It throws for `%p`, `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert - [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The host's [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.