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