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