add non-stub implementation of catgets localization functions
authorRich Felker <dalias@aerifal.cx>
Thu, 8 Aug 2019 01:15:53 +0000 (21:15 -0400)
committerRich Felker <dalias@aerifal.cx>
Thu, 8 Aug 2019 01:15:53 +0000 (21:15 -0400)
these accept the netbsd/openbsd message catalog file format,
consisting of a sorted list of set headers and a sorted list of
message headers for each set, admitting trivial binary search for
lookups.

the gnu format was not chosen because it's unusably bad. it does not
admit efficient (log time or better) lookups; rather, it requires
linear search or hash table lookups, and the hash function is awful:
it's literally set_id*msg_id.

src/locale/catclose.c
src/locale/catgets.c
src/locale/catopen.c

index 02cd3e5c9c7a76a553e950c475e67b2c1e125259..54e24dd2163bc85a250b2efc7a36fa0f0aa446e8 100644 (file)
@@ -1,6 +1,14 @@
+#define _BSD_SOURCE
 #include <nl_types.h>
+#include <stdint.h>
+#include <endian.h>
+#include <sys/mman.h>
+
+#define V(p) be32toh(*(uint32_t *)(p))
 
 int catclose (nl_catd catd)
 {
+       char *map = (char *)catd;
+       munmap(map, V(map+8)+20);
        return 0;
 }
index bbee8986fc581ebce93494b2dad7539c37083628..d4a44b3555b0e6207351e6c9e5546bf6830c12ce 100644 (file)
@@ -1,6 +1,38 @@
+#define _BSD_SOURCE
 #include <nl_types.h>
+#include <endian.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+
+#define V(p) be32toh(*(uint32_t *)(p))
+
+int cmp(const void *a, const void *b)
+{
+       uint32_t x = V(a), y = V(b);
+       return x<y ? -1 : x>y ? 1 : 0;
+}
 
 char *catgets (nl_catd catd, int set_id, int msg_id, const char *s)
 {
-       return (char *)s;
+       const char *map = (const char *)catd;
+       uint32_t nsets = V(map+4);
+       const char *sets = map+20;
+       const char *msgs = map+20+V(map+12);
+       const char *strings = map+20+V(map+16);
+       uint32_t set_id_be = htobe32(set_id);
+       uint32_t msg_id_be = htobe32(msg_id);
+       const char *set = bsearch(&set_id_be, sets, nsets, 12, cmp);
+       if (!set) {
+               errno = ENOMSG;
+               return (char *)s;
+       }
+       uint32_t nmsgs = V(set+4);
+       msgs += 12*V(set+8);
+       const char *msg = bsearch(&msg_id_be, msgs, nmsgs, 12, cmp);
+       if (!msg) {
+               errno = ENOMSG;
+               return (char *)s;
+       }
+       return (char *)(strings + V(msg+8));
 }
index 3fbc77178fb24e7c9661f383dccdd9b762387957..97f2446d37f983d085c9965dd700e91eabe55969 100644 (file)
@@ -1,8 +1,79 @@
+#define _BSD_SOURCE
 #include <nl_types.h>
+#include <string.h>
+#include <stdint.h>
+#include <endian.h>
 #include <errno.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <sys/mman.h>
+#include "libc.h"
 
-nl_catd catopen (const char *name, int oflag)
+#define V(p) be32toh(*(uint32_t *)(p))
+
+static nl_catd do_catopen(const char *name)
+{
+       size_t size;
+       const unsigned char *map = __map_file(name, &size);
+       /* Size recorded in the file must match file size; otherwise
+        * the information needed to unmap the file will be lost. */
+       if (!map || V(map) != 0xff88ff89 || 20+V(map+8) != size) {
+               if(map) munmap((void *)map, size);
+               errno = ENOENT;
+               return (nl_catd)-1;
+       }
+       return (nl_catd)map;
+}
+
+nl_catd catopen(const char *name, int oflag)
 {
-       errno = EOPNOTSUPP;
+       nl_catd catd;
+
+       if (strchr(name, '/')) return do_catopen(name);
+
+       char buf[PATH_MAX];
+       size_t i;
+       const char *path, *lang, *p, *z;
+       if (libc.secure || !(path = getenv("NLSPATH"))) {
+               errno = ENOENT;
+               return (nl_catd)-1;
+       }
+       lang = oflag ? nl_langinfo(_NL_LOCALE_NAME(LC_MESSAGES)) : getenv("LANG");
+       if (!lang) lang = "";
+       for (p=path; *p; p=z) {
+               i = 0;
+               z = __strchrnul(p, ':');
+               for (; p<z; p++) {
+                       const char *v;
+                       size_t l;
+                       if (*p!='%') v=p, l=1;
+                       else switch (*++p) {
+                       case 'N': v=name; l=strlen(v); break;
+                       case 'L': v=lang; l=strlen(v); break;
+                       case 'l': v=lang; l=strcspn(v,"_.@"); break;
+                       case 't':
+                               v=__strchrnul(lang,'_');
+                               if (*v) v++;
+                               l=strcspn(v,".@");
+                               break;
+                       case 'c': v="UTF-8"; l=5; break;
+                       case '%': v="%"; l=1; break;
+                       default: v=0;
+                       }
+                       if (!v || l >= sizeof buf - i) {
+                               break;
+                       }
+                       memcpy(buf+i, v, l);
+                       i += l;
+               }
+               if (!*z && (p<z || !i)) break;
+               if (p<z) continue;
+               if (*z) z++;
+               buf[i] = 0;
+               /* Leading : or :: in NLSPATH is same as %N */
+               catd = do_catopen(i ? buf : name);
+               if (catd != (nl_catd)-1) return catd;
+       }
+       errno = ENOENT;
        return (nl_catd)-1;
 }