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