fix(libc): preserve strftime literal conversions

Normalize FreeBSD 11.4 literal conversions before host formatting.

AI-assisted: yes
This commit is contained in:
QRMOE 2026-10-11 16:51:42 +02:00 committed by GitHub
parent a11c8f7f70
commit 1b9179501f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 42 additions and 26 deletions

View File

@ -107,24 +107,41 @@ bool isFreeBsdConversion(char specifier) {
return specifier == 'k' || specifier == 'l' || specifier == 's' || specifier == 'v' || specifier == '+';
}
bool isModifiedPercent(const char* afterPercent) {
return (afterPercent[0] == 'E' || afterPercent[0] == 'O') && afterPercent[1] == '%';
}
struct FormatConversion {
std::size_t length;
char specifier;
bool literal;
bool padded;
};
std::size_t freeBsdConversionLength(const char* afterPercent, char& conversion) {
FormatConversion parseConversion(const char* afterPercent) {
const char* cursor = afterPercent;
if ((*cursor == 'E' || *cursor == 'O') && isFreeBsdConversion(cursor[1])) ++cursor;
if (!isFreeBsdConversion(*cursor)) return 0;
conversion = *cursor;
return static_cast<std::size_t>(cursor - afterPercent) + 1;
bool alternative = false;
bool padded = false;
while (*cursor != '\0') {
if (*cursor == 'E' || *cursor == 'O') {
if (alternative) break;
alternative = true;
} else if (*cursor == '-' || *cursor == '_' || *cursor == '0') {
if (padded) break;
padded = true;
} else {
break;
}
++cursor;
}
if (*cursor == '\0')
return {static_cast<std::size_t>(cursor - afterPercent), cursor[-1], true, padded};
const bool literal = *cursor == '%' || std::strchr("aAbBcCdDeFgGhHIjklmMnprRsStTuUVvWwXxyYzZ+", *cursor) == nullptr;
return {static_cast<std::size_t>(cursor - afterPercent) + 1, *cursor, literal, padded};
}
bool hasFreeBsdConversion(const char* format) {
bool requiresFreeBsdExpansion(const char* format) {
while (*format != '\0') {
if (*format++ != '%' || *format == '\0') continue;
char conversion = 0;
if (freeBsdConversionLength(format, conversion) != 0) return true;
format += isModifiedPercent(format) ? 2 : 1;
if (*format++ != '%') continue;
const auto conversion = parseConversion(format);
if (conversion.literal || (!conversion.padded && isFreeBsdConversion(conversion.specifier))) return true;
format += conversion.length;
}
return false;
}
@ -142,26 +159,25 @@ std::string expandFreeBsdConversions(const char* format, const GuestTm& guest) {
std::string result;
while (*format != '\0') {
const char next = *format++;
if (next != '%' || *format == '\0') {
if (next != '%') {
result += next;
continue;
}
if (isModifiedPercent(format)) {
result += '%';
result += *format++;
result += *format++;
const auto conversion = parseConversion(format);
if (conversion.literal) {
if (conversion.specifier == '%') result += '%';
result += conversion.specifier;
format += conversion.length;
continue;
}
char conversion = 0;
const std::size_t length = freeBsdConversionLength(format, conversion);
if (length == 0) {
result += '%';
result += *format++;
if (conversion.padded || !isFreeBsdConversion(conversion.specifier)) {
result.append(format - 1, conversion.length + 1);
format += conversion.length;
continue;
}
format += length;
format += conversion.length;
char text[32];
switch (conversion) {
switch (conversion.specifier) {
case 'k':
std::snprintf(text, sizeof(text), "%2d", guest.tm_hour);
result += text;
@ -302,7 +318,7 @@ int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr) {
size_t APS5_VABI libc_strftime_nid_postfix(char* str, size_t count, const char* format, const GuestTm* timeptr) {
const std::tm host = toHostTm(*timeptr);
if (!hasFreeBsdConversion(format)) return std::strftime(str, count, format, &host);
if (!requiresFreeBsdExpansion(format)) return std::strftime(str, count, format, &host);
const std::string expanded = expandFreeBsdConversions(format, *timeptr);
return std::strftime(str, count, expanded.c_str(), &host);
}