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