1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-29 16:21:20 +03:00
postgres/src/port/pg_localeconv_r.c
Michael Paquier 2c6469d4cd Fix incorrect year in some copyright notices
A couple of new files have been added in the tree with a copyright year
of 2024 while we were already in 2025.  These should be marked with
2025, so let's fix them.

Reported-by: Shaik Mohammad Mujeeb <mujeeb.sk.dev@gmail.com>
Discussion: https://postgr.es/m/CALa6HA4_Wu7-2PV0xv-Q84cT8eG7rTx6bdjUV0Pc=McAwkNMfQ@mail.gmail.com
2025-05-19 09:46:52 +09:00

369 lines
11 KiB
C

/*-------------------------------------------------------------------------
*
* pg_localeconv_r.c
* Thread-safe implementations of localeconv()
*
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/port/pg_localeconv_r.c
*
*-------------------------------------------------------------------------
*/
#include "c.h"
#if !defined(WIN32)
#include <langinfo.h>
#include <pthread.h>
#endif
#include <limits.h>
#ifdef MON_THOUSANDS_SEP
/*
* One of glibc's extended langinfo items detected. Assume that the full set
* is present, which means we can use nl_langinfo_l() instead of localeconv().
*/
#define TRANSLATE_FROM_LANGINFO
#endif
struct lconv_member_info
{
bool is_string;
int category;
size_t offset;
#ifdef TRANSLATE_FROM_LANGINFO
nl_item item;
#endif
};
/* Some macros to declare the lconv members compactly. */
#ifdef TRANSLATE_FROM_LANGINFO
#define LCONV_M(is_string, category, name, item) \
{ is_string, category, offsetof(struct lconv, name), item }
#else
#define LCONV_M(is_string, category, name, item) \
{ is_string, category, offsetof(struct lconv, name) }
#endif
#define LCONV_S(c, n, i) LCONV_M(true, c, n, i)
#define LCONV_C(c, n, i) LCONV_M(false, c, n, i)
/*
* The work of populating lconv objects is driven by this table. Since we
* tolerate non-matching encodings in LC_NUMERIC and LC_MONETARY, we have to
* call the underlying OS routine multiple times, with the correct locales.
* The first column of this table says which locale category applies to each struct
* member. The second column is the name of the struct member. The third
* column is the name of the nl_item, if translating from nl_langinfo_l() (it's
* always the member name, in upper case).
*/
static const struct lconv_member_info table[] = {
/* String fields. */
LCONV_S(LC_NUMERIC, decimal_point, DECIMAL_POINT),
LCONV_S(LC_NUMERIC, thousands_sep, THOUSANDS_SEP),
LCONV_S(LC_NUMERIC, grouping, GROUPING),
LCONV_S(LC_MONETARY, int_curr_symbol, INT_CURR_SYMBOL),
LCONV_S(LC_MONETARY, currency_symbol, CURRENCY_SYMBOL),
LCONV_S(LC_MONETARY, mon_decimal_point, MON_DECIMAL_POINT),
LCONV_S(LC_MONETARY, mon_thousands_sep, MON_THOUSANDS_SEP),
LCONV_S(LC_MONETARY, mon_grouping, MON_GROUPING),
LCONV_S(LC_MONETARY, positive_sign, POSITIVE_SIGN),
LCONV_S(LC_MONETARY, negative_sign, NEGATIVE_SIGN),
/* Character fields. */
LCONV_C(LC_MONETARY, int_frac_digits, INT_FRAC_DIGITS),
LCONV_C(LC_MONETARY, frac_digits, FRAC_DIGITS),
LCONV_C(LC_MONETARY, p_cs_precedes, P_CS_PRECEDES),
LCONV_C(LC_MONETARY, p_sep_by_space, P_SEP_BY_SPACE),
LCONV_C(LC_MONETARY, n_cs_precedes, N_CS_PRECEDES),
LCONV_C(LC_MONETARY, n_sep_by_space, N_SEP_BY_SPACE),
LCONV_C(LC_MONETARY, p_sign_posn, P_SIGN_POSN),
LCONV_C(LC_MONETARY, n_sign_posn, N_SIGN_POSN),
};
static inline char **
lconv_string_member(struct lconv *lconv, int i)
{
return (char **) ((char *) lconv + table[i].offset);
}
static inline char *
lconv_char_member(struct lconv *lconv, int i)
{
return (char *) lconv + table[i].offset;
}
/*
* Free the members of a struct lconv populated by pg_localeconv_r(). The
* struct itself is in storage provided by the caller of pg_localeconv_r().
*/
void
pg_localeconv_free(struct lconv *lconv)
{
for (int i = 0; i < lengthof(table); ++i)
if (table[i].is_string)
free(*lconv_string_member(lconv, i));
}
#ifdef TRANSLATE_FROM_LANGINFO
/*
* Fill in struct lconv members using the equivalent nl_langinfo_l() items.
*/
static int
pg_localeconv_from_langinfo(struct lconv *dst,
locale_t monetary_locale,
locale_t numeric_locale)
{
for (int i = 0; i < lengthof(table); ++i)
{
locale_t locale;
locale = table[i].category == LC_NUMERIC ?
numeric_locale : monetary_locale;
if (table[i].is_string)
{
char *string;
string = nl_langinfo_l(table[i].item, locale);
if (!(string = strdup(string)))
{
pg_localeconv_free(dst);
errno = ENOMEM;
return -1;
}
*lconv_string_member(dst, i) = string;
}
else
{
*lconv_char_member(dst, i) =
*nl_langinfo_l(table[i].item, locale);
}
}
return 0;
}
#else /* not TRANSLATE_FROM_LANGINFO */
/*
* Copy members from a given category. Note that you have to call this twice
* to copy the LC_MONETARY and then LC_NUMERIC members.
*/
static int
pg_localeconv_copy_members(struct lconv *dst,
struct lconv *src,
int category)
{
for (int i = 0; i < lengthof(table); ++i)
{
if (table[i].category != category)
continue;
if (table[i].is_string)
{
char *string;
string = *lconv_string_member(src, i);
if (!(string = strdup(string)))
{
pg_localeconv_free(dst);
errno = ENOMEM;
return -1;
}
*lconv_string_member(dst, i) = string;
}
else
{
*lconv_char_member(dst, i) = *lconv_char_member(src, i);
}
}
return 0;
}
#endif /* not TRANSLATE_FROM_LANGINFO */
/*
* A thread-safe routine to get a copy of the lconv struct for a given
* LC_NUMERIC and LC_MONETARY. Different approaches are used on different
* OSes, because the standard interface is so multi-threading unfriendly.
*
* 1. On Windows, there is no uselocale(), but there is a way to put
* setlocale() into a thread-local mode temporarily. Its localeconv() is
* documented as returning a pointer to thread-local storage, so we don't have
* to worry about concurrent callers.
*
* 2. On Glibc, as an extension, all the information required to populate
* struct lconv is also available via nl_langpath_l(), which is thread-safe.
*
* 3. On macOS and *BSD, there is localeconv_l(), so we can create a temporary
* locale_t to pass in, and the result is a pointer to storage associated with
* the locale_t so we control its lifetime and we don't have to worry about
* concurrent calls clobbering it.
*
* 4. Otherwise, we wrap plain old localeconv() in uselocale() to avoid
* touching the global locale, but the output buffer is allowed by the standard
* to be overwritten by concurrent calls to localeconv(). We protect against
* _this_ function doing that with a Big Lock, but there isn't much we can do
* about code outside our tree that might call localeconv(), given such a poor
* interface.
*
* The POSIX standard explicitly says that it is undefined what happens if
* LC_MONETARY or LC_NUMERIC imply an encoding (codeset) different from that
* implied by LC_CTYPE. In practice, all Unix-ish platforms seem to believe
* that localeconv() should return strings that are encoded in the codeset
* implied by the LC_MONETARY or LC_NUMERIC locale name. On Windows, LC_CTYPE
* has to match to get sane results.
*
* To get predictable results on all platforms, we'll call the underlying
* routines with LC_ALL set to the appropriate locale for each set of members,
* and merge the results. Three members of the resulting object are therefore
* guaranteed to be encoded with LC_NUMERIC's codeset: "decimal_point",
* "thousands_sep" and "grouping". All other members are encoded with
* LC_MONETARY's codeset.
*
* Returns 0 on success. Returns non-zero on failure, and sets errno. On
* success, the caller is responsible for calling pg_localeconv_free() on the
* output struct to free the string members it contains.
*/
int
pg_localeconv_r(const char *lc_monetary,
const char *lc_numeric,
struct lconv *output)
{
#ifdef WIN32
wchar_t *save_lc_ctype = NULL;
wchar_t *save_lc_monetary = NULL;
wchar_t *save_lc_numeric = NULL;
int save_config_thread_locale;
int result = -1;
/* Put setlocale() into thread-local mode. */
save_config_thread_locale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
/*
* Capture the current values as wide strings. Otherwise, we might not be
* able to restore them if their names contain non-ASCII characters and
* the intermediate locale changes the expected encoding. We don't want
* to leave the caller in an unexpected state by failing to restore, or
* crash the runtime library.
*/
save_lc_ctype = _wsetlocale(LC_CTYPE, NULL);
if (!save_lc_ctype || !(save_lc_ctype = wcsdup(save_lc_ctype)))
goto exit;
save_lc_monetary = _wsetlocale(LC_MONETARY, NULL);
if (!save_lc_monetary || !(save_lc_monetary = wcsdup(save_lc_monetary)))
goto exit;
save_lc_numeric = _wsetlocale(LC_NUMERIC, NULL);
if (!save_lc_numeric || !(save_lc_numeric = wcsdup(save_lc_numeric)))
goto exit;
memset(output, 0, sizeof(*output));
/* Copy the LC_MONETARY members. */
if (!setlocale(LC_ALL, lc_monetary))
goto exit;
result = pg_localeconv_copy_members(output, localeconv(), LC_MONETARY);
if (result != 0)
goto exit;
/* Copy the LC_NUMERIC members. */
if (!setlocale(LC_ALL, lc_numeric))
goto exit;
result = pg_localeconv_copy_members(output, localeconv(), LC_NUMERIC);
exit:
/* Restore everything we changed. */
if (save_lc_ctype)
{
_wsetlocale(LC_CTYPE, save_lc_ctype);
free(save_lc_ctype);
}
if (save_lc_monetary)
{
_wsetlocale(LC_MONETARY, save_lc_monetary);
free(save_lc_monetary);
}
if (save_lc_numeric)
{
_wsetlocale(LC_NUMERIC, save_lc_numeric);
free(save_lc_numeric);
}
_configthreadlocale(save_config_thread_locale);
return result;
#else /* !WIN32 */
locale_t monetary_locale;
locale_t numeric_locale;
int result;
/*
* All variations on Unix require locale_t objects for LC_MONETARY and
* LC_NUMERIC. We'll set all locale categories, so that we can don't have
* to worry about POSIX's undefined behavior if LC_CTYPE's encoding
* doesn't match.
*/
errno = ENOENT;
monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
if (monetary_locale == 0)
return -1;
numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
if (numeric_locale == 0)
{
freelocale(monetary_locale);
return -1;
}
memset(output, 0, sizeof(*output));
#if defined(TRANSLATE_FROM_LANGINFO)
/* Copy from non-standard nl_langinfo_l() extended items. */
result = pg_localeconv_from_langinfo(output,
monetary_locale,
numeric_locale);
#elif defined(HAVE_LOCALECONV_L)
/* Copy the LC_MONETARY members from a thread-safe lconv object. */
result = pg_localeconv_copy_members(output,
localeconv_l(monetary_locale),
LC_MONETARY);
if (result == 0)
{
/* Copy the LC_NUMERIC members from a thread-safe lconv object. */
result = pg_localeconv_copy_members(output,
localeconv_l(numeric_locale),
LC_NUMERIC);
}
#else
/* We have nothing better than standard POSIX facilities. */
{
static pthread_mutex_t big_lock = PTHREAD_MUTEX_INITIALIZER;
locale_t save_locale;
pthread_mutex_lock(&big_lock);
/* Copy the LC_MONETARY members. */
save_locale = uselocale(monetary_locale);
result = pg_localeconv_copy_members(output,
localeconv(),
LC_MONETARY);
if (result == 0)
{
/* Copy the LC_NUMERIC members. */
uselocale(numeric_locale);
result = pg_localeconv_copy_members(output,
localeconv(),
LC_NUMERIC);
}
pthread_mutex_unlock(&big_lock);
uselocale(save_locale);
}
#endif
freelocale(monetary_locale);
freelocale(numeric_locale);
return result;
#endif /* !WIN32 */
}