1/*
2 * Copyright (c) 2005, 2008 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "xlocale_private.h"
25#include <errno.h>
26#include <stddef.h>
27#include <string.h>
28#include "ldpart.h"
29
30#define NMBSTATET	10
31#define C_LOCALE_INITIALIZER	{	\
32	0, XPERMANENT,			\
33	{}, {}, {}, {}, {},		\
34	{}, {}, {}, {}, {},		\
35	LOCK_INITIALIZER,		\
36	XMAGIC,				\
37	1, 0, 0, 0, 0, 0, 1, 1, 0,	\
38	NULL,				\
39	&_DefaultRuneXLocale,		\
40}
41
42static char C[] = "C";
43static struct _xlocale __c_locale = C_LOCALE_INITIALIZER;
44const locale_t _c_locale = (const locale_t)&__c_locale;
45struct _xlocale __global_locale = C_LOCALE_INITIALIZER;
46pthread_key_t __locale_key = (pthread_key_t)-1;
47
48extern int __collate_load_tables(const char *, locale_t);
49extern int __detect_path_locale(void);
50extern const char *__get_locale_env(int);
51extern int __messages_load_locale(const char *, locale_t);
52extern int __monetary_load_locale(const char *, locale_t);
53extern int __numeric_load_locale(const char *, locale_t);
54extern int __setrunelocale(const char *, locale_t);
55extern int __time_load_locale(const char *, locale_t);
56
57static void _releaselocale(locale_t loc);
58
59/*
60 * check that the encoding is the right size, isn't . or .. and doesn't
61 * contain any slashes
62 */
63static inline __attribute__((always_inline)) int
64_checkencoding(const char *encoding)
65{
66	return (encoding && (strlen(encoding) > ENCODING_LEN
67	  || (encoding[0] == '.' && (encoding[1] == 0
68	  || (encoding[1] == '.' && encoding[2] == 0)))
69	  || strchr(encoding, '/') != NULL)) ? -1 : 0;
70}
71
72/*
73 * check that the locale has the right magic number
74 */
75static inline __attribute__((always_inline)) int
76_checklocale(const locale_t loc)
77{
78	if (!loc)
79		return 0;
80	return (loc == LC_GLOBAL_LOCALE || loc->__magic == XMAGIC) ? 0 : -1;
81}
82
83/*
84 * copy a locale_t except anything before the magic value
85 */
86static inline __attribute__((always_inline)) void
87_copylocale(locale_t dst, const locale_t src)
88{
89	memcpy(&dst->__magic, &src->__magic, sizeof(*dst) - offsetof(struct _xlocale, __magic));
90}
91
92/*
93 * Make a copy of a locale_t, locking/unlocking the source.
94 * A NULL locale_t means to make a copy of the current
95 * locale while LC_GLOBAL_LOCALE means to copy the global locale.  If
96 * &__c_locale is passed (meaning a C locale is desired), just make
97 * a copy.
98 */
99static locale_t
100_duplocale(locale_t loc)
101{
102	locale_t new;
103
104	if ((new = (locale_t)malloc(sizeof(struct _xlocale))) == NULL)
105		return NULL;
106	new->__refcount = 1;
107	new->__free_extra = (__free_extra_t)_releaselocale;
108	new->__lock = LOCK_INITIALIZER;
109	if (loc == NULL)
110		loc = __current_locale();
111	else if (loc == LC_GLOBAL_LOCALE)
112		loc = &__global_locale;
113	else if (loc == &__c_locale) {
114		*new = __c_locale;
115		new->__refcount = 1;
116		new->__free_extra = (__free_extra_t)_releaselocale;
117		new->__lock = LOCK_INITIALIZER;
118		return new;
119	}
120	XL_LOCK(loc);
121	_copylocale(new, loc);
122	XL_UNLOCK(loc);
123	/* __mbs_mblen is the first of NMBSTATET mbstate_t buffers */
124	bzero(&new->__mbs_mblen, offsetof(struct _xlocale, __magic)
125	    - offsetof(struct _xlocale, __mbs_mblen));
126	/* collate */
127	XL_RETAIN(new->__lc_collate);
128	/* ctype */
129	XL_RETAIN(new->__lc_ctype);
130	/* messages */
131	XL_RETAIN(new->__lc_messages);
132	/* monetary */
133	XL_RETAIN(new->__lc_monetary);
134	/* numeric */
135	XL_RETAIN(new->__lc_numeric);
136	XL_RETAIN(new->__lc_numeric_loc);
137	/* time */
138	XL_RETAIN(new->__lc_time);
139
140	return new;
141}
142
143/*
144 * Modify a locale_t, setting the parts specified in the mask
145 * to the locale specified by the string.  If the string is NULL, the C
146 * locale is used.  If the string is empty, the value is determined from
147 * the environment.  -1 is returned on error, and loc is in a partial state.
148 */
149static int
150_modifylocale(locale_t loc, int mask, __const char *locale)
151{
152	int m, ret;
153	const char *enc = NULL;
154	char *oenc;
155
156	if (!locale)
157		locale = C;
158
159	ret = __detect_path_locale();
160	if (ret) {
161		errno = ret;
162		return -1;
163	}
164
165	if (*locale)
166		enc = locale;
167	for(m = 1; m <= _LC_LAST_MASK; m <<= 1) {
168		if (m & mask) {
169			switch(m) {
170			case LC_COLLATE_MASK:
171				if (!*locale) {
172					enc = __get_locale_env(LC_COLLATE);
173					if (_checkencoding(enc) < 0) {
174						errno = EINVAL;
175						return -1;
176					}
177				}
178				oenc = (loc->__collate_load_error ? C : loc->__lc_collate->__encoding);
179				if (strcmp(enc, oenc) != 0 && __collate_load_tables(enc, loc) == _LDP_ERROR)
180					return -1;
181				break;
182			case LC_CTYPE_MASK:
183				if (!*locale) {
184					enc = __get_locale_env(LC_CTYPE);
185					if (_checkencoding(enc) < 0) {
186						errno = EINVAL;
187						return -1;
188					}
189				}
190				if (strcmp(enc, loc->__lc_ctype->__ctype_encoding) != 0) {
191					if ((ret = __setrunelocale(enc, loc)) != 0) {
192						errno = ret;
193						return -1;
194					}
195					if (loc->__numeric_fp_cvt == LC_NUMERIC_FP_SAME_LOCALE)
196						loc->__numeric_fp_cvt = LC_NUMERIC_FP_UNINITIALIZED;
197				}
198				break;
199			case LC_MESSAGES_MASK:
200				if (!*locale) {
201					enc = __get_locale_env(LC_MESSAGES);
202					if (_checkencoding(enc) < 0) {
203						errno = EINVAL;
204						return -1;
205					}
206				}
207				oenc = (loc->_messages_using_locale ? loc->__lc_messages->_messages_locale_buf : C);
208				if (strcmp(enc, oenc) != 0 && __messages_load_locale(enc, loc) == _LDP_ERROR)
209					return -1;
210				break;
211			case LC_MONETARY_MASK:
212				if (!*locale) {
213					enc = __get_locale_env(LC_MONETARY);
214					if (_checkencoding(enc) < 0) {
215						errno = EINVAL;
216						return -1;
217					}
218				}
219				oenc = (loc->_monetary_using_locale ? loc->__lc_monetary->_monetary_locale_buf : C);
220				if (strcmp(enc, oenc) != 0 && __monetary_load_locale(enc, loc) == _LDP_ERROR)
221					return -1;
222				break;
223			case LC_NUMERIC_MASK:
224				if (!*locale) {
225					enc = __get_locale_env(LC_NUMERIC);
226					if (_checkencoding(enc) < 0) {
227						errno = EINVAL;
228						return -1;
229					}
230				}
231				oenc = (loc->_numeric_using_locale ? loc->__lc_numeric->_numeric_locale_buf : C);
232				if (strcmp(enc, oenc) != 0) {
233					if (__numeric_load_locale(enc, loc) == _LDP_ERROR)
234						return -1;
235					loc->__numeric_fp_cvt = LC_NUMERIC_FP_UNINITIALIZED;
236					XL_RELEASE(loc->__lc_numeric_loc);
237					loc->__lc_numeric_loc = NULL;
238				}
239				break;
240			case LC_TIME_MASK:
241				if (!*locale) {
242					enc = __get_locale_env(LC_TIME);
243					if (_checkencoding(enc) < 0) {
244						errno = EINVAL;
245						return -1;
246					}
247				}
248				oenc = (loc->_time_using_locale ? loc->__lc_time->_time_locale_buf : C);
249				if (strcmp(enc, oenc) != 0 && __time_load_locale(enc, loc) == _LDP_ERROR)
250					return -1;
251				break;
252			}
253		}
254	}
255	return 0;
256}
257
258/*
259 * release all the memory objects (the memory will be freed when the refcount
260 * becomes zero)
261 */
262static void
263_releaselocale(locale_t loc)
264{
265	/* collate */
266	XL_RELEASE(loc->__lc_collate);
267	/* ctype */
268	XL_RELEASE(loc->__lc_ctype);
269	/* messages */
270	XL_RELEASE(loc->__lc_messages);
271	/* monetary */
272	XL_RELEASE(loc->__lc_monetary);
273	/* numeric */
274	XL_RELEASE(loc->__lc_numeric);
275	XL_RELEASE(loc->__lc_numeric_loc);
276	/* time */
277	XL_RELEASE(loc->__lc_time);
278}
279
280/*
281 * EXTERNAL: Duplicate a (non-NULL) locale_t.  LC_GLOBAL_LOCALE means the
282 * global locale, while NULL means the current locale.  NULL is returned
283 * on error.
284 */
285locale_t
286duplocale(locale_t loc)
287{
288	if (_checklocale(loc) < 0) {
289		errno = EINVAL;
290		return NULL;
291	}
292	return _duplocale(loc);
293}
294
295/*
296 * EXTERNAL: Free a locale_t, releasing all memory objects.  Don't free
297 * illegal locale_t's or the global locale.
298 */
299int
300freelocale(locale_t loc)
301{
302	if (!loc || _checklocale(loc) < 0 || loc == &__global_locale
303	    || loc == LC_GLOBAL_LOCALE || loc == &__c_locale) {
304		errno = EINVAL;
305		return -1;
306	}
307	XL_RELEASE(loc);
308	return 0;
309}
310
311/*
312 * EXTERNAL: Create a new locale_t, based on the base locale_t, and modified
313 * by the mask and locale string.  If the base is NULL, the current locale
314 * is used as the base.  If locale is NULL, changes are made from the C locale
315 * for categories set in mask.
316 */
317locale_t
318newlocale(int mask, __const char *locale, locale_t base)
319{
320	locale_t new;
321	int lcmask = (mask & LC_ALL_MASK);
322
323	if (_checkencoding(locale) < 0) {
324		errno = EINVAL;
325		return NULL;
326	}
327	if (lcmask == LC_ALL_MASK)
328		base = (locale_t)&__c_locale;
329	else if (_checklocale(base) < 0) {
330		errno = EINVAL;
331		return NULL;
332	}
333	new = _duplocale(base);
334	if (new == NULL)
335		return NULL;
336	if (lcmask == 0 || (lcmask == LC_ALL_MASK && locale == NULL))
337		return new;
338	if (_modifylocale(new, lcmask, locale) < 0) {
339		freelocale(new);
340		return NULL;
341	}
342	return new;
343}
344
345/*
346 * PRIVATE EXTERNAL: Returns the locale that can be used by wcstod and
347 * family, to convert the wide character string to a multi-byte string
348 * (the LC_NUMERIC and LC_CTYPE locales may be different).
349 */
350__private_extern__ locale_t
351__numeric_ctype(locale_t loc)
352{
353	switch(loc->__numeric_fp_cvt) {
354	case LC_NUMERIC_FP_UNINITIALIZED: {
355		const char *ctype = loc->__lc_ctype->__ctype_encoding;
356		const char *numeric = (loc->_numeric_using_locale ? loc->__lc_numeric->_numeric_locale_buf : C);
357		if (strcmp(ctype, numeric) == 0) {
358			loc->__numeric_fp_cvt = LC_NUMERIC_FP_SAME_LOCALE;
359			return loc;
360		} else {
361			loc->__lc_numeric_loc = newlocale(LC_CTYPE_MASK, numeric, (locale_t)&__c_locale);
362			if (loc->__lc_numeric_loc) {
363				loc->__numeric_fp_cvt = LC_NUMERIC_FP_USE_LOCALE;
364				return loc->__lc_numeric_loc;
365			} else { /* shouldn't happen, but just use the same locale */
366				loc->__numeric_fp_cvt = LC_NUMERIC_FP_SAME_LOCALE;
367				return loc;
368			}
369		}
370	}
371	case LC_NUMERIC_FP_SAME_LOCALE:
372		return loc;
373	case LC_NUMERIC_FP_USE_LOCALE:
374		return loc->__lc_numeric_loc;
375	}
376	return loc;	/* shouldn't happen */
377}
378
379/*
380 * EXTERNAL: Returns the locale string for the part specified in mask.  The
381 * least significant bit is used.  If loc is NULL, the current per-thread
382 * locale is used.
383 */
384const char *
385querylocale(int mask, locale_t loc)
386{
387	int m;
388	const char *ret;
389
390	if (_checklocale(loc) < 0 || (mask & LC_ALL_MASK) == 0) {
391		errno = EINVAL;
392		return NULL;
393	}
394	DEFAULT_CURRENT_LOCALE(loc);
395	m = ffs(mask);
396	if (m == 0 || m > _LC_NUM_MASK) {
397		errno = EINVAL;
398		return NULL;
399	}
400	XL_LOCK(loc);
401	switch(1 << (m - 1)) {
402	case LC_COLLATE_MASK:
403		ret = (loc->__collate_load_error ? C : loc->__lc_collate->__encoding);
404		break;
405	case LC_CTYPE_MASK:
406		ret = loc->__lc_ctype->__ctype_encoding;
407		break;
408	case LC_MESSAGES_MASK:
409		ret = (loc->_messages_using_locale ? loc->__lc_messages->_messages_locale_buf : C);
410		break;
411	case LC_MONETARY_MASK:
412		ret = (loc->_monetary_using_locale ? loc->__lc_monetary->_monetary_locale_buf : C);
413		break;
414	case LC_NUMERIC_MASK:
415		ret = (loc->_numeric_using_locale ? loc->__lc_numeric->_numeric_locale_buf : C);
416		break;
417	case LC_TIME_MASK:
418		ret = (loc->_time_using_locale ? loc->__lc_time->_time_locale_buf : C);
419		break;
420	default:
421		/* should never get here */
422		XL_UNLOCK(loc);
423		errno = EINVAL;
424		return NULL;
425	}
426	XL_UNLOCK(loc);
427	return ret;
428}
429
430/*
431 * EXTERNAL: Set the thread-specific locale.  The previous locale is returned.
432 * Use LC_GLOBAL_LOCALE to set the global locale.  LC_GLOBAL_LOCALE
433 * may also be returned if there was no previous thread-specific locale in
434 * effect.  If loc is NULL, the current locale is returned, but no locale
435 * chance is made.  NULL is returned on error.
436 */
437locale_t
438uselocale(locale_t loc)
439{
440	locale_t orig;
441
442	if (loc == NULL)
443		orig = (locale_t)pthread_getspecific(__locale_key);
444	else {
445		if (_checklocale(loc) < 0) {
446			errno = EINVAL;
447			return NULL;
448		}
449		if (loc == LC_GLOBAL_LOCALE ||
450		    loc == &__global_locale)	/* should never happen */
451			loc = NULL;
452		XL_RETAIN(loc);
453		orig = pthread_getspecific(__locale_key);
454		pthread_setspecific(__locale_key, loc);
455		XL_RELEASE(orig);
456	}
457	return (orig ? orig : LC_GLOBAL_LOCALE);
458}
459
460/*
461 * EXTERNAL: Used by the MB_CUR_MAX macro to determine the thread-specific
462 * value.
463 */
464int
465___mb_cur_max(void)
466{
467	return __current_locale()->__lc_ctype->__mb_cur_max;
468}
469
470/*
471 * EXTERNAL: Used by the MB_CUR_MAX_L macro to determine the thread-specific
472 * value, from the given locale_t.
473 */
474int
475___mb_cur_max_l(locale_t loc)
476{
477	return __locale_ptr(loc)->__lc_ctype->__mb_cur_max;
478}
479
480static void
481__xlocale_release(void *loc)
482{
483	XL_RELEASE((locale_t)loc);
484}
485
486/*
487 * Called from the Libc initializer to setup the thread-specific key.
488 */
489__private_extern__ void
490__xlocale_init(void)
491{
492	if (__locale_key == (pthread_key_t)-1) {
493		__locale_key = __LIBC_PTHREAD_KEY_XLOCALE;
494		pthread_key_init_np(__locale_key, __xlocale_release);
495	}
496}
497
498