summaryrefslogtreecommitdiff
path: root/src/locale/dcngettext.c
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2014-07-27 04:29:56 -0400
committerRich Felker <dalias@aerifal.cx>2014-07-27 04:29:56 -0400
commit2068b4e8911a3a49cded44b4568f6c943a8c98f8 (patch)
treed662b28c1a9f87674d0bc25be051a8fb69312316 /src/locale/dcngettext.c
parentc5b8f1930512d206a7c1cf1093a4a47e1722a414 (diff)
downloadmusl-2068b4e8911a3a49cded44b4568f6c943a8c98f8.tar.gz
implement gettext message translation functions
this commit replaces the stub implementations with working message translation functions. translation units are factored so as to prevent pulling in the legacy, non-library-safe functions which use a global textdomain in modern code which is using the versions with an explicit domain argument. bind_textdomain_codeset is also placed in its own file since it should not be needed by most programs. this implementation is still missing some features: the LANGUAGE environment variable (for multiple fallback languages) is not honored, and non-default plural-form rules are not supported. these issues will be addressed in a later commit. one notable difference from the GNU implementation is that there is no default path for loading translation files. in principle one could be added, but since the documented correct usage is to call the bindtextdomain function, a default path is probably unnecessary.
Diffstat (limited to 'src/locale/dcngettext.c')
-rw-r--r--src/locale/dcngettext.c216
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);
+}