LCOV - code coverage report
Current view: top level - src/port - pg_localeconv_r.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 83.8 % 37 31
Test Date: 2026-03-03 10:15:07 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * pg_localeconv_r.c
       4              :  *    Thread-safe implementations of localeconv()
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/port/pg_localeconv_r.c
      12              :  *
      13              :  *-------------------------------------------------------------------------
      14              :  */
      15              : 
      16              : #include "c.h"
      17              : 
      18              : #if !defined(WIN32)
      19              : #include <langinfo.h>
      20              : #include <pthread.h>
      21              : #endif
      22              : 
      23              : #include <limits.h>
      24              : 
      25              : #ifdef MON_THOUSANDS_SEP
      26              : /*
      27              :  * One of glibc's extended langinfo items detected.  Assume that the full set
      28              :  * is present, which means we can use nl_langinfo_l() instead of localeconv().
      29              :  */
      30              : #define TRANSLATE_FROM_LANGINFO
      31              : #endif
      32              : 
      33              : struct lconv_member_info
      34              : {
      35              :     bool        is_string;
      36              :     int         category;
      37              :     size_t      offset;
      38              : #ifdef TRANSLATE_FROM_LANGINFO
      39              :     nl_item     item;
      40              : #endif
      41              : };
      42              : 
      43              : /* Some macros to declare the lconv members compactly. */
      44              : #ifdef TRANSLATE_FROM_LANGINFO
      45              : #define LCONV_M(is_string, category, name, item)                        \
      46              :     { is_string, category, offsetof(struct lconv, name), item }
      47              : #else
      48              : #define LCONV_M(is_string, category, name, item)            \
      49              :     { is_string, category, offsetof(struct lconv, name) }
      50              : #endif
      51              : #define LCONV_S(c, n, i) LCONV_M(true,  c, n, i)
      52              : #define LCONV_C(c, n, i) LCONV_M(false, c, n, i)
      53              : 
      54              : /*
      55              :  * The work of populating lconv objects is driven by this table.  Since we
      56              :  * tolerate non-matching encodings in LC_NUMERIC and LC_MONETARY, we have to
      57              :  * call the underlying OS routine multiple times, with the correct locales.
      58              :  * The first column of this table says which locale category applies to each struct
      59              :  * member.  The second column is the name of the struct member.  The third
      60              :  * column is the name of the nl_item, if translating from nl_langinfo_l() (it's
      61              :  * always the member name, in upper case).
      62              :  */
      63              : static const struct lconv_member_info table[] = {
      64              :     /* String fields. */
      65              :     LCONV_S(LC_NUMERIC, decimal_point, DECIMAL_POINT),
      66              :     LCONV_S(LC_NUMERIC, thousands_sep, THOUSANDS_SEP),
      67              :     LCONV_S(LC_NUMERIC, grouping, GROUPING),
      68              :     LCONV_S(LC_MONETARY, int_curr_symbol, INT_CURR_SYMBOL),
      69              :     LCONV_S(LC_MONETARY, currency_symbol, CURRENCY_SYMBOL),
      70              :     LCONV_S(LC_MONETARY, mon_decimal_point, MON_DECIMAL_POINT),
      71              :     LCONV_S(LC_MONETARY, mon_thousands_sep, MON_THOUSANDS_SEP),
      72              :     LCONV_S(LC_MONETARY, mon_grouping, MON_GROUPING),
      73              :     LCONV_S(LC_MONETARY, positive_sign, POSITIVE_SIGN),
      74              :     LCONV_S(LC_MONETARY, negative_sign, NEGATIVE_SIGN),
      75              : 
      76              :     /* Character fields. */
      77              :     LCONV_C(LC_MONETARY, int_frac_digits, INT_FRAC_DIGITS),
      78              :     LCONV_C(LC_MONETARY, frac_digits, FRAC_DIGITS),
      79              :     LCONV_C(LC_MONETARY, p_cs_precedes, P_CS_PRECEDES),
      80              :     LCONV_C(LC_MONETARY, p_sep_by_space, P_SEP_BY_SPACE),
      81              :     LCONV_C(LC_MONETARY, n_cs_precedes, N_CS_PRECEDES),
      82              :     LCONV_C(LC_MONETARY, n_sep_by_space, N_SEP_BY_SPACE),
      83              :     LCONV_C(LC_MONETARY, p_sign_posn, P_SIGN_POSN),
      84              :     LCONV_C(LC_MONETARY, n_sign_posn, N_SIGN_POSN),
      85              : };
      86              : 
      87              : static inline char **
      88          560 : lconv_string_member(struct lconv *lconv, int i)
      89              : {
      90          560 :     return (char **) ((char *) lconv + table[i].offset);
      91              : }
      92              : 
      93              : static inline char *
      94          224 : lconv_char_member(struct lconv *lconv, int i)
      95              : {
      96          224 :     return (char *) lconv + table[i].offset;
      97              : }
      98              : 
      99              : /*
     100              :  * Free the members of a struct lconv populated by pg_localeconv_r().  The
     101              :  * struct itself is in storage provided by the caller of pg_localeconv_r().
     102              :  */
     103              : void
     104           28 : pg_localeconv_free(struct lconv *lconv)
     105              : {
     106          532 :     for (int i = 0; i < lengthof(table); ++i)
     107          504 :         if (table[i].is_string)
     108          280 :             free(*lconv_string_member(lconv, i));
     109           28 : }
     110              : 
     111              : #ifdef TRANSLATE_FROM_LANGINFO
     112              : /*
     113              :  * Fill in struct lconv members using the equivalent nl_langinfo_l() items.
     114              :  */
     115              : static int
     116           28 : pg_localeconv_from_langinfo(struct lconv *dst,
     117              :                             locale_t monetary_locale,
     118              :                             locale_t numeric_locale)
     119              : {
     120          532 :     for (int i = 0; i < lengthof(table); ++i)
     121              :     {
     122              :         locale_t    locale;
     123              : 
     124         1008 :         locale = table[i].category == LC_NUMERIC ?
     125          504 :             numeric_locale : monetary_locale;
     126              : 
     127          504 :         if (table[i].is_string)
     128              :         {
     129              :             char       *string;
     130              : 
     131          280 :             string = nl_langinfo_l(table[i].item, locale);
     132          280 :             if (!(string = strdup(string)))
     133              :             {
     134            0 :                 pg_localeconv_free(dst);
     135            0 :                 errno = ENOMEM;
     136            0 :                 return -1;
     137              :             }
     138          280 :             *lconv_string_member(dst, i) = string;
     139              :         }
     140              :         else
     141              :         {
     142          224 :             *lconv_char_member(dst, i) =
     143          448 :                 *nl_langinfo_l(table[i].item, locale);
     144              :         }
     145              :     }
     146              : 
     147           28 :     return 0;
     148              : }
     149              : #else                           /* not TRANSLATE_FROM_LANGINFO */
     150              : /*
     151              :  * Copy members from a given category.  Note that you have to call this twice
     152              :  * to copy the LC_MONETARY and then LC_NUMERIC members.
     153              :  */
     154              : static int
     155              : pg_localeconv_copy_members(struct lconv *dst,
     156              :                            struct lconv *src,
     157              :                            int category)
     158              : {
     159              :     for (int i = 0; i < lengthof(table); ++i)
     160              :     {
     161              :         if (table[i].category != category)
     162              :             continue;
     163              : 
     164              :         if (table[i].is_string)
     165              :         {
     166              :             char       *string;
     167              : 
     168              :             string = *lconv_string_member(src, i);
     169              :             if (!(string = strdup(string)))
     170              :             {
     171              :                 pg_localeconv_free(dst);
     172              :                 errno = ENOMEM;
     173              :                 return -1;
     174              :             }
     175              :             *lconv_string_member(dst, i) = string;
     176              :         }
     177              :         else
     178              :         {
     179              :             *lconv_char_member(dst, i) = *lconv_char_member(src, i);
     180              :         }
     181              :     }
     182              : 
     183              :     return 0;
     184              : }
     185              : #endif                          /* not TRANSLATE_FROM_LANGINFO */
     186              : 
     187              : /*
     188              :  * A thread-safe routine to get a copy of the lconv struct for a given
     189              :  * LC_NUMERIC and LC_MONETARY.  Different approaches are used on different
     190              :  * OSes, because the standard interface is so multi-threading unfriendly.
     191              :  *
     192              :  * 1.  On Windows, there is no uselocale(), but there is a way to put
     193              :  * setlocale() into a thread-local mode temporarily.  Its localeconv() is
     194              :  * documented as returning a pointer to thread-local storage, so we don't have
     195              :  * to worry about concurrent callers.
     196              :  *
     197              :  * 2.  On Glibc, as an extension, all the information required to populate
     198              :  * struct lconv is also available via nl_langpath_l(), which is thread-safe.
     199              :  *
     200              :  * 3.  On macOS and *BSD, there is localeconv_l(), so we can create a temporary
     201              :  * locale_t to pass in, and the result is a pointer to storage associated with
     202              :  * the locale_t so we control its lifetime and we don't have to worry about
     203              :  * concurrent calls clobbering it.
     204              :  *
     205              :  * 4.  Otherwise, we wrap plain old localeconv() in uselocale() to avoid
     206              :  * touching the global locale, but the output buffer is allowed by the standard
     207              :  * to be overwritten by concurrent calls to localeconv().  We protect against
     208              :  * _this_ function doing that with a Big Lock, but there isn't much we can do
     209              :  * about code outside our tree that might call localeconv(), given such a poor
     210              :  * interface.
     211              :  *
     212              :  * The POSIX standard explicitly says that it is undefined what happens if
     213              :  * LC_MONETARY or LC_NUMERIC imply an encoding (codeset) different from that
     214              :  * implied by LC_CTYPE.  In practice, all Unix-ish platforms seem to believe
     215              :  * that localeconv() should return strings that are encoded in the codeset
     216              :  * implied by the LC_MONETARY or LC_NUMERIC locale name.  On Windows, LC_CTYPE
     217              :  * has to match to get sane results.
     218              :  *
     219              :  * To get predictable results on all platforms, we'll call the underlying
     220              :  * routines with LC_ALL set to the appropriate locale for each set of members,
     221              :  * and merge the results.  Three members of the resulting object are therefore
     222              :  * guaranteed to be encoded with LC_NUMERIC's codeset: "decimal_point",
     223              :  * "thousands_sep" and "grouping".  All other members are encoded with
     224              :  * LC_MONETARY's codeset.
     225              :  *
     226              :  * Returns 0 on success.  Returns non-zero on failure, and sets errno.  On
     227              :  * success, the caller is responsible for calling pg_localeconv_free() on the
     228              :  * output struct to free the string members it contains.
     229              :  */
     230              : int
     231           28 : pg_localeconv_r(const char *lc_monetary,
     232              :                 const char *lc_numeric,
     233              :                 struct lconv *output)
     234              : {
     235              : #ifdef WIN32
     236              :     wchar_t    *save_lc_ctype = NULL;
     237              :     wchar_t    *save_lc_monetary = NULL;
     238              :     wchar_t    *save_lc_numeric = NULL;
     239              :     int         save_config_thread_locale;
     240              :     int         result = -1;
     241              : 
     242              :     /* Put setlocale() into thread-local mode. */
     243              :     save_config_thread_locale = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
     244              : 
     245              :     /*
     246              :      * Capture the current values as wide strings.  Otherwise, we might not be
     247              :      * able to restore them if their names contain non-ASCII characters and
     248              :      * the intermediate locale changes the expected encoding.  We don't want
     249              :      * to leave the caller in an unexpected state by failing to restore, or
     250              :      * crash the runtime library.
     251              :      */
     252              :     save_lc_ctype = _wsetlocale(LC_CTYPE, NULL);
     253              :     if (!save_lc_ctype || !(save_lc_ctype = wcsdup(save_lc_ctype)))
     254              :         goto exit;
     255              :     save_lc_monetary = _wsetlocale(LC_MONETARY, NULL);
     256              :     if (!save_lc_monetary || !(save_lc_monetary = wcsdup(save_lc_monetary)))
     257              :         goto exit;
     258              :     save_lc_numeric = _wsetlocale(LC_NUMERIC, NULL);
     259              :     if (!save_lc_numeric || !(save_lc_numeric = wcsdup(save_lc_numeric)))
     260              :         goto exit;
     261              : 
     262              :     memset(output, 0, sizeof(*output));
     263              : 
     264              :     /* Copy the LC_MONETARY members. */
     265              :     if (!setlocale(LC_ALL, lc_monetary))
     266              :         goto exit;
     267              :     result = pg_localeconv_copy_members(output, localeconv(), LC_MONETARY);
     268              :     if (result != 0)
     269              :         goto exit;
     270              : 
     271              :     /* Copy the LC_NUMERIC members. */
     272              :     if (!setlocale(LC_ALL, lc_numeric))
     273              :         goto exit;
     274              :     result = pg_localeconv_copy_members(output, localeconv(), LC_NUMERIC);
     275              : 
     276              : exit:
     277              :     /* Restore everything we changed. */
     278              :     if (save_lc_ctype)
     279              :     {
     280              :         _wsetlocale(LC_CTYPE, save_lc_ctype);
     281              :         free(save_lc_ctype);
     282              :     }
     283              :     if (save_lc_monetary)
     284              :     {
     285              :         _wsetlocale(LC_MONETARY, save_lc_monetary);
     286              :         free(save_lc_monetary);
     287              :     }
     288              :     if (save_lc_numeric)
     289              :     {
     290              :         _wsetlocale(LC_NUMERIC, save_lc_numeric);
     291              :         free(save_lc_numeric);
     292              :     }
     293              :     _configthreadlocale(save_config_thread_locale);
     294              : 
     295              :     return result;
     296              : 
     297              : #else                           /* !WIN32 */
     298              :     locale_t    monetary_locale;
     299              :     locale_t    numeric_locale;
     300              :     int         result;
     301              : 
     302              :     /*
     303              :      * All variations on Unix require locale_t objects for LC_MONETARY and
     304              :      * LC_NUMERIC.  We'll set all locale categories, so that we can don't have
     305              :      * to worry about POSIX's undefined behavior if LC_CTYPE's encoding
     306              :      * doesn't match.
     307              :      */
     308           28 :     errno = ENOENT;
     309           28 :     monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
     310           28 :     if (monetary_locale == 0)
     311            0 :         return -1;
     312           28 :     numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
     313           28 :     if (numeric_locale == 0)
     314              :     {
     315            0 :         freelocale(monetary_locale);
     316            0 :         return -1;
     317              :     }
     318              : 
     319           28 :     memset(output, 0, sizeof(*output));
     320              : #if defined(TRANSLATE_FROM_LANGINFO)
     321              :     /* Copy from non-standard nl_langinfo_l() extended items. */
     322           28 :     result = pg_localeconv_from_langinfo(output,
     323              :                                          monetary_locale,
     324              :                                          numeric_locale);
     325              : #elif defined(HAVE_LOCALECONV_L)
     326              :     /* Copy the LC_MONETARY members from a thread-safe lconv object. */
     327              :     result = pg_localeconv_copy_members(output,
     328              :                                         localeconv_l(monetary_locale),
     329              :                                         LC_MONETARY);
     330              :     if (result == 0)
     331              :     {
     332              :         /* Copy the LC_NUMERIC members from a thread-safe lconv object. */
     333              :         result = pg_localeconv_copy_members(output,
     334              :                                             localeconv_l(numeric_locale),
     335              :                                             LC_NUMERIC);
     336              :     }
     337              : #else
     338              :     /* We have nothing better than standard POSIX facilities. */
     339              :     {
     340              :         static pthread_mutex_t big_lock = PTHREAD_MUTEX_INITIALIZER;
     341              :         locale_t    save_locale;
     342              : 
     343              :         pthread_mutex_lock(&big_lock);
     344              :         /* Copy the LC_MONETARY members. */
     345              :         save_locale = uselocale(monetary_locale);
     346              :         result = pg_localeconv_copy_members(output,
     347              :                                             localeconv(),
     348              :                                             LC_MONETARY);
     349              :         if (result == 0)
     350              :         {
     351              :             /* Copy the LC_NUMERIC members. */
     352              :             uselocale(numeric_locale);
     353              :             result = pg_localeconv_copy_members(output,
     354              :                                                 localeconv(),
     355              :                                                 LC_NUMERIC);
     356              :         }
     357              :         pthread_mutex_unlock(&big_lock);
     358              : 
     359              :         uselocale(save_locale);
     360              :     }
     361              : #endif
     362              : 
     363           28 :     freelocale(monetary_locale);
     364           28 :     freelocale(numeric_locale);
     365              : 
     366           28 :     return result;
     367              : #endif                          /* !WIN32 */
     368              : }
        

Generated by: LCOV version 2.0-1