From eccf12866527b8d24c7d7f92f755142be8ef9b11 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Thu, 31 Aug 2023 19:50:05 +0300 Subject: [PATCH] xz: Refactor thousand separator detection and disable it on MSVC. Now the two variations of the format strings are created with a macro, and the whole detection code can be easily disabled on platforms where thousand separator formatting is known to not work (MSVC has no support, and on DJGPP 2.05 it can have problems in some cases). --- src/xz/util.c | 89 ++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/src/xz/util.c b/src/xz/util.c index 9f9a8fb0..6ab4c2d7 100644 --- a/src/xz/util.c +++ b/src/xz/util.c @@ -17,9 +17,45 @@ /// Buffers for uint64_to_str() and uint64_to_nicestr() static char bufs[4][128]; -/// Thousand separator support in uint64_to_str() and uint64_to_nicestr() + +// Thousand separator support in uint64_to_str() and uint64_to_nicestr(): +// +// DJGPP 2.05 added support for thousands separators but it's broken +// at least under WinXP with Finnish locale that uses a non-breaking space +// as the thousands separator. Workaround by disabling thousands separators +// for DJGPP builds. +// +// MSVC doesn't support thousand separators. +#if defined(__DJGPP__) || defined(_MSC_VER) +# define FORMAT_THOUSAND_SEP(prefix, suffix) prefix suffix +# define check_thousand_sep(slot) do { } while (0) +#else +# define FORMAT_THOUSAND_SEP(prefix, suffix) ((thousand == WORKS) \ + ? prefix "'" suffix \ + : prefix suffix) + static enum { UNKNOWN, WORKS, BROKEN } thousand = UNKNOWN; +/// Check if thousands separator is supported. Run-time checking is easiest +/// because it seems to be sometimes lacking even on a POSIXish system. +/// Note that trying to use thousands separators when snprintf() doesn't +/// support them results in undefined behavior. This just has happened to +/// work well enough in practice. +/// +/// This must be called before using the FORMAT_THOUSAND_SEP macro. +static void +check_thousand_sep(uint32_t slot) +{ + if (thousand == UNKNOWN) { + bufs[slot][0] = '\0'; + snprintf(bufs[slot], sizeof(bufs[slot]), "%'u", 1U); + thousand = bufs[slot][0] == '1' ? WORKS : BROKEN; + } + + return; +} +#endif + extern void * xrealloc(void *ptr, size_t size) @@ -142,31 +178,6 @@ round_up_to_mib(uint64_t n) } -/// Check if thousands separator is supported. Run-time checking is easiest -/// because it seems to be sometimes lacking even on a POSIXish system. -/// Note that trying to use thousands separators when snprintf() doesn't -/// support them results in undefined behavior. This just has happened to -/// work well enough in practice. -/// -/// DJGPP 2.05 added support for thousands separators but it's broken -/// at least under WinXP with Finnish locale that uses a non-breaking space -/// as the thousands separator. Workaround by disabling thousands separators -/// for DJGPP builds. -static void -check_thousand_sep(uint32_t slot) -{ - if (thousand == UNKNOWN) { - bufs[slot][0] = '\0'; -#ifndef __DJGPP__ - snprintf(bufs[slot], sizeof(bufs[slot]), "%'u", 1U); -#endif - thousand = bufs[slot][0] == '1' ? WORKS : BROKEN; - } - - return; -} - - extern const char * uint64_to_str(uint64_t value, uint32_t slot) { @@ -174,10 +185,8 @@ uint64_to_str(uint64_t value, uint32_t slot) check_thousand_sep(slot); - if (thousand == WORKS) - snprintf(bufs[slot], sizeof(bufs[slot]), "%'" PRIu64, value); - else - snprintf(bufs[slot], sizeof(bufs[slot]), "%" PRIu64, value); + snprintf(bufs[slot], sizeof(bufs[slot]), + FORMAT_THOUSAND_SEP("%", PRIu64), value); return bufs[slot]; } @@ -201,10 +210,8 @@ uint64_to_nicestr(uint64_t value, enum nicestr_unit unit_min, if ((unit_min == NICESTR_B && value < 10000) || unit_max == NICESTR_B) { // The value is shown as bytes. - if (thousand == WORKS) - my_snprintf(&pos, &left, "%'u", (unsigned int)value); - else - my_snprintf(&pos, &left, "%u", (unsigned int)value); + my_snprintf(&pos, &left, FORMAT_THOUSAND_SEP("%", "u"), + (unsigned int)value); } else { // Scale the value to a nicer unit. Unless unit_min and // unit_max limit us, we will show at most five significant @@ -215,21 +222,15 @@ uint64_to_nicestr(uint64_t value, enum nicestr_unit unit_min, ++unit; } while (unit < unit_min || (d > 9999.9 && unit < unit_max)); - if (thousand == WORKS) - my_snprintf(&pos, &left, "%'.1f", d); - else - my_snprintf(&pos, &left, "%.1f", d); + my_snprintf(&pos, &left, FORMAT_THOUSAND_SEP("%", ".1f"), d); } static const char suffix[5][4] = { "B", "KiB", "MiB", "GiB", "TiB" }; my_snprintf(&pos, &left, " %s", suffix[unit]); - if (always_also_bytes && value >= 10000) { - if (thousand == WORKS) - snprintf(pos, left, " (%'" PRIu64 " B)", value); - else - snprintf(pos, left, " (%" PRIu64 " B)", value); - } + if (always_also_bytes && value >= 10000) + snprintf(pos, left, FORMAT_THOUSAND_SEP(" (%", PRIu64 " B)"), + value); return bufs[slot]; }