1#include <libintl.h>
2#include <stdlib.h>
3#include <string.h>
4#include <errno.h>
5#include <limits.h>
6#include <sys/stat.h>
7#include <ctype.h>
8#include "locale_impl.h"
9#include "libc.h"
10#include "atomic.h"
11
12struct binding {
13	struct binding *next;
14	int dirlen;
15	volatile int active;
16	char *domainname;
17	char *dirname;
18	char buf[];
19};
20
21static void *volatile bindings;
22
23static char *gettextdir(const char *domainname, size_t *dirlen)
24{
25	struct binding *p;
26	for (p=bindings; p; p=p->next) {
27		if (!strcmp(p->domainname, domainname) && p->active) {
28			*dirlen = p->dirlen;
29			return (char *)p->dirname;
30		}
31	}
32	return 0;
33}
34
35char *bindtextdomain(const char *domainname, const char *dirname)
36{
37	static volatile int lock[2];
38	struct binding *p, *q;
39
40	if (!domainname) return 0;
41	if (!dirname) return gettextdir(domainname, &(size_t){0});
42
43	size_t domlen = strlen(domainname);
44	size_t dirlen = strlen(dirname);
45	if (domlen > NAME_MAX || dirlen >= PATH_MAX) {
46		errno = EINVAL;
47		return 0;
48	}
49
50	LOCK(lock);
51
52	for (p=bindings; p; p=p->next) {
53		if (!strcmp(p->domainname, domainname) &&
54		    !strcmp(p->dirname, dirname)) {
55			break;
56		}
57	}
58
59	if (!p) {
60		p = malloc(sizeof *p + domlen + dirlen + 2);
61		if (!p) {
62			UNLOCK(lock);
63			return 0;
64		}
65		p->next = bindings;
66		p->dirlen = dirlen;
67		p->domainname = p->buf;
68		p->dirname = p->buf + domlen + 1;
69		memcpy(p->domainname, domainname, domlen+1);
70		memcpy(p->dirname, dirname, dirlen+1);
71		a_cas_p(&bindings, bindings, p);
72	}
73
74	a_store(&p->active, 1);
75
76	for (q=bindings; q; q=q->next) {
77		if (!strcmp(p->domainname, domainname) && q != p)
78			a_store(&q->active, 0);
79	}
80
81	UNLOCK(lock);
82
83	return (char *)p->dirname;
84}
85
86static const char catnames[][12] = {
87	"LC_CTYPE",
88	"LC_NUMERIC",
89	"LC_TIME",
90	"LC_COLLATE",
91	"LC_MONETARY",
92	"LC_MESSAGES",
93};
94
95static const char catlens[] = { 8, 10, 7, 10, 11, 11 };
96
97struct msgcat {
98	struct msgcat *next;
99	const void *map;
100	size_t map_size;
101	void *volatile plural_rule;
102	volatile int nplurals;
103	char name[];
104};
105
106static char *dummy_gettextdomain()
107{
108	return "messages";
109}
110
111weak_alias(dummy_gettextdomain, __gettextdomain);
112
113const unsigned char *__map_file(const char *, size_t *);
114int __munmap(void *, size_t);
115unsigned long __pleval(const char *, unsigned long);
116
117char *dcngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n, int category)
118{
119	static struct msgcat *volatile cats;
120	struct msgcat *p;
121	struct __locale_struct *loc = CURRENT_LOCALE;
122	const struct __locale_map *lm;
123	const char *dirname, *locname, *catname;
124	size_t dirlen, loclen, catlen, domlen;
125
126	if ((unsigned)category >= LC_ALL) goto notrans;
127
128	if (!domainname) domainname = __gettextdomain();
129
130	domlen = strlen(domainname);
131	if (domlen > NAME_MAX) goto notrans;
132
133	dirname = gettextdir(domainname, &dirlen);
134	if (!dirname) goto notrans;
135
136	lm = loc->cat[category];
137	if (!lm) {
138notrans:
139		return (char *) ((n == 1) ? msgid1 : msgid2);
140	}
141	locname = lm->name;
142
143	catname = catnames[category];
144	catlen = catlens[category];
145	loclen = strlen(locname);
146
147	size_t namelen = dirlen+1 + loclen+1 + catlen+1 + domlen+3;
148	char name[namelen+1], *s = name;
149
150	memcpy(s, dirname, dirlen);
151	s[dirlen] = '/';
152	s += dirlen + 1;
153	memcpy(s, locname, loclen);
154	s[loclen] = '/';
155	s += loclen + 1;
156	memcpy(s, catname, catlen);
157	s[catlen] = '/';
158	s += catlen + 1;
159	memcpy(s, domainname, domlen);
160	s[domlen] = '.';
161	s[domlen+1] = 'm';
162	s[domlen+2] = 'o';
163	s[domlen+3] = 0;
164
165	for (p=cats; p; p=p->next)
166		if (!strcmp(p->name, name))
167			break;
168
169	if (!p) {
170		void *old_cats;
171		size_t map_size;
172		const void *map = __map_file(name, &map_size);
173		if (!map) goto notrans;
174		p = malloc(sizeof *p + namelen + 1);
175		if (!p) {
176			__munmap((void *)map, map_size);
177			goto notrans;
178		}
179		p->map = map;
180		p->map_size = map_size;
181		memcpy(p->name, name, namelen+1);
182		do {
183			old_cats = cats;
184			p->next = old_cats;
185		} while (a_cas_p(&cats, old_cats, p) != old_cats);
186	}
187
188	const char *trans = __mo_lookup(p->map, p->map_size, msgid1);
189	if (!trans) goto notrans;
190
191	/* Non-plural-processing gettext forms pass a null pointer as
192	 * msgid2 to request that dcngettext suppress plural processing. */
193	if (!msgid2) return (char *)trans;
194
195	if (!p->plural_rule) {
196		const char *rule = "n!=1;";
197		unsigned long np = 2;
198		const char *r = __mo_lookup(p->map, p->map_size, "");
199		char *z;
200		while (r && strncmp(r, "Plural-Forms:", 13)) {
201			z = strchr(r, '\n');
202			r = z ? z+1 : 0;
203		}
204		if (r) {
205			r += 13;
206			while (isspace(*r)) r++;
207			if (!strncmp(r, "nplurals=", 9)) {
208				np = strtoul(r+9, &z, 10);
209				r = z;
210			}
211			while (*r && *r != ';') r++;
212			if (*r) {
213				r++;
214				while (isspace(*r)) r++;
215				if (!strncmp(r, "plural=", 7))
216					rule = r+7;
217			}
218		}
219		a_store(&p->nplurals, np);
220		a_cas_p(&p->plural_rule, 0, (void *)rule);
221	}
222	if (p->nplurals) {
223		unsigned long plural = __pleval(p->plural_rule, n);
224		if (plural > p->nplurals) goto notrans;
225		while (plural--) {
226			size_t rem = p->map_size - (trans - (char *)p->map);
227			size_t l = strnlen(trans, rem);
228			if (l+1 >= rem)
229				goto notrans;
230			trans += l+1;
231		}
232	}
233	return (char *)trans;
234}
235
236char *dcgettext(const char *domainname, const char *msgid, int category)
237{
238	return dcngettext(domainname, msgid, 0, 1, category);
239}
240
241char *dngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n)
242{
243	return dcngettext(domainname, msgid1, msgid2, n, LC_MESSAGES);
244}
245
246char *dgettext(const char *domainname, const char *msgid)
247{
248	return dcngettext(domainname, msgid, 0, 1, LC_MESSAGES);
249}
250