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