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