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