implement gettext message translation functions
authorRich Felker <dalias@aerifal.cx>
Sun, 27 Jul 2014 08:29:56 +0000 (04:29 -0400)
committerRich Felker <dalias@aerifal.cx>
Sun, 27 Jul 2014 08:29:56 +0000 (04:29 -0400)
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.

src/locale/bind_textdomain_codeset.c [new file with mode: 0644]
src/locale/dcngettext.c [new file with mode: 0644]
src/locale/intl.c [deleted file]
src/locale/textdomain.c [new file with mode: 0644]

diff --git a/src/locale/bind_textdomain_codeset.c b/src/locale/bind_textdomain_codeset.c
new file mode 100644 (file)
index 0000000..5ebfd5e
--- /dev/null
@@ -0,0 +1,11 @@
+#include <libintl.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+
+char *bind_textdomain_codeset(const char *domainname, const char *codeset)
+{
+       if (codeset && strcasecmp(codeset, "UTF-8"))
+               errno = EINVAL;
+       return NULL;
+}
diff --git a/src/locale/dcngettext.c b/src/locale/dcngettext.c
new file mode 100644 (file)
index 0000000..4f9e417
--- /dev/null
@@ -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);
+}
diff --git a/src/locale/intl.c b/src/locale/intl.c
deleted file mode 100644 (file)
index ad04052..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#include <libintl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-#include <errno.h>
-
-char *gettext(const char *msgid)
-{
-       return (char *) msgid;
-}
-
-char *dgettext(const char *domainname, const char *msgid)
-{
-       return (char *) msgid;
-}
-
-char *dcgettext(const char *domainname, const char *msgid, int category)
-{
-       return (char *) msgid;
-}
-
-char *ngettext(const char *msgid1, const char *msgid2, unsigned long int n)
-{
-       return (char *) ((n == 1) ? msgid1 : msgid2);
-}
-
-char *dngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n)
-{
-       return (char *) ((n == 1) ? msgid1 : msgid2);
-}
-
-char *dcngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n, int category)
-{
-       return (char *) ((n == 1) ? msgid1 : msgid2);
-}
-
-char *textdomain(const char *domainname)
-{
-       static const char default_str[] = "messages";
-
-       if (domainname && *domainname && strcmp(domainname, default_str)) {
-               errno = EINVAL;
-               return NULL;
-       }
-       return (char *) default_str;
-}
-
-char *bindtextdomain(const char *domainname, const char *dirname)
-{
-       static const char dir[] = "/";
-
-       if (!domainname || !*domainname
-               || (dirname && ((dirname[0] != '/') || dirname[1]))
-               ) {
-               errno = EINVAL;
-               return NULL;
-       }
-
-       return (char *) dir;
-}
-
-char *bind_textdomain_codeset(const char *domainname, const char *codeset)
-{
-       if (!domainname || !*domainname || (codeset && strcasecmp(codeset, "UTF-8"))) {
-               errno = EINVAL;
-       }
-       return NULL;
-}
diff --git a/src/locale/textdomain.c b/src/locale/textdomain.c
new file mode 100644 (file)
index 0000000..c501501
--- /dev/null
@@ -0,0 +1,44 @@
+#include <libintl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include "libc.h"
+#include "atomic.h"
+
+static char *current_domain;
+
+char *__gettextdomain()
+{
+       return current_domain ? current_domain : "messages";
+}
+
+char *textdomain(const char *domainname)
+{
+       if (!domainname) return __gettextdomain();
+
+       size_t domlen = strlen(domainname);
+       if (domlen > NAME_MAX) {
+               errno = EINVAL;
+               return 0;
+       }
+
+       if (!current_domain) {
+               current_domain = malloc(NAME_MAX+1);
+               if (!current_domain) return 0;
+       }
+
+       memcpy(current_domain, domainname, domlen+1);
+
+       return current_domain;
+}
+
+char *gettext(const char *msgid)
+{
+       return dgettext(0, msgid);
+}
+
+char *ngettext(const char *msgid1, const char *msgid2, unsigned long int n)
+{
+       return dngettext(0, msgid1, msgid2, n);
+}