Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/libs/prx/libc/include/Api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,7 @@ int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr);
int64_t APS5_VABI timelocal_nid_postfix(GuestTm* timeptr);
size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr);
char* APS5_VABI asctime_nid_postfix(const GuestTm* timeptr);
char* APS5_VABI asctime_r_nid_postfix(const GuestTm* timeptr, char* buffer);
char* APS5_VABI ctime_nid_postfix(const int64_t* timer);
size_t APS5_VABI strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr);
size_t APS5_VABI wcsftime_nid_postfix(std::uint16_t* str, size_t count, const std::uint16_t* format, const GuestTm* timeptr);
Expand Down
17 changes: 11 additions & 6 deletions core/libs/prx/libc/src/Time.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -661,17 +661,22 @@ size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char*
return formatGuestTime(str, count, format, *timeptr);
}

char* APS5_VABI asctime_nid_postfix(const GuestTm* timeptr) {
static thread_local char result[ASCTIME_SIZE];
char* APS5_VABI asctime_r_nid_postfix(const GuestTm* timeptr, char* buffer) {
if (buffer == nullptr) throw std::invalid_argument("asctime_r: null buffer");
if (timeptr == nullptr) {
errno = EINVAL;
std::memcpy(result, "??? ??? ?? ??:??:?? ????\n", sizeof("??? ??? ?? ??:??:?? ????\n"));
return result;
std::memcpy(buffer, "??? ??? ?? ??:??:?? ????\n", sizeof("??? ??? ?? ??:??:?? ????\n"));
return buffer;
}
const std::int64_t year = static_cast<std::int64_t>(timeptr->tm_year) + YEAR_BASE;
std::snprintf(result, sizeof(result), "%s %s%3d %02d:%02d:%02d %04d\n", tableName(timeptr->tm_wday, WEEKDAY_NAMES, WEEKDAY_NAMES, 7), tableName(timeptr->tm_mon, MONTH_NAMES, MONTH_NAMES, 12),
std::snprintf(buffer, 26, "%s %s%3d %02d:%02d:%02d %04d\n", tableName(timeptr->tm_wday, WEEKDAY_NAMES, WEEKDAY_NAMES, 7), tableName(timeptr->tm_mon, MONTH_NAMES, MONTH_NAMES, 12),
clampedField(timeptr->tm_mday, 100), clampedField(timeptr->tm_hour, 100), clampedField(timeptr->tm_min, 100), clampedField(timeptr->tm_sec, 100), clampedField(year, 10000));
return result;
return buffer;
}

char* APS5_VABI asctime_nid_postfix(const GuestTm* timeptr) {
static thread_local char result[ASCTIME_SIZE];
return asctime_r_nid_postfix(timeptr, result);
}

char* APS5_VABI ctime_nid_postfix(const int64_t* timer) {
Expand Down
6 changes: 6 additions & 0 deletions core/libs/tests/GuestStrftime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ void CheckAsctime(const char* name, const GuestTm& time, const char* expected) {
std::fprintf(stderr, "asctime %s: expected \"%s\", received \"%s\"\n", name, expected, result);
correct = false;
}
char buffer[26]{};
const char* reentrant = asctime_r_nid_postfix(&time, buffer);
if (reentrant != buffer || std::strcmp(buffer, expected) != 0) {
std::fprintf(stderr, "asctime_r %s: expected \"%s\", received \"%s\"\n", name, expected, buffer);
correct = false;
}
}

}
Expand Down
14 changes: 14 additions & 0 deletions core/libs/tests/GuestTimeConversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ static GuestTm UtcTime(std::int64_t timer) {
static void CheckAsctime(const GuestTm& brokenDown, const char* expected) {
const char* text = asctime_nid_postfix(&brokenDown);
Require(text != nullptr && std::strcmp(text, expected) == 0, "asctime did not format the broken-down time");
char buffer[26]{};
Require(asctime_r_nid_postfix(&brokenDown, buffer) == buffer && std::strcmp(buffer, expected) == 0,
"asctime_r did not format the broken-down time");
}

struct UtcCase {
Expand Down Expand Up @@ -496,6 +499,17 @@ int main() {
*__error_nid_postfix() = 0;
Require(std::strcmp(ctime_nid_postfix(&underflow), "??? ??? ?? ??:??:?? ????\n") == 0 && *__error_nid_postfix() == 22,
"ctime of an unrepresentable time did not report it");
char asctimeBuffer[26]{};
*__error_nid_postfix() = 0;
Require(asctime_r_nid_postfix(nullptr, asctimeBuffer) == asctimeBuffer && std::strcmp(asctimeBuffer, "??? ??? ?? ??:??:?? ????\n") == 0
&& *__error_nid_postfix() == 22, "asctime_r of a null time did not report it");
bool nullBufferThrew = false;
try {
asctime_r_nid_postfix(&roundTrip, nullptr);
} catch (const std::invalid_argument&) {
nullBufferThrew = true;
}
Require(nullBufferThrew, "asctime_r accepted a null buffer");
{
std::barrier finish(3);
std::array<const char*, 2> texts{};
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@
- [tzname](../../../core/libs/prx/libc/src/Time.cpp) (libc) points at the host's zone names, read at load and again by `tzset`: the abbreviations a `TZ` string gives (`EST5EDT` is `EST` and `EDT` on both hosts), but on Windows without `TZ` the CRT's full names such as `GMT Standard Time`, which ICU's [uprv_tzname](https://github.com/unicode-org/icu/blob/457157a92aa053e632cc7fcfd0e12f8a943b2d11/icu4c/source/common/putil.cpp#L1244-L1264) (ES-DE in PPSA99169) cannot map to a zone ID. A payload process on a PS5 (firmware 13.60) read both names empty with `TZ` unset, and its `%Z` printed nothing whatever `tm_zone` held; the names a title process sees are not known
- [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. On macOS `mktime` converts through the host's `mktime`, which refuses every year before 1900. A local time before 1900 is instead solved for directly: the offset `localtime_r` gives for a candidate time, which follows the zone's history (local mean time before its first transition), is subtracted from the local fields, and the step is repeated until the offset holds. `tm_isdst` is not consulted there, so a time with `tm_isdst` set where the zone had no daylight offset is taken as standard time. FreeBSD's [`time1`](https://github.com/freebsd/freebsd-src/blob/stable/11/contrib/tzcode/stdtime/localtime.c#L2036) would instead shift it by the difference between one of the zone's daylight types and a standard one. `gmtime` converts every time whose year fits an `int` on both hosts.
- [timelocal](../../../core/libs/prx/libc/src/Time.cpp) (libc) sets `tm_isdst = -1` before converting through `mktime`, following FreeBSD 11 [timelocal](https://github.com/freebsd/freebsd-src/blob/stable/11/contrib/tzcode/stdtime/localtime.c#L2126-L2132).
- [strftime and asctime](../../../core/libs/prx/libc/src/Time.cpp) (libc) follow a payload process on a PS5 (firmware 13.60) in the C locale. Out-of-range fields in `%G`, `%g`, `%V`, `%U` and `%W` follow the SDK 7.00 `libc.prx` that PPSA06416 ships, read from its disassembly and run, and are measured on a console only for the cases in the test; it counts a year divisible by 100 as leap only when `tm_year / 400` is 100 or -300. `%k`, `%l`, `%P`, `%s`, `%+`, `%v`, flags and widths are copied as written, `E` or `O` before an unknown conversion is dropped, and a format ending in either throws, where the console crashed. Negative fields print 0 and fields wrap at 100 (`%j` at 1000, `%u` and `%w` at 10); `%Y` and `%G` are the year mod 10000, `%C` is `tm_year / 100 + 19`. A negative weekday or month gives `Sun` or `Jan`. `%z` and `%Z` ignore `tm_gmtoff` and `tm_zone` and use the guest zone and `tzname`
- [strftime and asctime](../../../core/libs/prx/libc/src/Time.cpp) (libc) follow a payload process on a PS5 (firmware 13.60) in the C locale. Out-of-range fields in `%G`, `%g`, `%V`, `%U` and `%W` follow the SDK 7.00 `libc.prx` that PPSA06416 ships, read from its disassembly and run, and are measured on a console only for the cases in the test; it counts a year divisible by 100 as leap only when `tm_year / 400` is 100 or -300. `%k`, `%l`, `%P`, `%s`, `%+`, `%v`, flags and widths are copied as written, `E` or `O` before an unknown conversion is dropped, and a format ending in either throws, where the console crashed. Negative fields print 0 and fields wrap at 100 (`%j` at 1000, `%u` and `%w` at 10); `%Y` and `%G` are the year mod 10000, `%C` is `tm_year / 100 + 19`. A negative weekday or month gives `Sun` or `Jan`. `%z` and `%Z` ignore `tm_gmtoff` and `tm_zone` and use the guest zone and `tzname`. `asctime_r` shares the `asctime` formatting, unmeasured; a null buffer throws
- [wcsftime](../../../core/libs/prx/libc/src/Time.cpp) (libc) has the `strftime` gaps above. A null buffer returns 0, where the console writes through it, and a size at or above `SIZE_MAX / _Getmbcurmax()` returns 0 with `EINVAL`, where the console formats as usual. A null format or `tm` throws. Conversion-overflow continuation follows [reported native-PC measurements of firmware 13.42](https://github.com/boykopovar/AnyPS5/pull/10895#issuecomment-6494817352), not a physical-console run. `%Z`/`%z`, failures within a conversion and differences in title-bundled `libc.prx` remain unverified by that measurement
- [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 (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; when no identity is set, FreeBSD falls back to the program name, which this backend does not emit. `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
- [fwscanf, vfwscanf, wscanf, vwscanf and their `_s` forms](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) run the `swscanf` scanner on characters read with `fgetwc`, so everything listed for `swscanf` in this file applies to them. As in the console's `libc.prx`, a field takes characters while they can still begin a valid item, so a failed field such as `0x` for `%x` stays consumed, and only the character that ended the scan goes back to the stream. That character is pushed back as its UTF-8 bytes with `ungetc`, because `ungetwc` takes single bytes only; the Windows C runtime guarantees one byte of push-back, so a non-ASCII lookahead can be lost there. A null stream or format returns `EOF`; the console checks them only in the `_s` forms.
Expand Down
Loading