#ifndef _LCONV_C99
#define _LCONV_C99
#endif
#include "lint.h"
#include <atomic.h>
#include <locale.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include "libc.h"
#include "mtlib.h"
#include "tsd.h"
#include "localeimpl.h"
#include "lctype.h"
typedef struct locdata *(*loadfn_t)(const char *);
static const loadfn_t loaders[LC_ALL] = {
__lc_ctype_load,
__lc_numeric_load,
__lc_time_load,
__lc_collate_load,
__lc_monetary_load,
__lc_messages_load,
};
extern struct lc_monetary lc_monetary_posix;
extern struct lc_numeric lc_numeric_posix;
extern struct lc_messages lc_messages_posix;
extern struct lc_time lc_time_posix;
extern struct lc_ctype lc_ctype_posix;
extern struct lc_collate lc_collate_posix;
extern struct _RuneLocale _DefaultRuneLocale;
static struct _locale posix_locale = {
.locdata = {
&__posix_ctype_locdata,
&__posix_numeric_locdata,
&__posix_time_locdata,
&__posix_collate_locdata,
&__posix_monetary_locdata,
&__posix_messages_locdata,
},
.locname = "C",
.ctype = &lc_ctype_posix,
.numeric = &lc_numeric_posix,
.collate = &lc_collate_posix,
.monetary = &lc_monetary_posix,
.messages = &lc_messages_posix,
.time = &lc_time_posix,
.runelocale = &_DefaultRuneLocale,
};
locale_t ___global_locale = &posix_locale;
locale_t
__global_locale(void)
{
return (___global_locale);
}
static struct locdata cutf_locdata[LC_ALL] = {
{ "C.UTF-8", NULL },
{ "C.UTF-8", &lc_numeric_posix },
{ "C.UTF-8", &lc_time_posix },
{ "C.UTF-8", &lc_collate_posix },
{ "C.UTF-8", &lc_monetary_posix },
{ "C.UTF-8", &lc_messages_posix },
};
#define NUM_CATS 7
static char *categories[7] = {
"LC_CTYPE",
"LC_NUMERIC",
"LC_TIME",
"LC_COLLATE",
"LC_MONETARY",
"LC_MESSAGES",
"LC_ALL",
};
static const char *get_locale_env(int);
static struct locdata *locdata_get(int, const char *);
static struct locdata *locdata_get_cache(int, const char *);
static locale_t mklocname(locale_t);
struct locdata *
__locdata_alloc(const char *name, size_t memsz)
{
struct locdata *ldata;
if ((ldata = lmalloc(sizeof (*ldata))) == NULL) {
return (NULL);
}
if ((ldata->l_data[0] = libc_malloc(memsz)) == NULL) {
lfree(ldata, sizeof (*ldata));
errno = ENOMEM;
return (NULL);
}
(void) strlcpy(ldata->l_lname, name, sizeof (ldata->l_lname));
return (ldata);
}
void
__locdata_free(struct locdata *ldata)
{
for (int i = 0; i < NLOCDATA; i++)
libc_free(ldata->l_data[i]);
if (ldata->l_map != NULL && ldata->l_map_len)
(void) munmap(ldata->l_map, ldata->l_map_len);
lfree(ldata, sizeof (*ldata));
}
static struct locdata *cache_data[LC_ALL];
static struct locdata *cat_data[LC_ALL];
static mutex_t cache_lock = DEFAULTMUTEX;
static struct locdata *
locdata_get_cache(int category, const char *locname)
{
struct locdata *loc;
if (category < 0 || category >= LC_ALL)
return (NULL);
lmutex_lock(&cache_lock);
loc = cache_data[category];
if ((loc != NULL) && (strcmp(loc->l_lname, locname) == 0)) {
lmutex_unlock(&cache_lock);
return (loc);
}
for (loc = cat_data[category]; loc != NULL; loc = loc->l_next) {
if (strcmp(locname, loc->l_lname) == 0) {
break;
}
}
if (loc == NULL) {
lmutex_unlock(&cache_lock);
loc = (*loaders[category])(locname);
lmutex_lock(&cache_lock);
if (loc != NULL)
(void) strlcpy(loc->l_lname, locname,
sizeof (loc->l_lname));
}
if (loc != NULL) {
cache_data[category] = loc;
if (!loc->l_cached) {
loc->l_cached = 1;
loc->l_next = cat_data[category];
cat_data[category] = loc;
}
}
lmutex_unlock(&cache_lock);
return (loc);
}
static const struct {
const char *alias;
const char *name;
} cmalias[] = {
{ "utf8", "UTF-8" },
{ "iso88591", "ISO8859-1" },
{ "iso885915", "ISO8859-15" },
{ "gb18030", "GB18030" },
{ "koi8r", "KOI8-R" },
{ NULL, NULL }
};
static struct locdata *
locdata_get(int category, const char *locname)
{
char scratch[ENCODING_LEN + 1];
char scratch2[ENCODING_LEN + 1];
const char *sep, *cm;
int cnt;
int len;
int i;
if (locname == NULL || *locname == 0) {
locname = get_locale_env(category);
}
if ((sep = strchr(locname, '/')) != NULL) {
for (cnt = category; cnt && sep != NULL; cnt--) {
locname = sep + 1;
sep = strchr(locname, '/');
}
if (sep) {
len = sep - locname + 1;
if (len >= sizeof (scratch)) {
len = sizeof (scratch);
}
} else {
len = sizeof (scratch);
}
(void) strlcpy(scratch, locname, len);
locname = scratch;
} else if ((sep = strchr(locname, ';')) != NULL) {
const char *catname = categories[category];
size_t catlen = strlen(catname);
const char *locnameend = locname + strlen(locname);
const char *locp = locname;
const char *endp = sep;
const char *value = NULL;
const char *endvalue = NULL;
size_t copylen;
for (;;) {
if (((endp - locp) > catlen) &&
(memcmp(locp, catname, catlen) == 0) &&
(locp[catlen] == '=')) {
value = &locp[catlen + 1];
endvalue = endp;
}
if (endp >= locnameend)
break;
locp = endp + 1;
endp = strchr(locp, ';');
if (endp == NULL)
endp = locnameend;
}
if (value == NULL) {
errno = EINVAL;
return (NULL);
}
copylen = endvalue - value;
if (copylen > ENCODING_LEN) {
errno = ENOENT;
return (NULL);
}
if (copylen == 0) {
locname = get_locale_env(category);
} else {
memcpy(scratch, value, copylen);
scratch[copylen] = 0;
locname = scratch;
}
}
if ((strcmp(locname, "C") == 0) || (strcmp(locname, "POSIX") == 0))
return (posix_locale.locdata[category]);
for (i = 0; cmalias[i].alias != NULL; i++) {
if ((cm = strstr(locname, cmalias[i].alias)) != NULL &&
strlen(cm) == strlen(cmalias[i].alias)) {
len = cm - locname + 1;
if (len + strlen(cmalias[i].name) >= sizeof (scratch2))
break;
(void) strlcpy(scratch2, locname, len);
(void) strlcat(scratch2, cmalias[i].name,
sizeof (scratch2));
locname = scratch2;
break;
}
}
if ((strcmp(locname, "C.UTF-8") == 0) && (category != LC_CTYPE))
return (&cutf_locdata[category]);
return (locdata_get_cache(category, locname));
}
static void
freelocptr(void *arg)
{
locale_t *locptr = arg;
if (*locptr != NULL)
freelocale(*locptr);
}
static const char *
get_locale_env(int category)
{
const char *env;
env = getenv(categories[LC_ALL]);
if (env == NULL || *env == '\0')
env = getenv(categories[category]);
if (env == NULL || *env == '\0')
env = getenv("LANG");
if (env == NULL || *env == '\0')
env = "C";
return (env);
}
unsigned char
__mb_cur_max_l(locale_t loc)
{
return (loc->ctype->lc_max_mblen);
}
unsigned char
__mb_cur_max(void)
{
return (__mb_cur_max_l(uselocale(NULL)));
}
locale_t
duplocale(locale_t src)
{
locale_t loc;
int i;
loc = lmalloc(sizeof (*loc));
if (loc == NULL) {
return (NULL);
}
if (src == NULL) {
src = ___global_locale;
}
for (i = 0; i < LC_ALL; i++) {
loc->locdata[i] = src->locdata[i];
loc->loaded[i] = 0;
}
loc->collate = loc->locdata[LC_COLLATE]->l_data[0];
loc->ctype = loc->locdata[LC_CTYPE]->l_data[0];
loc->runelocale = loc->locdata[LC_CTYPE]->l_data[1];
loc->messages = loc->locdata[LC_MESSAGES]->l_data[0];
loc->monetary = loc->locdata[LC_MONETARY]->l_data[0];
loc->numeric = loc->locdata[LC_NUMERIC]->l_data[0];
loc->time = loc->locdata[LC_TIME]->l_data[0];
return (loc);
}
void
freelocale(locale_t loc)
{
if ((loc != NULL) && (loc != &posix_locale) && (!loc->on_list))
lfree(loc, sizeof (*loc));
}
locale_t
newlocale(int catmask, const char *locname, locale_t base)
{
locale_t loc;
int i, e;
if (catmask & ~(LC_ALL_MASK)) {
errno = EINVAL;
return (NULL);
}
if (base == NULL || base == ___global_locale) {
loc = duplocale(___global_locale);
} else {
loc = duplocale(base);
}
if (loc == NULL) {
return (NULL);
}
for (i = 0; i < LC_ALL; i++) {
struct locdata *ldata;
loc->loaded[i] = 0;
if (((1 << i) & catmask) == 0) {
continue;
}
ldata = locdata_get(i, locname);
if (ldata == NULL) {
e = errno;
freelocale(loc);
errno = e;
return (NULL);
}
loc->locdata[i] = ldata;
}
loc->collate = loc->locdata[LC_COLLATE]->l_data[0];
loc->ctype = loc->locdata[LC_CTYPE]->l_data[0];
loc->runelocale = loc->locdata[LC_CTYPE]->l_data[1];
loc->messages = loc->locdata[LC_MESSAGES]->l_data[0];
loc->monetary = loc->locdata[LC_MONETARY]->l_data[0];
loc->numeric = loc->locdata[LC_NUMERIC]->l_data[0];
loc->time = loc->locdata[LC_TIME]->l_data[0];
freelocale(base);
return (mklocname(loc));
}
locale_t
uselocale(locale_t loc)
{
locale_t lastloc = ___global_locale;
locale_t *locptr;
locptr = tsdalloc(_T_SETLOCALE, sizeof (locale_t), freelocptr);
if (locptr == NULL) {
errno = EINVAL;
return (NULL);
}
if (*locptr != NULL)
lastloc = *locptr;
if (loc != NULL) {
if (loc == ___global_locale) {
*locptr = NULL;
} else {
*locptr = loc;
}
}
return (lastloc);
}
static locale_t
mklocname(locale_t loc)
{
int composite = 0;
for (int i = 1; i < LC_ALL; ++i) {
if (strcmp(loc->locdata[0]->l_lname,
loc->locdata[i]->l_lname) != 0) {
composite = 1;
break;
}
}
if (composite) {
(void) snprintf(loc->locname, sizeof (loc->locname),
"%s/%s/%s/%s/%s/%s",
loc->locdata[LC_CTYPE]->l_lname,
loc->locdata[LC_NUMERIC]->l_lname,
loc->locdata[LC_TIME]->l_lname,
loc->locdata[LC_COLLATE]->l_lname,
loc->locdata[LC_MONETARY]->l_lname,
loc->locdata[LC_MESSAGES]->l_lname);
} else {
(void) strlcpy(loc->locname, loc->locdata[LC_CTYPE]->l_lname,
sizeof (loc->locname));
}
return (loc);
}
const char *
getlocalename_l(int category, locale_t loc)
{
if (loc == NULL) {
return (NULL);
}
switch (category) {
case LC_CTYPE:
case LC_NUMERIC:
case LC_TIME:
case LC_COLLATE:
case LC_MONETARY:
case LC_MESSAGES:
return (loc->locdata[category]->l_lname);
case LC_ALL:
return (loc->locname);
default:
return (NULL);
}
}