LCOV - code coverage report
Current view: top level - src/port - pg_localeconv_r.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 87.8 % 49 43
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 69.6 % 23 16

             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                 :         210 : lconv_string_member(struct lconv *lconv, int i)
      89                 :             : {
      90                 :         210 :         return (char **) ((char *) lconv + table[i].offset);
      91                 :             : }
      92                 :             : 
      93                 :             : static inline char *
      94                 :         112 : lconv_char_member(struct lconv *lconv, int i)
      95                 :             : {
      96                 :         112 :         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                 :           7 : pg_localeconv_free(struct lconv *lconv)
     105                 :             : {
     106         [ +  + ]:         133 :         for (int i = 0; i < lengthof(table); ++i)
     107         [ +  + ]:         196 :                 if (table[i].is_string)
     108                 :          70 :                         free(*lconv_string_member(lconv, i));
     109                 :           7 : }
     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                 :             : pg_localeconv_from_langinfo(struct lconv *dst,
     117                 :             :                                                         locale_t monetary_locale,
     118                 :             :                                                         locale_t numeric_locale)
     119                 :             : {
     120                 :             :         for (int i = 0; i < lengthof(table); ++i)
     121                 :             :         {
     122                 :             :                 locale_t        locale;
     123                 :             : 
     124                 :             :                 locale = table[i].category == LC_NUMERIC ?
     125                 :             :                         numeric_locale : monetary_locale;
     126                 :             : 
     127                 :             :                 if (table[i].is_string)
     128                 :             :                 {
     129                 :             :                         char       *string;
     130                 :             : 
     131                 :             :                         string = nl_langinfo_l(table[i].item, locale);
     132                 :             :                         if (!(string = strdup(string)))
     133                 :             :                         {
     134                 :             :                                 pg_localeconv_free(dst);
     135                 :             :                                 errno = ENOMEM;
     136                 :             :                                 return -1;
     137                 :             :                         }
     138                 :             :                         *lconv_string_member(dst, i) = string;
     139                 :             :                 }
     140                 :             :                 else
     141                 :             :                 {
     142                 :             :                         *lconv_char_member(dst, i) =
     143                 :             :                                 *nl_langinfo_l(table[i].item, locale);
     144                 :             :                 }
     145                 :             :         }
     146                 :             : 
     147                 :             :         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                 :          14 : pg_localeconv_copy_members(struct lconv *dst,
     156                 :             :                                                    struct lconv *src,
     157                 :             :                                                    int category)
     158                 :             : {
     159   [ +  +  -  -  :         266 :         for (int i = 0; i < lengthof(table); ++i)
                      + ]
     160                 :             :         {
     161         [ +  + ]:         252 :                 if (table[i].category != category)
     162                 :         126 :                         continue;
     163                 :             : 
     164         [ +  + ]:         126 :                 if (table[i].is_string)
     165                 :             :                 {
     166                 :          70 :                         char       *string;
     167                 :             : 
     168                 :          70 :                         string = *lconv_string_member(src, i);
     169         [ +  - ]:          70 :                         if (!(string = strdup(string)))
     170                 :             :                         {
     171                 :           0 :                                 pg_localeconv_free(dst);
     172                 :           0 :                                 errno = ENOMEM;
     173                 :           0 :                                 return -1;
     174                 :             :                         }
     175                 :          70 :                         *lconv_string_member(dst, i) = string;
     176         [ -  + ]:          70 :                 }
     177                 :             :                 else
     178                 :             :                 {
     179                 :          56 :                         *lconv_char_member(dst, i) = *lconv_char_member(src, i);
     180                 :             :                 }
     181                 :         126 :         }
     182                 :             : 
     183                 :          14 :         return 0;
     184                 :          14 : }
     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                 :           7 : 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                 :           7 :         locale_t        monetary_locale;
     299                 :           7 :         locale_t        numeric_locale;
     300                 :           7 :         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                 :           7 :         errno = ENOENT;
     309                 :           7 :         monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
     310         [ +  - ]:           7 :         if (monetary_locale == 0)
     311                 :           0 :                 return -1;
     312                 :           7 :         numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
     313         [ +  - ]:           7 :         if (numeric_locale == 0)
     314                 :             :         {
     315                 :           0 :                 freelocale(monetary_locale);
     316                 :           0 :                 return -1;
     317                 :             :         }
     318                 :             : 
     319                 :           7 :         memset(output, 0, sizeof(*output));
     320                 :             : #if defined(TRANSLATE_FROM_LANGINFO)
     321                 :             :         /* Copy from non-standard nl_langinfo_l() extended items. */
     322                 :             :         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                 :          14 :         result = pg_localeconv_copy_members(output,
     328                 :           7 :                                                                                 localeconv_l(monetary_locale),
     329                 :             :                                                                                 LC_MONETARY);
     330         [ -  + ]:           7 :         if (result == 0)
     331                 :             :         {
     332                 :             :                 /* Copy the LC_NUMERIC members from a thread-safe lconv object. */
     333                 :          14 :                 result = pg_localeconv_copy_members(output,
     334                 :           7 :                                                                                         localeconv_l(numeric_locale),
     335                 :             :                                                                                         LC_NUMERIC);
     336                 :           7 :         }
     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                 :           7 :         freelocale(monetary_locale);
     364                 :           7 :         freelocale(numeric_locale);
     365                 :             : 
     366                 :           7 :         return result;
     367                 :             : #endif                                                  /* !WIN32 */
     368                 :           7 : }
        

Generated by: LCOV version 2.3.2-1