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
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <limits.h>
50 #include <locale.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/setlocale.h" /* for ENCODING_LEN */
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) { WLOCK(NLERR); \
81 np = malloc(sizeof(struct catentry)); \
82 if (np != NULL) { \
83 np->name = strdup(n); \
84 np->path = NULL; \
85 np->catd = NLERR; \
86 np->refcount = 0; \
87 np->lang = (l == NULL) ? NULL : \
88 strdup(l); \
89 np->caterrno = e; \
90 SLIST_INSERT_HEAD(&cache, np, list); \
91 } \
92 UNLOCK; \
93 errno = e; \
94 }
95
96 static nl_catd load_msgcat(const char *, const char *, const char *);
97
98 static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
99
100 struct catentry {
101 SLIST_ENTRY(catentry) list;
102 char *name;
103 char *path;
104 int caterrno;
105 nl_catd catd;
106 char *lang;
107 int refcount;
108 };
109
110 SLIST_HEAD(listhead, catentry) cache =
111 SLIST_HEAD_INITIALIZER(cache);
112
113 nl_catd
catopen(const char * name,int type)114 catopen(const char *name, int type)
115 {
116 struct stat sbuf;
117 struct catentry *np;
118 char *base, *cptr, *cptr1, *lang, *nlspath, *pathP, *pcode;
119 char *plang, *pter, *tmpptr;
120 int saverr, spcleft;
121 char path[PATH_MAX];
122
123 /* sanity checking */
124 if (name == NULL || *name == '\0')
125 NLRETERR(EINVAL);
126
127 if (strchr(name, '/') != NULL)
128 /* have a pathname */
129 lang = NULL;
130 else {
131 if (type == NL_CAT_LOCALE)
132 lang = setlocale(LC_MESSAGES, NULL);
133 else
134 lang = getenv("LANG");
135
136 if (lang == NULL || *lang == '\0' || strlen(lang) > ENCODING_LEN ||
137 (lang[0] == '.' &&
138 (lang[1] == '\0' || (lang[1] == '.' && lang[2] == '\0'))) ||
139 strchr(lang, '/') != NULL)
140 lang = "C";
141 }
142
143 /* Try to get it from the cache first */
144 RLOCK(NLERR);
145 SLIST_FOREACH(np, &cache, list) {
146 if ((strcmp(np->name, name) == 0) &&
147 ((lang != NULL && np->lang != NULL &&
148 strcmp(np->lang, lang) == 0) || (np->lang == lang))) {
149 if (np->caterrno != 0) {
150 /* Found cached failing entry */
151 UNLOCK;
152 NLRETERR(np->caterrno);
153 } else {
154 /* Found cached successful entry */
155 np->refcount++;
156 UNLOCK;
157 return (np->catd);
158 }
159 }
160 }
161 UNLOCK;
162
163 /* is it absolute path ? if yes, load immediately */
164 if (strchr(name, '/') != NULL)
165 return (load_msgcat(name, name, lang));
166
167 /* sanity checking */
168 if ((plang = cptr1 = strdup(lang)) == NULL)
169 return (NLERR);
170 if ((cptr = strchr(cptr1, '@')) != NULL)
171 *cptr = '\0';
172 pter = pcode = "";
173 if ((cptr = strchr(cptr1, '_')) != NULL) {
174 *cptr++ = '\0';
175 pter = cptr1 = cptr;
176 }
177 if ((cptr = strchr(cptr1, '.')) != NULL) {
178 *cptr++ = '\0';
179 pcode = cptr;
180 }
181
182 if ((nlspath = getenv("NLSPATH")) == NULL || issetugid())
183 nlspath = _DEFAULT_NLS_PATH;
184
185 if ((base = cptr = strdup(nlspath)) == NULL) {
186 saverr = errno;
187 free(plang);
188 errno = saverr;
189 return (NLERR);
190 }
191
192 while ((nlspath = strsep(&cptr, ":")) != NULL) {
193 pathP = path;
194 if (*nlspath) {
195 for (; *nlspath; ++nlspath) {
196 if (*nlspath == '%') {
197 switch (*(nlspath + 1)) {
198 case 'l':
199 tmpptr = plang;
200 break;
201 case 't':
202 tmpptr = pter;
203 break;
204 case 'c':
205 tmpptr = pcode;
206 break;
207 case 'L':
208 tmpptr = lang;
209 break;
210 case 'N':
211 tmpptr = (char *)name;
212 break;
213 case '%':
214 ++nlspath;
215 /* FALLTHROUGH */
216 default:
217 if (pathP - path >=
218 sizeof(path) - 1)
219 goto too_long;
220 *(pathP++) = *nlspath;
221 continue;
222 }
223 ++nlspath;
224 put_tmpptr:
225 spcleft = sizeof(path) -
226 (pathP - path) - 1;
227 if (strlcpy(pathP, tmpptr, spcleft) >=
228 spcleft) {
229 too_long:
230 free(plang);
231 free(base);
232 SAVEFAIL(name, lang, ENAMETOOLONG);
233 NLRETERR(ENAMETOOLONG);
234 }
235 pathP += strlen(tmpptr);
236 } else {
237 if (pathP - path >= sizeof(path) - 1)
238 goto too_long;
239 *(pathP++) = *nlspath;
240 }
241 }
242 *pathP = '\0';
243 if (stat(path, &sbuf) == 0) {
244 free(plang);
245 free(base);
246 return (load_msgcat(path, name, lang));
247 }
248 } else {
249 tmpptr = (char *)name;
250 --nlspath;
251 goto put_tmpptr;
252 }
253 }
254 free(plang);
255 free(base);
256 SAVEFAIL(name, lang, ENOENT);
257 NLRETERR(ENOENT);
258 }
259
260 char *
catgets(nl_catd catd,int set_id,int msg_id,const char * s)261 catgets(nl_catd catd, int set_id, int msg_id, const char *s)
262 {
263 struct _nls_cat_hdr *cat_hdr;
264 struct _nls_msg_hdr *msg_hdr;
265 struct _nls_set_hdr *set_hdr;
266 int i, l, r, u;
267
268 if (catd == NULL || catd == NLERR) {
269 errno = EBADF;
270 /* LINTED interface problem */
271 return ((char *)s);
272 }
273
274 cat_hdr = (struct _nls_cat_hdr *)catd->__data;
275 set_hdr = (struct _nls_set_hdr *)(void *)((char *)catd->__data +
276 sizeof(struct _nls_cat_hdr));
277
278 /* binary search, see knuth algorithm b */
279 l = 0;
280 u = ntohl((u_int32_t)cat_hdr->__nsets) - 1;
281 while (l <= u) {
282 i = (l + u) / 2;
283 r = set_id - ntohl((u_int32_t)set_hdr[i].__setno);
284
285 if (r == 0) {
286 msg_hdr = (struct _nls_msg_hdr *)
287 (void *)((char *)catd->__data +
288 sizeof(struct _nls_cat_hdr) +
289 ntohl((u_int32_t)cat_hdr->__msg_hdr_offset));
290
291 l = ntohl((u_int32_t)set_hdr[i].__index);
292 u = l + ntohl((u_int32_t)set_hdr[i].__nmsgs) - 1;
293 while (l <= u) {
294 i = (l + u) / 2;
295 r = msg_id -
296 ntohl((u_int32_t)msg_hdr[i].__msgno);
297 if (r == 0) {
298 return ((char *) catd->__data +
299 sizeof(struct _nls_cat_hdr) +
300 ntohl((u_int32_t)
301 cat_hdr->__msg_txt_offset) +
302 ntohl((u_int32_t)
303 msg_hdr[i].__offset));
304 } else if (r < 0) {
305 u = i - 1;
306 } else {
307 l = i + 1;
308 }
309 }
310
311 /* not found */
312 goto notfound;
313
314 } else if (r < 0) {
315 u = i - 1;
316 } else {
317 l = i + 1;
318 }
319 }
320
321 notfound:
322 /* not found */
323 errno = ENOMSG;
324 /* LINTED interface problem */
325 return ((char *)s);
326 }
327
328 int
catclose(nl_catd catd)329 catclose(nl_catd catd)
330 {
331 struct catentry *np;
332
333 /* sanity checking */
334 if (catd == NULL || catd == NLERR) {
335 errno = EBADF;
336 return (-1);
337 }
338
339 /* Remove from cache if not referenced any more */
340 WLOCK(-1);
341 SLIST_FOREACH(np, &cache, list) {
342 if (catd == np->catd) {
343 np->refcount--;
344 if (np->refcount == 0) {
345 munmap(catd->__data, (size_t)catd->__size);
346 free(catd);
347 SLIST_REMOVE(&cache, np, catentry, list);
348 free(np->name);
349 free(np->path);
350 free(np->lang);
351 free(np);
352 }
353 break;
354 }
355 }
356 UNLOCK;
357 return (0);
358 }
359
360 /*
361 * Internal support functions
362 */
363
364 static nl_catd
load_msgcat(const char * path,const char * name,const char * lang)365 load_msgcat(const char *path, const char *name, const char *lang)
366 {
367 struct stat st;
368 nl_catd catd;
369 struct catentry *np;
370 void *data;
371 int fd;
372
373 /* path/name will never be NULL here */
374
375 /*
376 * One more try in cache; if it was not found by name,
377 * it might still be found by absolute path.
378 */
379 RLOCK(NLERR);
380 SLIST_FOREACH(np, &cache, list) {
381 if ((np->path != NULL) && (strcmp(np->path, path) == 0)) {
382 np->refcount++;
383 UNLOCK;
384 return (np->catd);
385 }
386 }
387 UNLOCK;
388
389 if ((fd = _open(path, O_RDONLY | O_CLOEXEC)) == -1) {
390 SAVEFAIL(name, lang, errno);
391 NLRETERR(errno);
392 }
393
394 if (_fstat(fd, &st) != 0) {
395 _close(fd);
396 SAVEFAIL(name, lang, EFTYPE);
397 NLRETERR(EFTYPE);
398 }
399
400 /*
401 * If the file size cannot be held in size_t we cannot mmap()
402 * it to the memory. Probably, this will not be a problem given
403 * that catalog files are usually small.
404 */
405 if (st.st_size > SIZE_T_MAX) {
406 _close(fd);
407 SAVEFAIL(name, lang, EFBIG);
408 NLRETERR(EFBIG);
409 }
410
411 if ((data = mmap(0, (size_t)st.st_size, PROT_READ,
412 MAP_FILE|MAP_SHARED, fd, (off_t)0)) == MAP_FAILED) {
413 int saved_errno = errno;
414 _close(fd);
415 SAVEFAIL(name, lang, saved_errno);
416 NLRETERR(saved_errno);
417 }
418 _close(fd);
419
420 if (ntohl((u_int32_t)((struct _nls_cat_hdr *)data)->__magic) !=
421 _NLS_MAGIC) {
422 munmap(data, (size_t)st.st_size);
423 SAVEFAIL(name, lang, EFTYPE);
424 NLRETERR(EFTYPE);
425 }
426
427 if ((catd = malloc(sizeof (*catd))) == NULL) {
428 munmap(data, (size_t)st.st_size);
429 SAVEFAIL(name, lang, ENOMEM);
430 NLRETERR(ENOMEM);
431 }
432
433 catd->__data = data;
434 catd->__size = (int)st.st_size;
435
436 /* Caching opened catalog */
437 WLOCK(NLERR);
438 if ((np = malloc(sizeof(struct catentry))) != NULL) {
439 np->name = strdup(name);
440 np->path = strdup(path);
441 np->catd = catd;
442 np->lang = (lang == NULL) ? NULL : strdup(lang);
443 np->refcount = 1;
444 np->caterrno = 0;
445 SLIST_INSERT_HEAD(&cache, np, list);
446 }
447 UNLOCK;
448 return (catd);
449 }
450