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