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