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