Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/port/pg_localeconv_r.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * pg_localeconv_r.c
4
 *    Thread-safe implementations of localeconv()
5
 *
6
 * Portions Copyright (c) 1996-2025, 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
lconv_string_member(struct lconv *lconv, int i)
89
0
{
90
0
  return (char **) ((char *) lconv + table[i].offset);
91
0
}
92
93
static inline char *
94
lconv_char_member(struct lconv *lconv, int i)
95
0
{
96
0
  return (char *) lconv + table[i].offset;
97
0
}
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
pg_localeconv_free(struct lconv *lconv)
105
0
{
106
0
  for (int i = 0; i < lengthof(table); ++i)
107
0
    if (table[i].is_string)
108
0
      free(*lconv_string_member(lconv, i));
109
0
}
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
0
{
120
0
  for (int i = 0; i < lengthof(table); ++i)
121
0
  {
122
0
    locale_t  locale;
123
124
0
    locale = table[i].category == LC_NUMERIC ?
125
0
      numeric_locale : monetary_locale;
126
127
0
    if (table[i].is_string)
128
0
    {
129
0
      char     *string;
130
131
0
      string = nl_langinfo_l(table[i].item, locale);
132
0
      if (!(string = strdup(string)))
133
0
      {
134
0
        pg_localeconv_free(dst);
135
0
        errno = ENOMEM;
136
0
        return -1;
137
0
      }
138
0
      *lconv_string_member(dst, i) = string;
139
0
    }
140
0
    else
141
0
    {
142
0
      *lconv_char_member(dst, i) =
143
0
        *nl_langinfo_l(table[i].item, locale);
144
0
    }
145
0
  }
146
147
0
  return 0;
148
0
}
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
pg_localeconv_r(const char *lc_monetary,
232
        const char *lc_numeric,
233
        struct lconv *output)
234
0
{
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
0
  locale_t  monetary_locale;
299
0
  locale_t  numeric_locale;
300
0
  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
0
  errno = ENOENT;
309
0
  monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
310
0
  if (monetary_locale == 0)
311
0
    return -1;
312
0
  numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
313
0
  if (numeric_locale == 0)
314
0
  {
315
0
    freelocale(monetary_locale);
316
0
    return -1;
317
0
  }
318
319
0
  memset(output, 0, sizeof(*output));
320
0
#if defined(TRANSLATE_FROM_LANGINFO)
321
  /* Copy from non-standard nl_langinfo_l() extended items. */
322
0
  result = pg_localeconv_from_langinfo(output,
323
0
                     monetary_locale,
324
0
                     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
0
  freelocale(monetary_locale);
364
0
  freelocale(numeric_locale);
365
366
0
  return result;
367
0
#endif              /* !WIN32 */
368
0
}