1 /***********************************************************
2 Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
3 Copyright 2010, Gabor Kovesdan <gabor@FreeBSD.org>
4
5 All Rights Reserved
6
7 Permission to use, copy, modify, and distribute this software and its
8 documentation for any purpose and without fee is hereby granted,
9 provided that the above copyright notice appear in all copies and that
10 both that copyright notice and this permission notice appear in
11 supporting documentation, and that Alfalfa's name not be used in
12 advertising or publicity pertaining to distribution of the software
13 without specific, written prior permission.
14
15 ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
16 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
17 ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
18 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
19 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
20 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
21 SOFTWARE.
22
23 If you make any modifications, bugfixes or other changes to this software
24 we'd appreciate it if you could send a copy to us so we can keep things
25 up-to-date. Many thanks.
26 Kee Hinckley
27 Alfalfa Software, Inc.
28 267 Allston St., #3
29 Cambridge, MA 02139 USA
30 nazgul@alfalfa.com
31
32 ******************************************************************/
33
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
36
37 #define _NLS_PRIVATE
38
39 #include "namespace.h"
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <sys/mman.h>
43 #include <sys/queue.h>
44
45 #include <arpa/inet.h> /* for ntohl() */
46 #include <machine/atomic.h>
47
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <limits.h>
51 #include <nl_types.h>
52 #include <pthread.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include "un-namespace.h"
58
59 #include "../locale/xlocale_private.h"
60
61 #define _DEFAULT_NLS_PATH "/usr/share/nls/%L/%N.cat:/usr/share/nls/%N/%L:/usr/local/share/nls/%L/%N.cat:/usr/local/share/nls/%N/%L"
62
63 #define RLOCK(fail) { int ret; \
64 if (__isthreaded && \
65 ((ret = _pthread_rwlock_rdlock(&rwlock)) != 0)) { \
66 errno = ret; \
67 return (fail); \
68 }}
69 #define WLOCK(fail) { int ret; \
70 if (__isthreaded && \
71 ((ret = _pthread_rwlock_wrlock(&rwlock)) != 0)) { \
72 errno = ret; \
73 return (fail); \
74 }}
75 #define UNLOCK { if (__isthreaded) \
76 _pthread_rwlock_unlock(&rwlock); }
77
78 #define NLERR ((nl_catd) -1)
79 #define NLRETERR(errc) { errno = errc; return (NLERR); }
80 #define SAVEFAIL(n, l, e) { np = calloc(1, sizeof(struct catentry)); \
81 if (np != NULL) { \
82 np->name = strdup(n); \
83 np->catd = NLERR; \
84 np->lang = (l == NULL) ? NULL : \
85 strdup(l); \
86 np->caterrno = e; \
87 if (np->name == NULL || \
88 (l != NULL && np->lang == NULL)) { \
89 free(np->name); \
90 free(np->lang); \
91 free(np); \
92 } else { \
93 WLOCK(NLERR); \
94 SLIST_INSERT_HEAD(&cache, np, \
95 list); \
96 UNLOCK; \
97 } \
98 } \
99 errno = e; \
100 }
101
102 static nl_catd load_msgcat(const char *, const char *, const char *);
103
104 static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
105
106 struct catentry {
107 SLIST_ENTRY(catentry) list;
108 char *name;
109 char *path;
110 int caterrno;
111 nl_catd catd;
112 char *lang;
113 int refcount;
114 };
115
116 SLIST_HEAD(listhead, catentry) cache =
117 SLIST_HEAD_INITIALIZER(cache);
118
119 nl_catd
catopen(const char * name,int type)120 catopen(const char *name, int type)
121 {
122 struct stat sbuf;
123 struct catentry *np;
124 char *base, *cptr, *cptr1, *nlspath, *pathP, *pcode;
125 char *plang, *pter;
126 int saverr, spcleft;
127 const char *lang, *tmpptr;
128 char path[PATH_MAX];
129
130 /* sanity checking */
131 if (name == NULL || *name == '\0')
132 NLRETERR(EINVAL);
133
134 if (strchr(name, '/') != NULL)
135 /* have a pathname */
136 lang = NULL;
137 else {
138 if (type == NL_CAT_LOCALE)
139 lang = querylocale(LC_MESSAGES_MASK, __get_locale());
140 else
141 lang = getenv("LANG");
142
143 if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
144 (lang[0] == '.' &&
145 (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
146 strchr(lang, '/') != NULL)
147 lang = "C";
148 }
149
150 /* Try to get it from the cache first */
151 RLOCK(NLERR);
152 SLIST_FOREACH(np, &cache, list) {
153 if ((strcmp(np->name, name) == 0) &&
154 ((lang != NULL && np->lang != NULL &&
155 strcmp(np->lang, lang) == 0) || (np->lang == lang))) {
156 if (np->caterrno != 0) {
157 /* Found cached failing entry */
158 UNLOCK;
159 NLRETERR(np->caterrno);
160 } else {
161 /* Found cached successful entry */
162 atomic_add_int(&np->refcount, 1);
163 UNLOCK;
164 return (np->catd);
165 }
166 }
167 }
168 UNLOCK;
169
170 /* is it absolute path ? if yes, load immediately */
171 if (strchr(name, '/') != NULL)
172 return (load_msgcat(name, name, lang));
173
174 /* sanity checking */
175 if ((plang = cptr1 = strdup(lang)) == NULL)
176 return (NLERR);
177 if ((cptr = strchr(cptr1, '@')) != NULL)
178 *cptr = '\0';
179 pter = pcode = "";
180 if ((cptr = strchr(cptr1, '_')) != NULL) {
181 *cptr++ = '\0';
182 pter = cptr1 = cptr;
183 }
184 if ((cptr = strchr(cptr1, '.')) != NULL) {
185 *cptr++ = '\0';
186 pcode = cptr;
187 }
188
189 if ((nlspath = getenv("NLSPATH")) == NULL || issetugid())
190 nlspath = _DEFAULT_NLS_PATH;
191
192 if ((base = cptr = strdup(nlspath)) == NULL) {
193 saverr = errno;
194 free(plang);
195 errno = saverr;
196 return (NLERR);
197 }
198
199 while ((nlspath = strsep(&cptr, ":")) != NULL) {
200 pathP = path;
201 if (*nlspath) {
202 for (; *nlspath; ++nlspath) {
203 if (*nlspath == '%') {
204 switch (*(nlspath + 1)) {
205 case 'l':
206 tmpptr = plang;
207 break;
208 case 't':
209 tmpptr = pter;
210 break;
211 case 'c':
212 tmpptr = pcode;
213 break;
214 case 'L':
215 tmpptr = lang;
216 break;
217 case 'N':
218 tmpptr = (char *)name;
219 break;
220 case '%':
221 ++nlspath;
222 /* FALLTHROUGH */
223 default:
224 if (pathP - path >=
225 sizeof(path) - 1)
226 goto too_long;
227 *(pathP++) = *nlspath;
228 continue;
229 }
230 ++nlspath;
231 put_tmpptr:
232 spcleft = sizeof(path) -
233 (pathP - path) - 1;
234 if (strlcpy(pathP, tmpptr, spcleft) >=
235 spcleft) {
236 too_long:
237 free(plang);
238 free(base);
239 SAVEFAIL(name, lang, ENAMETOOLONG);
240 NLRETERR(ENAMETOOLONG);
241 }
242 pathP += strlen(tmpptr);
243 } else {
244 if (pathP - path >= sizeof(path) - 1)
245 goto too_long;
246 *(pathP++) = *nlspath;
247 }
248 }
249 *pathP = '\0';
250 if (stat(path, &sbuf) == 0) {
251 free(plang);
252 free(base);
253 return (load_msgcat(path, name, lang));
254 }
255 } else {
256 tmpptr = (char *)name;
257 --nlspath;
258 goto put_tmpptr;
259 }
260 }
261 free(plang);
262 free(base);
263 SAVEFAIL(name, lang, ENOENT);
264 NLRETERR(ENOENT);
265 }
266
267 char *
catgets(nl_catd catd,int set_id,int msg_id,const char * s)268 catgets(nl_catd catd, int set_id, int msg_id, const char *s)
269 {
270 struct _nls_cat_hdr *cat_hdr;
271 struct _nls_msg_hdr *msg_hdr;
272 struct _nls_set_hdr *set_hdr;
273 int i, l, r, u;
274
275 if (catd == NULL || catd == NLERR) {
276 errno = EBADF;
277 /* LINTED interface problem */
278 return ((char *)s);
279 }
280
281 cat_hdr = (struct _nls_cat_hdr *)catd->__data;
282 set_hdr = (struct _nls_set_hdr *)(void *)((char *)catd->__data +
283 sizeof(struct _nls_cat_hdr));
284
285 /* binary search, see knuth algorithm b */
286 l = 0;
287 u = ntohl((u_int32_t)cat_hdr->__nsets) - 1;
288 while (l <= u) {
289 i = (l + u) / 2;
290 r = set_id - ntohl((u_int32_t)set_hdr[i].__setno);
291
292 if (r == 0) {
293 msg_hdr = (struct _nls_msg_hdr *)
294 (void *)((char *)catd->__data +
295 sizeof(struct _nls_cat_hdr) +
296 ntohl((u_int32_t)cat_hdr->__msg_hdr_offset));
297
298 l = ntohl((u_int32_t)set_hdr[i].__index);
299 u = l + ntohl((u_int32_t)set_hdr[i].__nmsgs) - 1;
300 while (l <= u) {
301 i = (l + u) / 2;
302 r = msg_id -
303 ntohl((u_int32_t)msg_hdr[i].__msgno);
304 if (r == 0) {
305 return ((char *) catd->__data +
306 sizeof(struct _nls_cat_hdr) +
307 ntohl((u_int32_t)
308 cat_hdr->__msg_txt_offset) +
309 ntohl((u_int32_t)
310 msg_hdr[i].__offset));
311 } else if (r < 0) {
312 u = i - 1;
313 } else {
314 l = i + 1;
315 }
316 }
317
318 /* not found */
319 goto notfound;
320
321 } else if (r < 0) {
322 u = i - 1;
323 } else {
324 l = i + 1;
325 }
326 }
327
328 notfound:
329 /* not found */
330 errno = ENOMSG;
331 /* LINTED interface problem */
332 return ((char *)s);
333 }
334
335 static void
catfree(struct catentry * np)336 catfree(struct catentry *np)
337 {
338
339 if (np->catd != NULL && np->catd != NLERR) {
340 munmap(np->catd->__data, (size_t)np->catd->__size);
341 free(np->catd);
342 }
343 SLIST_REMOVE(&cache, np, catentry, list);
344 free(np->name);
345 free(np->path);
346 free(np->lang);
347 free(np);
348 }
349
350 int
catclose(nl_catd catd)351 catclose(nl_catd catd)
352 {
353 struct catentry *np;
354
355 /* sanity checking */
356 if (catd == NULL || catd == NLERR) {
357 errno = EBADF;
358 return (-1);
359 }
360
361 /* Remove from cache if not referenced any more */
362 WLOCK(-1);
363 SLIST_FOREACH(np, &cache, list) {
364 if (catd == np->catd) {
365 if (atomic_fetchadd_int(&np->refcount, -1) == 1)
366 catfree(np);
367 break;
368 }
369 }
370 UNLOCK;
371 return (0);
372 }
373
374 /*
375 * Internal support functions
376 */
377
378 static nl_catd
load_msgcat(const char * path,const char * name,const char * lang)379 load_msgcat(const char *path, const char *name, const char *lang)
380 {
381 struct stat st;
382 nl_catd catd;
383 struct catentry *np;
384 void *data;
385 char *copy_path, *copy_name, *copy_lang;
386 int fd;
387
388 /* path/name will never be NULL here */
389
390 /*
391 * One more try in cache; if it was not found by name,
392 * it might still be found by absolute path.
393 */
394 RLOCK(NLERR);
395 SLIST_FOREACH(np, &cache, list) {
396 if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
397 atomic_add_int(&np->refcount, 1);
398 UNLOCK;
399 return (np->catd);
400 }
401 }
402 UNLOCK;
403
404 if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
405 SAVEFAIL(name, lang, errno);
406 NLRETERR(errno);
407 }
408
409 if (_fstat(fd, &st) != 0) {
410 _close(fd);
411 SAVEFAIL(name, lang, EFTYPE);
412 NLRETERR(EFTYPE);
413 }
414
415 /*
416 * If the file size cannot be held in size_t we cannot mmap()
417 * it to the memory. Probably, this will not be a problem given
418 * that catalog files are usually small.
419 */
420 if (st.st_size > SIZE_T_MAX) {
421 _close(fd);
422 SAVEFAIL(name, lang, EFBIG);
423 NLRETERR(EFBIG);
424 }
425
426 if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
427 MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
428 int saved_errno = errno;
429 _close(fd);
430 SAVEFAIL(name, lang, saved_errno);
431 NLRETERR(saved_errno);
432 }
433 _close(fd);
434
435 if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
436 _NLS_MAGIC) {
437 munmap(data, (size_t)st.st_size);
438 SAVEFAIL(name, lang, EFTYPE);
439 NLRETERR(EFTYPE);
440 }
441
442 copy_name = strdup(name);
443 copy_path = strdup(path);
444 copy_lang = (lang == NULL) ? NULL : strdup(lang);
445 catd = malloc(sizeof (*catd));
446 np = calloc(1, sizeof(struct catentry));
447
448 if (copy_name == NULL || copy_path == NULL ||
449 (lang != NULL && copy_lang == NULL) ||
450 catd == NULL || np == NULL) {
451 free(copy_name);
452 free(copy_path);
453 free(copy_lang);
454 free(catd);
455 free(np);
456 munmap(data, (size_t)st.st_size);
457 SAVEFAIL(name, lang, ENOMEM);
458 NLRETERR(ENOMEM);
459 }
460
461 catd->__data = data;
462 catd->__size = (int)st.st_size;
463
464 /* Caching opened catalog */
465 np->name = copy_name;
466 np->path = copy_path;
467 np->catd = catd;
468 np->lang = copy_lang;
469 atomic_store_int(&np->refcount, 1);
470 WLOCK(NLERR);
471 SLIST_INSERT_HEAD(&cache, np, list);
472 UNLOCK;
473 return (catd);
474 }
475