/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 | } |