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 } 447