From 073bd5416b4c31bbe08975a6863ae8a80d0542da Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Wed, 22 Oct 2025 10:49:59 -0700 Subject: [PATCH v1] NLS: use gettext() to translate system error messages. Previously, errors from the system such as "Permission denied" (EACCES) relied on strerror_r() to perform translation; which has different behavior from gettext(), which translates Postgres error messages like "division by zero". Disable translations inside of strerror_r() by temporarily switching to the C locale, and instead perform the translations with gettext. This makes translation of system error messages consistent across platforms, respecting whether NLS is enabled or not. It also avoids strerror_r()'s dependence on the global LC_CTYPE setting (gettext does not rely on LC_CTYPE). Creates a need to translate more messages -- one for each errno that Postgres might plausibly encounter, or possibly a few more for platform variations of the string representations. --- src/port/snprintf.c | 73 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/src/port/snprintf.c b/src/port/snprintf.c index d7f18b42d19..ace39f2673e 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -33,6 +33,9 @@ #include "c.h" +#ifdef HAVE_USELOCALE +#include +#endif #include /* @@ -161,6 +164,8 @@ typedef union static void flushbuffer(PrintfTarget *target); static void dopr(PrintfTarget *target, const char *format, va_list args); +static char *c_strerror_r(int errnum, char *buf, size_t buflen); +static char *nls_strerror_r(int errnum, char *buf, size_t buflen); /* @@ -724,8 +729,8 @@ nextch2: case 'm': { char errbuf[PG_STRERROR_R_BUFLEN]; - const char *errm = strerror_r(save_errno, - errbuf, sizeof(errbuf)); + const char *errm = nls_strerror_r(save_errno, + errbuf, sizeof(errbuf)); dostr(errm, strlen(errm), target); } @@ -1566,3 +1571,67 @@ trailing_pad(int padlen, PrintfTarget *target) if (padlen < 0) dopr_outchmulti(' ', -padlen, target); } + + +/* + * If NLS is enabled, translate the system error message. Otherwise, return + * the untranslated string. + */ +static char * +nls_strerror_r(int errnum, char *buf, size_t buflen) +{ +#ifdef ENABLE_NLS + char plain[PG_STRERROR_R_BUFLEN]; + char *msgid; + char *msgstr; + + /* run c_strerror_r to get plain untranslated string */ + msgid = c_strerror_r(errnum, plain, PG_STRERROR_R_BUFLEN); + + /* translate with gettext() and store in result buffer */ + msgstr = _(msgid); + strlcpy(buf, msgstr, buflen); + return buf; +#else + return c_strerror_r(errnum, buf, buflen); +#endif +} + +/* + * Temporarily switches to the C locale to ensure that strerror_r() returns an + * untranslated string. + * + * The purpose of this function is to avoid strerror_r() performing the + * translation itself, which has different behavior than gettext. In + * particular, strerror_r() may force the translated message into the ASCII + * character set if LC_CTYPE=C, even if the database encoding supports a wider + * character set (e.g. UTF-8). We also want to avoid translations when NLS is + * disabled. + */ +static char * +c_strerror_r(int errnum, char *buf, size_t buflen) +{ +#ifdef HAVE_USELOCALE + static locale_t c_locale = NULL; + char *msgid; + locale_t save_loc; + + if (!c_locale) + c_locale = newlocale(LC_ALL_MASK, "C", NULL); + + save_loc = uselocale(c_locale); + + msgid = strerror_r(errnum, buf, buflen); + + if (save_loc != NULL) + uselocale(save_loc); + + return msgid; +#else + /* + * Platforms lacking uselocale() have not been observed to translate + * messages inside strerror_r(). + */ + return strerror_r(errnum, buf, buflen); +#endif +} -- 2.43.0