PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
pg_localeconv_r.c
Go to the documentation of this file.
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
34{
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 */
63static 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
87static inline char **
88lconv_string_member(struct lconv *lconv, int i)
89{
90 return (char **) ((char *) lconv + table[i].offset);
91}
92
93static inline char *
94lconv_char_member(struct lconv *lconv, int i)
95{
96 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 */
103void
104pg_localeconv_free(struct lconv *lconv)
105{
106 for (int i = 0; i < lengthof(table); ++i)
107 if (table[i].is_string)
108 free(*lconv_string_member(lconv, i));
109}
110
111#ifdef TRANSLATE_FROM_LANGINFO
112/*
113 * Fill in struct lconv members using the equivalent nl_langinfo_l() items.
114 */
115static int
116pg_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 {
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 {
135 errno = ENOMEM;
136 return -1;
137 }
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 */
154static int
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 {
172 errno = ENOMEM;
173 return -1;
174 }
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 */
230int
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
276exit:
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 errno = ENOENT;
309 monetary_locale = newlocale(LC_ALL_MASK, lc_monetary, 0);
310 if (monetary_locale == 0)
311 return -1;
312 numeric_locale = newlocale(LC_ALL_MASK, lc_numeric, 0);
313 if (numeric_locale == 0)
314 {
315 freelocale(monetary_locale);
316 return -1;
317 }
318
319 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. */
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. */
334 localeconv_l(numeric_locale),
335 LC_NUMERIC);
336 }
337#else
338 /* We have nothing better than standard POSIX facilities. */
339 {
341 locale_t save_locale;
342
343 pthread_mutex_lock(&big_lock);
344 /* Copy the LC_MONETARY members. */
345 save_locale = uselocale(monetary_locale);
347 localeconv(),
348 LC_MONETARY);
349 if (result == 0)
350 {
351 /* Copy the LC_NUMERIC members. */
352 uselocale(numeric_locale);
354 localeconv(),
355 LC_NUMERIC);
356 }
357 pthread_mutex_unlock(&big_lock);
358
359 uselocale(save_locale);
360 }
361#endif
362
363 freelocale(monetary_locale);
364 freelocale(numeric_locale);
365
366 return result;
367#endif /* !WIN32 */
368}
#define lengthof(array)
Definition: c.h:759
static char * thousands_sep
Definition: print.c:50
static char * decimal_point
Definition: print.c:48
#define free(a)
Definition: header.h:65
FILE * output
static char * locale
Definition: initdb.c:140
static char * lc_numeric
Definition: initdb.c:144
static char * lc_monetary
Definition: initdb.c:143
int i
Definition: isn.c:77
#define LCONV_S(c, n, i)
static char * lconv_char_member(struct lconv *lconv, int i)
#define LCONV_C(c, n, i)
int pg_localeconv_r(const char *lc_monetary, const char *lc_numeric, struct lconv *output)
static int pg_localeconv_copy_members(struct lconv *dst, struct lconv *src, int category)
static char ** lconv_string_member(struct lconv *lconv, int i)
static const struct lconv_member_info table[]
void pg_localeconv_free(struct lconv *lconv)
char string[11]
Definition: preproc-type.c:52
int pthread_mutex_unlock(pthread_mutex_t *mp)
Definition: pthread-win32.c:60
int pthread_mutex_lock(pthread_mutex_t *mp)
Definition: pthread-win32.c:42
#define PTHREAD_MUTEX_INITIALIZER
Definition: pthread-win32.h:16
#define locale_t
Definition: win32_port.h:432
#define setlocale(a, b)
Definition: win32_port.h:475