LCOV - code coverage report
Current view: top level - src/port - pg_localeconv_r.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 83.8 % 37 31
Test Date: 2026-07-03 19:57:34 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 81.2 % 16 13

             Branch data     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                 :         700 : lconv_string_member(struct lconv *lconv, int i)
      89                 :             : {
      90                 :         700 :     return (char **) ((char *) lconv + table[i].offset);
      91                 :             : }
      92                 :             : 
      93                 :             : static inline char *
      94                 :         280 : lconv_char_member(struct lconv *lconv, int i)
      95                 :             : {
      96                 :         280 :     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                 :          35 : pg_localeconv_free(struct lconv *lconv)
     105                 :             : {
     106         [ +  + ]:         665 :     for (int i = 0; i < lengthof(table); ++i)
     107         [ +  + ]:         630 :         if (table[i].is_string)
     108                 :         350 :             free(*lconv_string_member(lconv, i));
     109                 :          35 : }
     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                 :          35 : pg_localeconv_from_langinfo(struct lconv *dst,
     117                 :             :                             locale_t monetary_locale,
     118                 :             :                             locale_t numeric_locale)
     119                 :             : {
     120         [ +  + ]:         665 :     for (int i = 0; i < lengthof(table); ++i)
     121                 :             :     {
     122                 :             :         locale_t    locale;
     123                 :             : 
     124                 :        1260 :         locale = table[i].category == LC_NUMERIC ?
     125         [ +  + ]:         630 :             numeric_locale : monetary_locale;
     126                 :             : 
     127         [ +  + ]:         630 :         if (table[i].is_string)
     128                 :             :         {
     129                 :             :             char       *string;
     130                 :             : 
     131                 :         350 :             string = nl_langinfo_l(table[i].item, locale);
     132         [ -  + ]:         350 :             if (!(string = strdup(string)))
     133                 :             :             {
     134                 :           0 :                 pg_localeconv_free(dst);
     135                 :           0 :                 errno = ENOMEM;
     136                 :           0 :                 return -1;
     137                 :             :             }
     138                 :         350 :             *lconv_string_member(dst, i) = string;
     139                 :             :         }
     140                 :             :         else
     141                 :             :         {
     142                 :         280 :             *lconv_char_member(dst, i) =
     143                 :         560 :                 *nl_langinfo_l(table[i].item, locale);
     144                 :             :         }
     145                 :             :     }
     146                 :             : 
     147                 :          35 :     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                 :          35 : 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                 :          35 :     errno = ENOENT;
     309                 :          35 :     monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
     310         [ -  + ]:          35 :     if (monetary_locale == 0)
     311                 :           0 :         return -1;
     312                 :          35 :     numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
     313         [ -  + ]:          35 :     if (numeric_locale == 0)
     314                 :             :     {
     315                 :           0 :         freelocale(monetary_locale);
     316                 :           0 :         return -1;
     317                 :             :     }
     318                 :             : 
     319                 :          35 :     memset(output, 0, sizeof(*output));
     320                 :             : #if defined(TRANSLATE_FROM_LANGINFO)
     321                 :             :     /* Copy from non-standard nl_langinfo_l() extended items. */
     322                 :          35 :     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                 :          35 :     freelocale(monetary_locale);
     364                 :          35 :     freelocale(numeric_locale);
     365                 :             : 
     366                 :          35 :     return result;
     367                 :             : #endif                          /* !WIN32 */
     368                 :             : }
        

Generated by: LCOV version 2.0-1