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