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