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