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