1 /**
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <libintl.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <limits.h>
21 #include <sys/stat.h>
22 #include <sys/mman.h>
23 #include <ctype.h>
24 #include "locale_impl.h"
25 #include "atomic.h"
26 #include "pleval.h"
27 #include "lock.h"
28
29 #define __DIGIT_SEVEN__ 7
30 #define __DIGIT_NINE__ 9
31 #define __DIGIT_TEN__ 10
32 #define __DIGIT_THIRTEEN__ 13
33
34 struct binding {
35 struct binding *next;
36 int dirlen;
37 volatile int active;
38 char *domainname;
39 char *dirname;
40 char buf[];
41 };
42
43 static void *volatile bindings;
44
gettextdir(const char * domainname,size_t * dirlen)45 static char *gettextdir(const char *domainname, size_t *dirlen)
46 {
47 struct binding *p;
48 for (p=bindings; p; p=p->next) {
49 if (!strcmp(p->domainname, domainname) && p->active) {
50 *dirlen = p->dirlen;
51 return (char *)p->dirname;
52 }
53 }
54 return 0;
55 }
56
bindtextdomain(const char * domainname,const char * dirname)57 char *bindtextdomain(const char *domainname, const char *dirname)
58 {
59 static volatile int lock[1];
60 struct binding *p, *q;
61
62 if (!domainname) {
63 return 0;
64 }
65 if (!dirname) {
66 return gettextdir(domainname, &(size_t){0});
67 }
68
69 size_t domlen = strnlen(domainname, NAME_MAX+1);
70 size_t dirlen = strnlen(dirname, PATH_MAX);
71 if (domlen > NAME_MAX || dirlen >= PATH_MAX) {
72 errno = EINVAL;
73 return 0;
74 }
75
76 LOCK(lock);
77
78 for (p=bindings; p; p=p->next) {
79 if (!strcmp(p->domainname, domainname) &&
80 !strcmp(p->dirname, dirname)) {
81 break;
82 }
83 }
84
85 if (!p) {
86 p = calloc(sizeof *p + domlen + dirlen + 2, 1);
87 if (!p) {
88 UNLOCK(lock);
89 return 0;
90 }
91 p->next = bindings;
92 p->dirlen = dirlen;
93 p->domainname = p->buf;
94 p->dirname = p->buf + domlen + 1;
95 memcpy(p->domainname, domainname, domlen+1);
96 memcpy(p->dirname, dirname, dirlen+1);
97 a_cas_p(&bindings, bindings, p);
98 }
99
100 a_store(&p->active, 1);
101
102 for (q=bindings; q; q=q->next) {
103 if (!strcmp(q->domainname, domainname) && q != p) {
104 a_store(&q->active, 0);
105 }
106 }
107
108 UNLOCK(lock);
109
110 return (char *)p->dirname;
111 }
112
113 static const char catnames[][18] = {
114 "LC_CTYPE",
115 "LC_NUMERIC",
116 "LC_TIME",
117 "LC_COLLATE",
118 "LC_MONETARY",
119 "LC_MESSAGES",
120 "LC_PAPER",
121 "LC_NAME",
122 "LC_ADDRESS",
123 "LC_TELEPHONE",
124 "LC_MEASUREMENT",
125 "LC_IDENTIFICATION",
126 };
127
128 static const char catlens[] = { 8, 10, 7, 10, 11, 11, 8, 7, 10, 12, 14, 17 };
129
130 struct msgcat {
131 struct msgcat *next;
132 const void *map;
133 size_t map_size;
134 const char *plural_rule;
135 int nplurals;
136 struct binding *binding;
137 const struct __locale_map *lm;
138 int cat;
139 };
140
dummy_gettextdomain()141 static char *dummy_gettextdomain()
142 {
143 return "messages";
144 }
145
146 weak_alias(dummy_gettextdomain, __gettextdomain);
147
dcngettext(const char * domainname,const char * msgid1,const char * msgid2,unsigned long int n,int category)148 char *dcngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n, int category)
149 {
150 static struct msgcat *volatile cats;
151 struct msgcat *p;
152 struct __locale_struct *loc = CURRENT_LOCALE;
153 const struct __locale_map *lm;
154 size_t domlen;
155 struct binding *q;
156 int old_errno = errno;
157
158 if ((unsigned)category >= LC_ALL) {
159 goto notrans;
160 }
161
162 if (!domainname) {
163 domainname = __gettextdomain();
164 }
165
166 domlen = strnlen(domainname, NAME_MAX+1);
167 if (domlen > NAME_MAX) {
168 goto notrans;
169 }
170
171 for (q=bindings; q; q=q->next) {
172 if (!strcmp(q->domainname, domainname) && q->active) {
173 break;
174 }
175 }
176 if (!q) {
177 goto notrans;
178 }
179
180 lm = loc->cat[category];
181 if (!lm) {
182 notrans:
183 errno = old_errno;
184 return (char *) ((n == 1) ? msgid1 : msgid2);
185 }
186
187 for (p=cats; p; p=p->next) {
188 if (p->binding == q && p->lm == lm && p->cat == category) {
189 break;
190 }
191 }
192
193 if (!p) {
194 const char *dirname, *locname, *catname, *modname, *locp;
195 size_t dirlen, loclen, catlen, modlen, alt_modlen;
196 void *old_cats;
197 size_t map_size;
198
199 dirname = q->dirname;
200 locname = lm->name;
201 catname = catnames[category];
202
203 dirlen = q->dirlen;
204 loclen = strlen(locname);
205 catlen = catlens[category];
206
207 /* Logically split @mod suffix from locale name. */
208 modname = memchr(locname, '@', loclen);
209 if (!modname) {
210 modname = locname + loclen;
211 }
212 alt_modlen = modlen = loclen - (modname-locname);
213 loclen = modname-locname;
214
215 /* Drop .charset identifier; it is not used. */
216 const char *csp = memchr(locname, '.', loclen);
217 if (csp) {
218 loclen = csp-locname;
219 }
220
221 char name[dirlen+1 + loclen+modlen+1 + catlen+1 + domlen+3 + 1];
222 const void *map;
223
224 for (;;) {
225 snprintf(name, sizeof name, "%s/%.*s%.*s/%s/%s.mo\0",
226 dirname, (int)loclen, locname,
227 (int)alt_modlen, modname, catname, domainname);
228 if (map = __map_file(name, &map_size)) {
229 break;
230 }
231
232 /* Try dropping @mod, _YY, then both. */
233 if (alt_modlen) {
234 alt_modlen = 0;
235 } else if ((locp = memchr(locname, '_', loclen))) {
236 loclen = locp-locname;
237 alt_modlen = modlen;
238 } else {
239 break;
240 }
241 }
242 if (!map) {
243 goto notrans;
244 }
245
246 p = calloc(sizeof *p, 1);
247 if (!p) {
248 __munmap((void *)map, map_size);
249 goto notrans;
250 }
251 p->cat = category;
252 p->binding = q;
253 p->lm = lm;
254 p->map = map;
255 p->map_size = map_size;
256
257 const char *rule = "n!=1;";
258 unsigned long np = 2;
259 const char *r = __mo_lookup(p->map, p->map_size, "");
260 char *z;
261 while (r && strncmp(r, "Plural-Forms:", __DIGIT_THIRTEEN__)) {
262 z = strchr(r, '\n');
263 r = z ? z+1 : 0;
264 }
265 if (r) {
266 r += __DIGIT_THIRTEEN__;
267 while (isspace(*r)) {
268 r++;
269 }
270 if (!strncmp(r, "nplurals=", __DIGIT_NINE__)) {
271 np = strtoul(r+__DIGIT_NINE__, &z, __DIGIT_TEN__);
272 r = z;
273 }
274 while (*r && *r != ';') {
275 r++;
276 }
277 if (*r) {
278 r++;
279 while (isspace(*r)) {
280 r++;
281 }
282 if (!strncmp(r, "plural=", __DIGIT_SEVEN__)) {
283 rule = r+__DIGIT_SEVEN__;
284 }
285 }
286 }
287 p->nplurals = np;
288 p->plural_rule = rule;
289
290 do {
291 old_cats = cats;
292 p->next = old_cats;
293 } while (a_cas_p(&cats, old_cats, p) != old_cats);
294 }
295
296 const char *trans = __mo_lookup(p->map, p->map_size, msgid1);
297 if (!trans) {
298 goto notrans;
299 }
300
301 /* Non-plural-processing gettext forms pass a null pointer as
302 * msgid2 to request that dcngettext suppress plural processing. */
303
304 if (msgid2 && p->nplurals) {
305 unsigned long plural = __pleval(p->plural_rule, n);
306 if (plural > p->nplurals) {
307 goto notrans;
308 }
309 while (plural--) {
310 size_t rem = p->map_size - (trans - (char *)p->map);
311 size_t l = strnlen(trans, rem);
312 if (l+1 >= rem) {
313 goto notrans;
314 }
315 trans += l+1;
316 }
317 }
318 errno = old_errno;
319 return (char *)trans;
320 }
321
dcgettext(const char * domainname,const char * msgid,int category)322 char *dcgettext(const char *domainname, const char *msgid, int category)
323 {
324 return dcngettext(domainname, msgid, 0, 1, category);
325 }
326
dngettext(const char * domainname,const char * msgid1,const char * msgid2,unsigned long int n)327 char *dngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n)
328 {
329 return dcngettext(domainname, msgid1, msgid2, n, LC_MESSAGES);
330 }
331
dgettext(const char * domainname,const char * msgid)332 char *dgettext(const char *domainname, const char *msgid)
333 {
334 return dcngettext(domainname, msgid, 0, 1, LC_MESSAGES);
335 }
336