#include "xlocale_private.h"
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include "ldpart.h"
#define NMBSTATET 10
#define C_LOCALE_INITIALIZER { \
0, NULL, \
{}, {}, {}, {}, {}, \
{}, {}, {}, {}, {}, \
XMAGIC, \
1, 0, 0, 0, 0, 0, 1, 1, 0, \
NULL, \
&_DefaultRuneXLocale, \
}
static char C[] = "C";
static const struct _xlocale __c_locale = C_LOCALE_INITIALIZER;
const locale_t _c_locale = (const locale_t)&__c_locale;
__private_extern__ struct _xlocale __global_locale = C_LOCALE_INITIALIZER;
__private_extern__ pthread_key_t __locale_key = (pthread_key_t)-1;
extern int __collate_load_tables(const char *, locale_t);
extern int __detect_path_locale(void);
extern const char *__get_locale_env(int);
extern int __messages_load_locale(const char *, locale_t);
extern int __monetary_load_locale(const char *, locale_t);
extern int __numeric_load_locale(const char *, locale_t);
extern int __setrunelocale(const char *, locale_t);
extern int __time_load_locale(const char *, locale_t);
static void _releaselocale(locale_t loc);
static inline __attribute__((always_inline)) int
_checkencoding(const char *encoding)
{
return (encoding && (strlen(encoding) > ENCODING_LEN
|| (encoding[0] == '.' && (encoding[1] == 0
|| (encoding[1] == '.' && encoding[2] == 0)))
|| strchr(encoding, '/') != NULL)) ? -1 : 0;
}
static inline __attribute__((always_inline)) int
_checklocale(const locale_t loc)
{
if (!loc)
return 0;
return (loc == LC_GLOBAL_LOCALE || loc->__magic == XMAGIC) ? 0 : -1;
}
static inline __attribute__((always_inline)) void
_copylocale(locale_t dst, const locale_t src)
{
memcpy(&dst->__magic, &src->__magic, sizeof(*dst) - offsetof(struct _xlocale, __magic));
}
static locale_t
_duplocale(locale_t loc)
{
locale_t new;
if ((new = (locale_t)malloc(sizeof(struct _xlocale))) == NULL)
return NULL;
new->__refcount = 1;
new->__free_extra = (__free_extra_t)_releaselocale;
if (loc == NULL)
loc = __current_locale();
else if (loc == LC_GLOBAL_LOCALE)
loc = &__global_locale;
else if (loc == &__c_locale) {
*new = __c_locale;
return new;
}
_copylocale(new, loc);
bzero(&new->__mbs_mblen, offsetof(struct _xlocale, __magic)
- offsetof(struct _xlocale, __mbs_mblen));
XL_RETAIN(new->__lc_collate);
XL_RETAIN(new->__lc_ctype);
XL_RETAIN(new->__lc_messages);
XL_RETAIN(new->__lc_monetary);
XL_RETAIN(new->__lc_numeric);
XL_RETAIN(new->__lc_numeric_loc);
XL_RETAIN(new->__lc_time);
XL_RETAIN(new->__lc_localeconv);
return new;
}
static int
_modifylocale(locale_t loc, int mask, __const char *locale)
{
int m, ret;
const char *enc = NULL;
char *oenc;
if (!locale)
locale = C;
ret = __detect_path_locale();
if (ret) {
errno = ret;
return -1;
}
if (*locale)
enc = locale;
for(m = 1; m <= _LC_LAST_MASK; m <<= 1) {
if (m & mask) {
switch(m) {
case LC_COLLATE_MASK:
if (!*locale) {
enc = __get_locale_env(LC_COLLATE);
if (_checkencoding(enc) < 0) {
errno = EINVAL;
return -1;
}
}
oenc = (loc->__collate_load_error ? C : loc->__lc_collate->__encoding);
if (strcmp(enc, oenc) != 0 && __collate_load_tables(enc, loc) == _LDP_ERROR)
return -1;
break;
case LC_CTYPE_MASK:
if (!*locale) {
enc = __get_locale_env(LC_CTYPE);
if (_checkencoding(enc) < 0) {
errno = EINVAL;
return -1;
}
}
if (strcmp(enc, loc->__lc_ctype->__ctype_encoding) != 0) {
if ((ret = __setrunelocale(enc, loc)) != 0) {
errno = ret;
return -1;
}
if (loc->__numeric_fp_cvt == LC_NUMERIC_FP_SAME_LOCALE)
loc->__numeric_fp_cvt = LC_NUMERIC_FP_UNINITIALIZED;
}
break;
case LC_MESSAGES_MASK:
if (!*locale) {
enc = __get_locale_env(LC_MESSAGES);
if (_checkencoding(enc) < 0) {
errno = EINVAL;
return -1;
}
}
oenc = (loc->_messages_using_locale ? loc->__lc_messages->_messages_locale_buf : C);
if (strcmp(enc, oenc) != 0 && __messages_load_locale(enc, loc) == _LDP_ERROR)
return -1;
break;
case LC_MONETARY_MASK:
if (!*locale) {
enc = __get_locale_env(LC_MONETARY);
if (_checkencoding(enc) < 0) {
errno = EINVAL;
return -1;
}
}
oenc = (loc->_monetary_using_locale ? loc->__lc_monetary->_monetary_locale_buf : C);
if (strcmp(enc, oenc) != 0 && __monetary_load_locale(enc, loc) == _LDP_ERROR)
return -1;
break;
case LC_NUMERIC_MASK:
if (!*locale) {
enc = __get_locale_env(LC_NUMERIC);
if (_checkencoding(enc) < 0) {
errno = EINVAL;
return -1;
}
}
oenc = (loc->_numeric_using_locale ? loc->__lc_numeric->_numeric_locale_buf : C);
if (strcmp(enc, oenc) != 0) {
if (__numeric_load_locale(enc, loc) == _LDP_ERROR)
return -1;
loc->__numeric_fp_cvt = LC_NUMERIC_FP_UNINITIALIZED;
XL_RELEASE(loc->__lc_numeric_loc);
loc->__lc_numeric_loc = NULL;
}
break;
case LC_TIME_MASK:
if (!*locale) {
enc = __get_locale_env(LC_TIME);
if (_checkencoding(enc) < 0) {
errno = EINVAL;
return -1;
}
}
oenc = (loc->_time_using_locale ? loc->__lc_time->_time_locale_buf : C);
if (strcmp(enc, oenc) != 0 && __time_load_locale(enc, loc) == _LDP_ERROR)
return -1;
break;
}
}
}
return 0;
}
static void
_releaselocale(locale_t loc)
{
XL_RELEASE(loc->__lc_collate);
XL_RELEASE(loc->__lc_ctype);
XL_RELEASE(loc->__lc_messages);
XL_RELEASE(loc->__lc_monetary);
XL_RELEASE(loc->__lc_numeric);
XL_RELEASE(loc->__lc_numeric_loc);
XL_RELEASE(loc->__lc_time);
XL_RELEASE(loc->__lc_localeconv);
}
locale_t
duplocale(locale_t loc)
{
if (_checklocale(loc) < 0) {
errno = EINVAL;
return NULL;
}
return _duplocale(loc);
}
int
freelocale(locale_t loc)
{
if (!loc || _checklocale(loc) < 0 || loc == &__global_locale
|| loc == LC_GLOBAL_LOCALE || loc == &__c_locale) {
errno = EINVAL;
return -1;
}
XL_RELEASE(loc);
return 0;
}
locale_t
newlocale(int mask, __const char *locale, locale_t base)
{
locale_t new;
int lcmask = (mask & LC_ALL_MASK);
if (_checkencoding(locale) < 0) {
errno = EINVAL;
return NULL;
}
if (lcmask == LC_ALL_MASK)
base = (locale_t)&__c_locale;
else if (_checklocale(base) < 0) {
errno = EINVAL;
return NULL;
}
new = _duplocale(base);
if (new == NULL)
return NULL;
if (lcmask == 0 || (lcmask == LC_ALL_MASK && locale == NULL))
return new;
if (_modifylocale(new, lcmask, locale) < 0) {
freelocale(new);
return NULL;
}
return new;
}
__private_extern__ locale_t
__numeric_ctype(locale_t loc)
{
switch(loc->__numeric_fp_cvt) {
case LC_NUMERIC_FP_UNINITIALIZED: {
const char *ctype = loc->__lc_ctype->__ctype_encoding;
const char *numeric = (loc->_numeric_using_locale ? loc->__lc_numeric->_numeric_locale_buf : C);
if (strcmp(ctype, numeric) == 0) {
loc->__numeric_fp_cvt = LC_NUMERIC_FP_SAME_LOCALE;
return loc;
} else {
loc->__lc_numeric_loc = newlocale(LC_CTYPE_MASK, numeric, &__c_locale);
if (loc->__lc_numeric_loc) {
loc->__numeric_fp_cvt = LC_NUMERIC_FP_USE_LOCALE;
return loc->__lc_numeric_loc;
} else {
loc->__numeric_fp_cvt = LC_NUMERIC_FP_SAME_LOCALE;
return loc;
}
}
}
case LC_NUMERIC_FP_SAME_LOCALE:
return loc;
case LC_NUMERIC_FP_USE_LOCALE:
return loc->__lc_numeric_loc;
}
return loc;
}
const char *
querylocale(int mask, locale_t loc)
{
int m;
if (_checklocale(loc) < 0 || (mask & LC_ALL_MASK) == 0) {
errno = EINVAL;
return NULL;
}
if (loc == NULL)
loc = __current_locale();
else if (loc == LC_GLOBAL_LOCALE)
loc = &__global_locale;
for(m = 1; m <= _LC_LAST_MASK; m <<= 1) {
if (m & mask) {
switch(m) {
case LC_COLLATE_MASK:
return (loc->__collate_load_error ? C : loc->__lc_collate->__encoding);
case LC_CTYPE_MASK:
return loc->__lc_ctype->__ctype_encoding;
case LC_MESSAGES_MASK:
return (loc->_messages_using_locale ? loc->__lc_messages->_messages_locale_buf : C);
case LC_MONETARY_MASK:
return (loc->_monetary_using_locale ? loc->__lc_monetary->_monetary_locale_buf : C);
case LC_NUMERIC_MASK:
return (loc->_numeric_using_locale ? loc->__lc_numeric->_numeric_locale_buf : C);
case LC_TIME_MASK:
return (loc->_time_using_locale ? loc->__lc_time->_time_locale_buf : C);
}
}
}
errno = EINVAL;
return NULL;
}
locale_t
uselocale(locale_t loc)
{
locale_t orig;
if (loc == NULL)
orig = (locale_t)pthread_getspecific(__locale_key);
else {
if (_checklocale(loc) < 0) {
errno = EINVAL;
return NULL;
}
if (loc == &__global_locale)
loc = LC_GLOBAL_LOCALE;
orig = pthread_getspecific(__locale_key);
pthread_setspecific(__locale_key, loc == LC_GLOBAL_LOCALE ? NULL : loc);
}
return (orig ? orig : LC_GLOBAL_LOCALE);
}
int
___mb_cur_max(void)
{
return __current_locale()->__lc_ctype->__mb_cur_max;
}
int
___mb_cur_max_l(locale_t loc)
{
return __locale_ptr(loc)->__lc_ctype->__mb_cur_max;
}
extern int __pthread_tsd_first;
__private_extern__ void
__xlocale_init(void)
{
if (__locale_key == (pthread_key_t)-1)
__locale_key = __pthread_tsd_first;
}