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