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