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