diff options
Diffstat (limited to 'src/locale/dcngettext.c')
-rw-r--r-- | src/locale/dcngettext.c | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/locale/dcngettext.c b/src/locale/dcngettext.c new file mode 100644 index 00000000..4f9e4174 --- /dev/null +++ b/src/locale/dcngettext.c @@ -0,0 +1,216 @@ +#include <libintl.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <limits.h> +#include <sys/stat.h> +#include "locale_impl.h" +#include "libc.h" +#include "atomic.h" + +struct binding { + struct binding *next; + int dirlen; + int active; + char *domainname; + char *dirname; + char buf[]; +}; + +static void *volatile bindings; + +static char *gettextdir(const char *domainname, size_t *dirlen) +{ + struct binding *p; + for (p=bindings; p; p=p->next) { + if (!strcmp(p->domainname, domainname) && p->active) { + *dirlen = p->dirlen; + return (char *)p->dirname; + } + } + return 0; +} + +char *bindtextdomain(const char *domainname, const char *dirname) +{ + static int lock[2]; + struct binding *p, *q; + + if (!domainname) return 0; + if (!dirname) return gettextdir(domainname, &(size_t){0}); + + size_t domlen = strlen(domainname); + size_t dirlen = strlen(dirname); + if (domlen > NAME_MAX || dirlen >= PATH_MAX) { + errno = EINVAL; + return 0; + } + + LOCK(lock); + + for (p=bindings; p; p=p->next) { + if (!strcmp(p->domainname, domainname) && + !strcmp(p->dirname, dirname)) { + break; + } + } + + if (!p) { + p = malloc(sizeof *p + domlen + dirlen + 2); + if (!p) { + UNLOCK(lock); + return 0; + } + p->next = bindings; + p->dirlen = dirlen; + p->domainname = p->buf; + p->dirname = p->buf + domlen + 1; + memcpy(p->domainname, domainname, domlen+1); + memcpy(p->dirname, dirname, dirlen+1); + a_cas_p(&bindings, bindings, p); + } + + a_store(&p->active, 1); + + for (q=bindings; q; q=q->next) { + if (!strcmp(p->domainname, domainname) && q != p) + a_store(&q->active, 0); + } + + UNLOCK(lock); + + return (char *)p->dirname; +} + +static const char catnames[][12] = { + "LC_TIME", + "LC_COLLATE", + "LC_MONETARY", + "LC_MESSAGES", +}; + +static const char catlens[] = { 7, 10, 11, 11 }; + +struct msgcat { + struct msgcat *next; + const void *map; + size_t map_size; + char name[]; +}; + +static char *dummy_gettextdomain() +{ + return "messages"; +} + +weak_alias(dummy_gettextdomain, __gettextdomain); + +const unsigned char *__map_file(const char *, size_t *); +int __munmap(void *, size_t); + +char *dcngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n, int category) +{ + static struct msgcat *volatile cats; + struct msgcat *p; + struct __locale_struct *loc = CURRENT_LOCALE; + struct __locale_map *lm; + const char *dirname, *locname, *catname; + size_t dirlen, loclen, catlen, domlen; + + if (!domainname) domainname = __gettextdomain(); + + domlen = strlen(domainname); + if (domlen > NAME_MAX) goto notrans; + + dirname = gettextdir(domainname, &dirlen); + if (!dirname) goto notrans; + + switch (category) { + case LC_MESSAGES: + locname = loc->messages_name; + if (!*locname) goto notrans; + break; + case LC_TIME: + case LC_MONETARY: + case LC_COLLATE: + lm = loc->cat[category-2]; + if (!lm) goto notrans; + locname = lm->name; + break; + default: +notrans: + return (char *) ((n == 1) ? msgid1 : msgid2); + } + + catname = catnames[category-2]; + catlen = catlens[category-2]; + loclen = strlen(locname); + + size_t namelen = dirlen+1 + loclen+1 + catlen+1 + domlen+3; + char name[namelen+1], *s = name; + + memcpy(s, dirname, dirlen); + s[dirlen] = '/'; + s += dirlen + 1; + memcpy(s, locname, loclen); + s[loclen] = '/'; + s += loclen + 1; + memcpy(s, catname, catlen); + s[catlen] = '/'; + s += catlen + 1; + memcpy(s, domainname, domlen); + s[domlen] = '.'; + s[domlen+1] = 'm'; + s[domlen+2] = 'o'; + s[domlen+3] = 0; + + for (p=cats; p; p=p->next) + if (!strcmp(p->name, name)) + break; + + if (!p) { + void *old_cats; + size_t map_size; + const void *map = __map_file(name, &map_size); + if (!map) goto notrans; + p = malloc(sizeof *p + namelen + 1); + if (!p) { + __munmap((void *)map, map_size); + goto notrans; + } + p->map = map; + p->map_size = map_size; + memcpy(p->name, name, namelen+1); + do { + old_cats = cats; + p->next = old_cats; + } while (a_cas_p(&cats, old_cats, p) != old_cats); + } + + const char *trans = __mo_lookup(p->map, p->map_size, msgid1); + if (!trans) goto notrans; + + /* FIXME: support alternate plural rules */ + if (n != 1) { + size_t l = strlen(trans); + if (l+1 >= p->map_size - (trans - (char *)p->map)) + goto notrans; + trans += l+1; + } + return (char *)trans; +} + +char *dcgettext(const char *domainname, const char *msgid, int category) +{ + return dcngettext(domainname, msgid, msgid, 1, category); +} + +char *dngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n) +{ + return dcngettext(domainname, msgid1, msgid2, n, LC_MESSAGES); +} + +char *dgettext(const char *domainname, const char *msgid) +{ + return dcngettext(domainname, msgid, msgid, 1, LC_MESSAGES); +} |