1 /* $NetBSD: gettext.c,v 1.28 2012/07/30 23:04:42 yamt Exp $ */ 2 3 /*- 4 * Copyright (c) 2000, 2001 Citrus Project, 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * $Citrus: xpg4dl/FreeBSD/lib/libintl/gettext.c,v 1.31 2001/09/27 15:18:45 yamt Exp $ 29 */ 30 31 #include <sys/cdefs.h> 32 __RCSID("$NetBSD: gettext.c,v 1.28 2012/07/30 23:04:42 yamt Exp $"); 33 34 #include <sys/param.h> 35 #include <sys/stat.h> 36 #include <sys/mman.h> 37 #include <sys/uio.h> 38 39 #include <assert.h> 40 #include <fcntl.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <unistd.h> 44 #include <string.h> 45 #if 0 46 #include <util.h> 47 #endif 48 #include <libintl.h> 49 #include <locale.h> 50 #include "libintl_local.h" 51 #include "plural_parser.h" 52 #include "pathnames.h" 53 54 static const char *lookup_category(int); 55 static const char *split_locale(const char *); 56 static const char *lookup_mofile(char *, size_t, const char *, const char *, 57 const char *, const char *, 58 struct domainbinding *); 59 static uint32_t flip(uint32_t, uint32_t); 60 static int validate(void *, struct mohandle *); 61 static int mapit(const char *, struct domainbinding *); 62 static int unmapit(struct domainbinding *); 63 static const char *lookup_hash(const char *, struct domainbinding *, size_t *); 64 static const char *lookup_bsearch(const char *, struct domainbinding *, 65 size_t *); 66 static const char *lookup(const char *, struct domainbinding *, size_t *); 67 static const char *get_lang_env(const char *); 68 69 /* 70 * shortcut functions. the main implementation resides in dcngettext(). 71 */ 72 char * 73 gettext(const char *msgid) 74 { 75 76 return dcngettext(NULL, msgid, NULL, 1UL, LC_MESSAGES); 77 } 78 79 char * 80 dgettext(const char *domainname, const char *msgid) 81 { 82 83 return dcngettext(domainname, msgid, NULL, 1UL, LC_MESSAGES); 84 } 85 86 char * 87 dcgettext(const char *domainname, const char *msgid, int category) 88 { 89 90 return dcngettext(domainname, msgid, NULL, 1UL, category); 91 } 92 93 char * 94 ngettext(const char *msgid1, const char *msgid2, unsigned long int n) 95 { 96 97 return dcngettext(NULL, msgid1, msgid2, n, LC_MESSAGES); 98 } 99 100 char * 101 dngettext(const char *domainname, const char *msgid1, const char *msgid2, 102 unsigned long int n) 103 { 104 105 return dcngettext(domainname, msgid1, msgid2, n, LC_MESSAGES); 106 } 107 108 /* 109 * dcngettext() - 110 * lookup internationalized message on database locale/category/domainname 111 * (like ja_JP.eucJP/LC_MESSAGES/domainname). 112 * if n equals to 1, internationalized message will be looked up for msgid1. 113 * otherwise, message will be looked up for msgid2. 114 * if the lookup fails, the function will return msgid1 or msgid2 as is. 115 * 116 * Even though the return type is "char *", caller should not rewrite the 117 * region pointed to by the return value (should be "const char *", but can't 118 * change it for compatibility with other implementations). 119 * 120 * by default (if domainname == NULL), domainname is taken from the value set 121 * by textdomain(). usually name of the application (like "ls") is used as 122 * domainname. category is usually LC_MESSAGES. 123 * 124 * the code reads in *.mo files generated by GNU gettext. *.mo is a host- 125 * endian encoded file. both endians are supported here, as the files are in 126 * /usr/share/locale! (or we should move those files into /usr/libdata) 127 */ 128 129 static const char * 130 lookup_category(int category) 131 { 132 133 switch (category) { 134 case LC_COLLATE: return "LC_COLLATE"; 135 case LC_CTYPE: return "LC_CTYPE"; 136 case LC_MONETARY: return "LC_MONETARY"; 137 case LC_NUMERIC: return "LC_NUMERIC"; 138 case LC_TIME: return "LC_TIME"; 139 case LC_MESSAGES: return "LC_MESSAGES"; 140 } 141 return NULL; 142 } 143 144 /* 145 * XPG syntax: language[_territory[.codeset]][@modifier] 146 * XXX boundary check on "result" is lacking 147 */ 148 static const char * 149 split_locale(const char *lname) 150 { 151 char buf[BUFSIZ], tmp[BUFSIZ]; 152 char *l, *t, *c, *m; 153 static char result[BUFSIZ]; 154 155 memset(result, 0, sizeof(result)); 156 157 if (strlen(lname) + 1 > sizeof(buf)) { 158 fail: 159 return lname; 160 } 161 162 strlcpy(buf, lname, sizeof(buf)); 163 m = strrchr(buf, '@'); 164 if (m) 165 *m++ = '\0'; 166 c = strrchr(buf, '.'); 167 if (c) 168 *c++ = '\0'; 169 t = strrchr(buf, '_'); 170 if (t) 171 *t++ = '\0'; 172 l = buf; 173 if (strlen(l) == 0) 174 goto fail; 175 if (c && !t) 176 goto fail; 177 178 if (m) { 179 if (t) { 180 if (c) { 181 snprintf(tmp, sizeof(tmp), "%s_%s.%s@%s", 182 l, t, c, m); 183 strlcat(result, tmp, sizeof(result)); 184 strlcat(result, ":", sizeof(result)); 185 } 186 snprintf(tmp, sizeof(tmp), "%s_%s@%s", l, t, m); 187 strlcat(result, tmp, sizeof(result)); 188 strlcat(result, ":", sizeof(result)); 189 } 190 snprintf(tmp, sizeof(tmp), "%s@%s", l, m); 191 strlcat(result, tmp, sizeof(result)); 192 strlcat(result, ":", sizeof(result)); 193 } 194 if (t) { 195 if (c) { 196 snprintf(tmp, sizeof(tmp), "%s_%s.%s", l, t, c); 197 strlcat(result, tmp, sizeof(result)); 198 strlcat(result, ":", sizeof(result)); 199 } 200 snprintf(tmp, sizeof(tmp), "%s_%s", l, t); 201 strlcat(result, tmp, sizeof(result)); 202 strlcat(result, ":", sizeof(result)); 203 } 204 strlcat(result, l, sizeof(result)); 205 206 return result; 207 } 208 209 static const char * 210 lookup_mofile(char *buf, size_t len, const char *dir, const char *lpath, 211 const char *category, const char *domainname, 212 struct domainbinding *db) 213 { 214 struct stat st; 215 char *p, *q; 216 char lpath_tmp[BUFSIZ]; 217 218 /* 219 * LANGUAGE is a colon separated list of locale names. 220 */ 221 222 strlcpy(lpath_tmp, lpath, sizeof(lpath_tmp)); 223 q = lpath_tmp; 224 /* CONSTCOND */ 225 while (1) { 226 p = strsep(&q, ":"); 227 if (!p) 228 break; 229 if (!*p) 230 continue; 231 232 /* don't mess with default locales */ 233 if (strcmp(p, "C") == 0 || strcmp(p, "POSIX") == 0) 234 return NULL; 235 236 /* validate pathname */ 237 if (strchr(p, '/') || strchr(category, '/')) 238 continue; 239 #if 1 /*?*/ 240 if (strchr(domainname, '/')) 241 continue; 242 #endif 243 244 snprintf(buf, len, "%s/%s/%s/%s.mo", dir, p, 245 category, domainname); 246 if (stat(buf, &st) < 0) 247 continue; 248 if ((st.st_mode & S_IFMT) != S_IFREG) 249 continue; 250 251 if (mapit(buf, db) == 0) 252 return buf; 253 } 254 255 return NULL; 256 } 257 258 static uint32_t 259 flip(uint32_t v, uint32_t magic) 260 { 261 262 if (magic == MO_MAGIC) 263 return v; 264 else if (magic == MO_MAGIC_SWAPPED) { 265 v = ((v >> 24) & 0xff) | ((v >> 8) & 0xff00) | 266 ((v << 8) & 0xff0000) | ((v << 24) & 0xff000000); 267 return v; 268 } else { 269 abort(); 270 /*NOTREACHED*/ 271 } 272 } 273 274 static int 275 validate(void *arg, struct mohandle *mohandle) 276 { 277 char *p; 278 279 p = (char *)arg; 280 if (p < (char *)mohandle->addr || 281 p > (char *)mohandle->addr + mohandle->len) 282 return 0; 283 else 284 return 1; 285 } 286 287 /* 288 * calculate the step value if the hash value is conflicted. 289 */ 290 static __inline uint32_t 291 calc_collision_step(uint32_t hashval, uint32_t hashsize) 292 { 293 _DIAGASSERT(hashsize>2); 294 return (hashval % (hashsize - 2)) + 1; 295 } 296 297 /* 298 * calculate the next index while conflicting. 299 */ 300 static __inline uint32_t 301 calc_next_index(uint32_t curidx, uint32_t hashsize, uint32_t step) 302 { 303 return curidx+step - (curidx >= hashsize-step ? hashsize : 0); 304 } 305 306 static int 307 get_sysdep_string_table(struct mosysdepstr_h **table_h, uint32_t *ofstable, 308 uint32_t nstrings, uint32_t magic, char *base) 309 { 310 unsigned int i; 311 int j, count; 312 size_t l; 313 struct mosysdepstr *table; 314 315 for (i=0; i<nstrings; i++) { 316 /* get mosysdepstr record */ 317 /* LINTED: ignore the alignment problem. */ 318 table = (struct mosysdepstr *)(base + flip(ofstable[i], magic)); 319 /* count number of segments */ 320 count = 0; 321 while (flip(table->segs[count++].ref, magic) != MO_LASTSEG) 322 ; 323 /* get table */ 324 l = sizeof(struct mosysdepstr_h) + 325 sizeof(struct mosysdepsegentry_h) * (count-1); 326 table_h[i] = (struct mosysdepstr_h *)malloc(l); 327 if (!table_h[i]) 328 return -1; 329 memset(table_h[i], 0, l); 330 table_h[i]->off = (const char *)(base + flip(table->off, magic)); 331 for (j=0; j<count; j++) { 332 table_h[i]->segs[j].len = 333 flip(table->segs[j].len, magic); 334 table_h[i]->segs[j].ref = 335 flip(table->segs[j].ref, magic); 336 } 337 /* LINTED: ignore the alignment problem. */ 338 table = (struct mosysdepstr *)&table->segs[count]; 339 } 340 return 0; 341 } 342 343 static int 344 expand_sysdep(struct mohandle *mohandle, struct mosysdepstr_h *str) 345 { 346 int i; 347 const char *src; 348 char *dst; 349 350 /* check whether already expanded */ 351 if (str->expanded) 352 return 0; 353 354 /* calc total length */ 355 str->expanded_len = 1; 356 for (i=0; /*CONSTCOND*/1; i++) { 357 str->expanded_len += str->segs[i].len; 358 if (str->segs[i].ref == MO_LASTSEG) 359 break; 360 str->expanded_len += 361 mohandle->mo.mo_sysdep_segs[str->segs[i].ref].len; 362 } 363 /* expand */ 364 str->expanded = malloc(str->expanded_len); 365 if (!str->expanded) 366 return -1; 367 src = str->off; 368 dst = str->expanded; 369 for (i=0; /*CONSTCOND*/1; i++) { 370 memcpy(dst, src, str->segs[i].len); 371 src += str->segs[i].len; 372 dst += str->segs[i].len; 373 if (str->segs[i].ref == MO_LASTSEG) 374 break; 375 memcpy(dst, mohandle->mo.mo_sysdep_segs[str->segs[i].ref].str, 376 mohandle->mo.mo_sysdep_segs[str->segs[i].ref].len); 377 dst += mohandle->mo.mo_sysdep_segs[str->segs[i].ref].len; 378 } 379 *dst = '\0'; 380 381 return 0; 382 } 383 384 static void 385 insert_to_hash(uint32_t *htable, uint32_t hsize, const char *str, uint32_t ref) 386 { 387 uint32_t hashval, idx, step; 388 389 hashval = __intl_string_hash(str); 390 step = calc_collision_step(hashval, hsize); 391 idx = hashval % hsize; 392 393 while (htable[idx]) 394 idx = calc_next_index(idx, hsize, step); 395 396 htable[idx] = ref; 397 } 398 399 static int 400 setup_sysdep_stuffs(struct mo *mo, struct mohandle *mohandle, char *base) 401 { 402 uint32_t magic; 403 struct moentry *stable; 404 size_t l; 405 unsigned int i; 406 char *v; 407 uint32_t *ofstable; 408 409 magic = mo->mo_magic; 410 411 mohandle->mo.mo_sysdep_nsegs = flip(mo->mo_sysdep_nsegs, magic); 412 mohandle->mo.mo_sysdep_nstring = flip(mo->mo_sysdep_nstring, magic); 413 414 if (mohandle->mo.mo_sysdep_nstring == 0) 415 return 0; 416 417 /* check hash size */ 418 if (mohandle->mo.mo_hsize <= 2 || 419 mohandle->mo.mo_hsize < 420 (mohandle->mo.mo_nstring + mohandle->mo.mo_sysdep_nstring)) 421 return -1; 422 423 /* get sysdep segments */ 424 l = sizeof(struct mosysdepsegs_h) * mohandle->mo.mo_sysdep_nsegs; 425 mohandle->mo.mo_sysdep_segs = (struct mosysdepsegs_h *)malloc(l); 426 if (!mohandle->mo.mo_sysdep_segs) 427 return -1; 428 /* LINTED: ignore the alignment problem. */ 429 stable = (struct moentry *)(base + flip(mo->mo_sysdep_segoff, magic)); 430 for (i=0; i<mohandle->mo.mo_sysdep_nsegs; i++) { 431 v = base + flip(stable[i].off, magic); 432 mohandle->mo.mo_sysdep_segs[i].str = 433 __intl_sysdep_get_string_by_tag( 434 v, 435 &mohandle->mo.mo_sysdep_segs[i].len); 436 } 437 438 /* get sysdep string table */ 439 mohandle->mo.mo_sysdep_otable = 440 (struct mosysdepstr_h **)calloc(mohandle->mo.mo_sysdep_nstring, 441 sizeof(struct mosysdepstr_h *)); 442 if (!mohandle->mo.mo_sysdep_otable) 443 return -1; 444 /* LINTED: ignore the alignment problem. */ 445 ofstable = (uint32_t *)(base + flip(mo->mo_sysdep_otable, magic)); 446 if (get_sysdep_string_table(mohandle->mo.mo_sysdep_otable, ofstable, 447 mohandle->mo.mo_sysdep_nstring, magic, 448 base)) 449 return -1; 450 mohandle->mo.mo_sysdep_ttable = 451 (struct mosysdepstr_h **)calloc(mohandle->mo.mo_sysdep_nstring, 452 sizeof(struct mosysdepstr_h *)); 453 if (!mohandle->mo.mo_sysdep_ttable) 454 return -1; 455 /* LINTED: ignore the alignment problem. */ 456 ofstable = (uint32_t *)(base + flip(mo->mo_sysdep_ttable, magic)); 457 if (get_sysdep_string_table(mohandle->mo.mo_sysdep_ttable, ofstable, 458 mohandle->mo.mo_sysdep_nstring, magic, 459 base)) 460 return -1; 461 462 /* update hash */ 463 for (i=0; i<mohandle->mo.mo_sysdep_nstring; i++) { 464 if (expand_sysdep(mohandle, mohandle->mo.mo_sysdep_otable[i])) 465 return -1; 466 insert_to_hash(mohandle->mo.mo_htable, 467 mohandle->mo.mo_hsize, 468 mohandle->mo.mo_sysdep_otable[i]->expanded, 469 (i+1) | MO_HASH_SYSDEP_MASK); 470 } 471 472 return 0; 473 } 474 475 int 476 mapit(const char *path, struct domainbinding *db) 477 { 478 int fd; 479 struct stat st; 480 char *base; 481 uint32_t magic, revision, flags = 0; 482 struct moentry *otable, *ttable; 483 const uint32_t *htable; 484 struct moentry_h *p; 485 struct mo *mo; 486 size_t l, headerlen; 487 unsigned int i; 488 char *v; 489 struct mohandle *mohandle = &db->mohandle; 490 491 if (mohandle->addr && mohandle->addr != MAP_FAILED && 492 mohandle->mo.mo_magic) 493 return 0; /*already opened*/ 494 495 unmapit(db); 496 497 #if 0 498 if (secure_path(path) != 0) 499 goto fail; 500 #endif 501 if (stat(path, &st) < 0) 502 goto fail; 503 if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size > GETTEXT_MMAP_MAX) 504 goto fail; 505 fd = open(path, O_RDONLY); 506 if (fd < 0) 507 goto fail; 508 if (read(fd, &magic, sizeof(magic)) != sizeof(magic) || 509 (magic != MO_MAGIC && magic != MO_MAGIC_SWAPPED)) { 510 close(fd); 511 goto fail; 512 } 513 if (read(fd, &revision, sizeof(revision)) != sizeof(revision)) { 514 close(fd); 515 goto fail; 516 } 517 switch (flip(revision, magic)) { 518 case MO_MAKE_REV(0, 0): 519 break; 520 case MO_MAKE_REV(0, 1): 521 case MO_MAKE_REV(1, 1): 522 flags |= MO_F_SYSDEP; 523 break; 524 default: 525 close(fd); 526 goto fail; 527 } 528 mohandle->addr = mmap(NULL, (size_t)st.st_size, PROT_READ, 529 MAP_FILE | MAP_SHARED, fd, (off_t)0); 530 if (!mohandle->addr || mohandle->addr == MAP_FAILED) { 531 close(fd); 532 goto fail; 533 } 534 close(fd); 535 mohandle->len = (size_t)st.st_size; 536 537 base = mohandle->addr; 538 mo = (struct mo *)mohandle->addr; 539 540 /* flip endian. do not flip magic number! */ 541 mohandle->mo.mo_magic = mo->mo_magic; 542 mohandle->mo.mo_revision = flip(mo->mo_revision, magic); 543 mohandle->mo.mo_nstring = flip(mo->mo_nstring, magic); 544 mohandle->mo.mo_hsize = flip(mo->mo_hsize, magic); 545 mohandle->mo.mo_flags = flags; 546 547 /* validate otable/ttable */ 548 /* LINTED: ignore the alignment problem. */ 549 otable = (struct moentry *)(base + flip(mo->mo_otable, magic)); 550 /* LINTED: ignore the alignment problem. */ 551 ttable = (struct moentry *)(base + flip(mo->mo_ttable, magic)); 552 if (!validate(otable, mohandle) || 553 !validate(&otable[mohandle->mo.mo_nstring], mohandle)) { 554 unmapit(db); 555 goto fail; 556 } 557 if (!validate(ttable, mohandle) || 558 !validate(&ttable[mohandle->mo.mo_nstring], mohandle)) { 559 unmapit(db); 560 goto fail; 561 } 562 563 /* allocate [ot]table, and convert to normal pointer representation. */ 564 l = sizeof(struct moentry_h) * mohandle->mo.mo_nstring; 565 mohandle->mo.mo_otable = (struct moentry_h *)malloc(l); 566 if (!mohandle->mo.mo_otable) { 567 unmapit(db); 568 goto fail; 569 } 570 mohandle->mo.mo_ttable = (struct moentry_h *)malloc(l); 571 if (!mohandle->mo.mo_ttable) { 572 unmapit(db); 573 goto fail; 574 } 575 p = mohandle->mo.mo_otable; 576 for (i = 0; i < mohandle->mo.mo_nstring; i++) { 577 p[i].len = flip(otable[i].len, magic); 578 p[i].off = base + flip(otable[i].off, magic); 579 580 if (!validate(p[i].off, mohandle) || 581 !validate(p[i].off + p[i].len + 1, mohandle)) { 582 unmapit(db); 583 goto fail; 584 } 585 } 586 p = mohandle->mo.mo_ttable; 587 for (i = 0; i < mohandle->mo.mo_nstring; i++) { 588 p[i].len = flip(ttable[i].len, magic); 589 p[i].off = base + flip(ttable[i].off, magic); 590 591 if (!validate(p[i].off, mohandle) || 592 !validate(p[i].off + p[i].len + 1, mohandle)) { 593 unmapit(db); 594 goto fail; 595 } 596 } 597 /* allocate htable, and convert it to the host order. */ 598 if (mohandle->mo.mo_hsize > 2) { 599 l = sizeof(uint32_t) * mohandle->mo.mo_hsize; 600 mohandle->mo.mo_htable = (uint32_t *)malloc(l); 601 if (!mohandle->mo.mo_htable) { 602 unmapit(db); 603 goto fail; 604 } 605 /* LINTED: ignore the alignment problem. */ 606 htable = (const uint32_t *)(base+flip(mo->mo_hoffset, magic)); 607 for (i=0; i < mohandle->mo.mo_hsize; i++) { 608 mohandle->mo.mo_htable[i] = flip(htable[i], magic); 609 if (mohandle->mo.mo_htable[i] >= 610 mohandle->mo.mo_nstring+1) { 611 /* illegal string number. */ 612 unmapit(db); 613 goto fail; 614 } 615 } 616 } 617 /* grab MIME-header and charset field */ 618 mohandle->mo.mo_header = lookup("", db, &headerlen); 619 if (mohandle->mo.mo_header) 620 v = strstr(mohandle->mo.mo_header, "charset="); 621 else 622 v = NULL; 623 if (v) { 624 mohandle->mo.mo_charset = strdup(v + 8); 625 if (!mohandle->mo.mo_charset) 626 goto fail; 627 v = strchr(mohandle->mo.mo_charset, '\n'); 628 if (v) 629 *v = '\0'; 630 } 631 if (!mohandle->mo.mo_header || 632 _gettext_parse_plural(&mohandle->mo.mo_plural, 633 &mohandle->mo.mo_nplurals, 634 mohandle->mo.mo_header, headerlen)) 635 mohandle->mo.mo_plural = NULL; 636 637 /* 638 * XXX check charset, reject it if we are unable to support the charset 639 * with the current locale. 640 * for example, if we are using euc-jp locale and we are looking at 641 * *.mo file encoded by euc-kr (charset=euc-kr), we should reject 642 * the *.mo file as we cannot support it. 643 */ 644 645 /* system dependent string support */ 646 if ((mohandle->mo.mo_flags & MO_F_SYSDEP) != 0) { 647 if (setup_sysdep_stuffs(mo, mohandle, base)) { 648 unmapit(db); 649 goto fail; 650 } 651 } 652 653 return 0; 654 655 fail: 656 return -1; 657 } 658 659 static void 660 free_sysdep_table(struct mosysdepstr_h **table, uint32_t nstring) 661 { 662 uint32_t i; 663 664 for (i=0; i<nstring; i++) { 665 if (table[i]) { 666 if (table[i]->expanded) 667 free(table[i]->expanded); 668 free(table[i]); 669 } 670 } 671 free(table); 672 } 673 674 static int 675 unmapit(struct domainbinding *db) 676 { 677 struct mohandle *mohandle = &db->mohandle; 678 679 /* unmap if there's already mapped region */ 680 if (mohandle->addr && mohandle->addr != MAP_FAILED) 681 munmap(mohandle->addr, mohandle->len); 682 mohandle->addr = NULL; 683 if (mohandle->mo.mo_otable) 684 free(mohandle->mo.mo_otable); 685 if (mohandle->mo.mo_ttable) 686 free(mohandle->mo.mo_ttable); 687 if (mohandle->mo.mo_charset) 688 free(mohandle->mo.mo_charset); 689 if (mohandle->mo.mo_htable) 690 free(mohandle->mo.mo_htable); 691 if (mohandle->mo.mo_sysdep_segs) 692 free(mohandle->mo.mo_sysdep_segs); 693 if (mohandle->mo.mo_sysdep_otable) { 694 free_sysdep_table(mohandle->mo.mo_sysdep_otable, 695 mohandle->mo.mo_sysdep_nstring); 696 } 697 if (mohandle->mo.mo_sysdep_ttable) { 698 free_sysdep_table(mohandle->mo.mo_sysdep_ttable, 699 mohandle->mo.mo_sysdep_nstring); 700 } 701 if (mohandle->mo.mo_plural) 702 _gettext_free_plural(mohandle->mo.mo_plural); 703 memset(&mohandle->mo, 0, sizeof(mohandle->mo)); 704 return 0; 705 } 706 707 /* ARGSUSED */ 708 static const char * 709 lookup_hash(const char *msgid, struct domainbinding *db, size_t *rlen) 710 { 711 struct mohandle *mohandle = &db->mohandle; 712 uint32_t idx, hashval, step, strno; 713 size_t len; 714 struct mosysdepstr_h *sysdep_otable, *sysdep_ttable; 715 716 if (mohandle->mo.mo_hsize <= 2 || mohandle->mo.mo_htable == NULL) 717 return NULL; 718 719 hashval = __intl_string_hash(msgid); 720 step = calc_collision_step(hashval, mohandle->mo.mo_hsize); 721 idx = hashval % mohandle->mo.mo_hsize; 722 len = strlen(msgid); 723 while (/*CONSTCOND*/1) { 724 strno = mohandle->mo.mo_htable[idx]; 725 if (strno == 0) { 726 /* unexpected miss */ 727 return NULL; 728 } 729 strno--; 730 if ((strno & MO_HASH_SYSDEP_MASK) == 0) { 731 /* system independent strings */ 732 if (len <= mohandle->mo.mo_otable[strno].len && 733 !strcmp(msgid, mohandle->mo.mo_otable[strno].off)) { 734 /* hit */ 735 if (rlen) 736 *rlen = 737 mohandle->mo.mo_ttable[strno].len; 738 return mohandle->mo.mo_ttable[strno].off; 739 } 740 } else { 741 /* system dependent strings */ 742 strno &= ~MO_HASH_SYSDEP_MASK; 743 sysdep_otable = mohandle->mo.mo_sysdep_otable[strno]; 744 sysdep_ttable = mohandle->mo.mo_sysdep_ttable[strno]; 745 if (len <= sysdep_otable->expanded_len && 746 !strcmp(msgid, sysdep_otable->expanded)) { 747 /* hit */ 748 if (expand_sysdep(mohandle, sysdep_ttable)) 749 /* memory exhausted */ 750 return NULL; 751 if (rlen) 752 *rlen = sysdep_ttable->expanded_len; 753 return sysdep_ttable->expanded; 754 } 755 } 756 idx = calc_next_index(idx, mohandle->mo.mo_hsize, step); 757 } 758 /*NOTREACHED*/ 759 } 760 761 static const char * 762 lookup_bsearch(const char *msgid, struct domainbinding *db, size_t *rlen) 763 { 764 int top, bottom, middle, omiddle; 765 int n; 766 struct mohandle *mohandle = &db->mohandle; 767 768 top = 0; 769 bottom = mohandle->mo.mo_nstring; 770 omiddle = -1; 771 /* CONSTCOND */ 772 while (1) { 773 if (top > bottom) 774 break; 775 middle = (top + bottom) / 2; 776 /* avoid possible infinite loop, when the data is not sorted */ 777 if (omiddle == middle) 778 break; 779 if ((size_t)middle >= mohandle->mo.mo_nstring) 780 break; 781 782 n = strcmp(msgid, mohandle->mo.mo_otable[middle].off); 783 if (n == 0) { 784 if (rlen) 785 *rlen = mohandle->mo.mo_ttable[middle].len; 786 return (const char *)mohandle->mo.mo_ttable[middle].off; 787 } 788 else if (n < 0) 789 bottom = middle; 790 else 791 top = middle; 792 omiddle = middle; 793 } 794 795 return NULL; 796 } 797 798 static const char * 799 lookup(const char *msgid, struct domainbinding *db, size_t *rlen) 800 { 801 const char *v; 802 803 v = lookup_hash(msgid, db, rlen); 804 if (v) 805 return v; 806 807 return lookup_bsearch(msgid, db, rlen); 808 } 809 810 static const char * 811 get_lang_env(const char *category_name) 812 { 813 const char *lang; 814 815 /* 816 * 1. see LANGUAGE variable first. 817 * 818 * LANGUAGE is a GNU extension. 819 * It's a colon separated list of locale names. 820 */ 821 lang = getenv("LANGUAGE"); 822 if (lang) 823 return lang; 824 825 /* 826 * 2. if LANGUAGE isn't set, see LC_ALL, LC_xxx, LANG. 827 * 828 * It's essentially setlocale(LC_xxx, NULL). 829 */ 830 lang = getenv("LC_ALL"); 831 if (!lang) 832 lang = getenv(category_name); 833 if (!lang) 834 lang = getenv("LANG"); 835 836 if (!lang) 837 return 0; /* error */ 838 839 return split_locale(lang); 840 } 841 842 static const char * 843 get_indexed_string(const char *str, size_t len, unsigned long idx) 844 { 845 while (idx > 0) { 846 if (len <= 1) 847 return str; 848 if (*str == '\0') 849 idx--; 850 if (len > 0) { 851 str++; 852 len--; 853 } 854 } 855 return str; 856 } 857 858 #define _NGETTEXT_DEFAULT(msgid1, msgid2, n) \ 859 ((char *)__UNCONST((n) == 1 ? (msgid1) : (msgid2))) 860 861 char * 862 dcngettext(const char *domainname, const char *msgid1, const char *msgid2, 863 unsigned long int n, int category) 864 { 865 const char *msgid; 866 char path[PATH_MAX]; 867 const char *lpath; 868 static char olpath[PATH_MAX]; 869 const char *cname = NULL; 870 const char *v; 871 static char *ocname = NULL; 872 static char *odomainname = NULL; 873 struct domainbinding *db; 874 unsigned long plural_index = 0; 875 size_t len; 876 877 if (!domainname) 878 domainname = __current_domainname; 879 cname = lookup_category(category); 880 if (!domainname || !cname) 881 goto fail; 882 883 lpath = get_lang_env(cname); 884 if (!lpath) 885 goto fail; 886 887 for (db = __bindings; db; db = db->next) 888 if (strcmp(db->domainname, domainname) == 0) 889 break; 890 if (!db) { 891 if (!bindtextdomain(domainname, _PATH_TEXTDOMAIN)) 892 goto fail; 893 db = __bindings; 894 } 895 896 /* resolve relative path */ 897 /* XXX not necessary? */ 898 if (db->path[0] != '/') { 899 char buf[PATH_MAX]; 900 901 if (getcwd(buf, sizeof(buf)) == 0) 902 goto fail; 903 if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf)) 904 goto fail; 905 if (strlcat(buf, db->path, sizeof(buf)) >= sizeof(buf)) 906 goto fail; 907 strlcpy(db->path, buf, sizeof(db->path)); 908 } 909 910 /* don't bother looking it up if the values are the same */ 911 if (odomainname && strcmp(domainname, odomainname) == 0 && 912 ocname && strcmp(cname, ocname) == 0 && strcmp(lpath, olpath) == 0 && 913 db->mohandle.mo.mo_magic) 914 goto found; 915 916 /* try to find appropriate file, from $LANGUAGE */ 917 if (lookup_mofile(path, sizeof(path), db->path, lpath, cname, 918 domainname, db) == NULL) 919 goto fail; 920 921 if (odomainname) 922 free(odomainname); 923 if (ocname) 924 free(ocname); 925 odomainname = strdup(domainname); 926 ocname = strdup(cname); 927 if (!odomainname || !ocname) { 928 if (odomainname) 929 free(odomainname); 930 if (ocname) 931 free(ocname); 932 odomainname = ocname = NULL; 933 } 934 else 935 strlcpy(olpath, lpath, sizeof(olpath)); 936 937 found: 938 if (db->mohandle.mo.mo_plural) { 939 plural_index = 940 _gettext_calculate_plural(db->mohandle.mo.mo_plural, n); 941 if (plural_index >= db->mohandle.mo.mo_nplurals) 942 plural_index = 0; 943 msgid = msgid1; 944 } else 945 msgid = _NGETTEXT_DEFAULT(msgid1, msgid2, n); 946 947 if (msgid == NULL) 948 return NULL; 949 950 v = lookup(msgid, db, &len); 951 if (v) { 952 if (db->mohandle.mo.mo_plural) 953 v = get_indexed_string(v, len, plural_index); 954 /* 955 * convert the translated message's encoding. 956 * 957 * special case: 958 * a result of gettext("") shouldn't need any conversion. 959 */ 960 if (msgid[0]) 961 v = __gettext_iconv(v, db); 962 963 /* 964 * Given the amount of printf-format security issues, it may 965 * be a good idea to validate if the original msgid and the 966 * translated message format string carry the same printf-like 967 * format identifiers. 968 */ 969 970 msgid = v; 971 } 972 973 return (char *)__UNCONST(msgid); 974 975 fail: 976 return _NGETTEXT_DEFAULT(msgid1, msgid2, n); 977 } 978