LCOV - code coverage report
Current view: top level - src/port - pg_localeconv_r.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 31 37 83.8 %
Date: 2025-04-01 15:15:16 Functions: 5 5 100.0 %
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-2024, 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        1120 : lconv_string_member(struct lconv *lconv, int i)
      89             : {
      90        1120 :     return (char **) ((char *) lconv + table[i].offset);
      91             : }
      92             : 
      93             : static inline char *
      94         448 : lconv_char_member(struct lconv *lconv, int i)
      95             : {
      96         448 :     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          56 : pg_localeconv_free(struct lconv *lconv)
     105             : {
     106        1064 :     for (int i = 0; i < lengthof(table); ++i)
     107        1008 :         if (table[i].is_string)
     108         560 :             free(*lconv_string_member(lconv, i));
     109          56 : }
     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          56 : pg_localeconv_from_langinfo(struct lconv *dst,
     117             :                             locale_t monetary_locale,
     118             :                             locale_t numeric_locale)
     119             : {
     120        1064 :     for (int i = 0; i < lengthof(table); ++i)
     121             :     {
     122             :         locale_t    locale;
     123             : 
     124        2016 :         locale = table[i].category == LC_NUMERIC ?
     125        1008 :             numeric_locale : monetary_locale;
     126             : 
     127        1008 :         if (table[i].is_string)
     128             :         {
     129             :             char       *string;
     130             : 
     131         560 :             string = nl_langinfo_l(table[i].item, locale);
     132         560 :             if (!(string = strdup(string)))
     133             :             {
     134           0 :                 pg_localeconv_free(dst);
     135           0 :                 errno = ENOMEM;
     136           0 :                 return -1;
     137             :             }
     138         560 :             *lconv_string_member(dst, i) = string;
     139             :         }
     140             :         else
     141             :         {
     142         448 :             *lconv_char_member(dst, i) =
     143         896 :                 *nl_langinfo_l(table[i].item, locale);
     144             :         }
     145             :     }
     146             : 
     147          56 :     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 predicable 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_localeconf_free() on the
     228             :  * output struct to free the string members it contains.
     229             :  */
     230             : int
     231          56 : 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          56 :     errno = ENOENT;
     309          56 :     monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
     310          56 :     if (monetary_locale == 0)
     311           0 :         return -1;
     312          56 :     numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
     313          56 :     if (numeric_locale == 0)
     314             :     {
     315           0 :         freelocale(monetary_locale);
     316           0 :         return -1;
     317             :     }
     318             : 
     319          56 :     memset(output, 0, sizeof(*output));
     320             : #if defined(TRANSLATE_FROM_LANGINFO)
     321             :     /* Copy from non-standard nl_langinfo_l() extended items. */
     322          56 :     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          56 :     freelocale(monetary_locale);
     364          56 :     freelocale(numeric_locale);
     365             : 
     366          56 :     return result;
     367             : #endif                          /* !WIN32 */
     368             : }

Generated by: LCOV version 1.14