1 /* ex:ts=4 2 */ 3 4 /* $NetBSD: gencat.c,v 1.18 2003/10/27 00:12:43 lukem Exp $ */ 5 6 /* 7 * Copyright (c) 1996 The NetBSD Foundation, Inc. 8 * All rights reserved. 9 * 10 * This code is derived from software contributed to The NetBSD Foundation 11 * by J.T. Conklin. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 24 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 26 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 * POSSIBILITY OF SUCH DAMAGE. 33 */ 34 35 /*********************************************************** 36 Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts. 37 38 All Rights Reserved 39 40 Permission to use, copy, modify, and distribute this software and its 41 documentation for any purpose and without fee is hereby granted, 42 provided that the above copyright notice appear in all copies and that 43 both that copyright notice and this permission notice appear in 44 supporting documentation, and that Alfalfa's name not be used in 45 advertising or publicity pertaining to distribution of the software 46 without specific, written prior permission. 47 48 ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 49 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL 50 ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 51 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 52 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 53 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 54 SOFTWARE. 55 56 If you make any modifications, bugfixes or other changes to this software 57 we'd appreciate it if you could send a copy to us so we can keep things 58 up-to-date. Many thanks. 59 Kee Hinckley 60 Alfalfa Software, Inc. 61 267 Allston St., #3 62 Cambridge, MA 02139 USA 63 nazgul@alfalfa.com 64 65 ******************************************************************/ 66 67 #include <sys/cdefs.h> 68 __FBSDID("$FreeBSD$"); 69 70 #define _NLS_PRIVATE 71 72 #include <sys/types.h> 73 #include <sys/queue.h> 74 75 #include <arpa/inet.h> /* for htonl() */ 76 77 #include <ctype.h> 78 #include <err.h> 79 #include <fcntl.h> 80 #include <limits.h> 81 #include <nl_types.h> 82 #include <stdio.h> 83 #include <stdlib.h> 84 #include <string.h> 85 #include <unistd.h> 86 87 struct _msgT { 88 long msgId; 89 char *str; 90 LIST_ENTRY(_msgT) entries; 91 }; 92 93 struct _setT { 94 long setId; 95 LIST_HEAD(msghead, _msgT) msghead; 96 LIST_ENTRY(_setT) entries; 97 }; 98 99 LIST_HEAD(sethead, _setT) sethead; 100 static struct _setT *curSet; 101 102 static char *curline = NULL; 103 static long lineno = 0; 104 105 static char *cskip(char *); 106 static void error(const char *); 107 static char *getline(int); 108 static char *getmsg(int, char *, char); 109 static void warning(const char *, const char *); 110 static char *wskip(char *); 111 static char *xstrdup(const char *); 112 static void *xmalloc(size_t); 113 static void *xrealloc(void *, size_t); 114 115 void MCParse(int); 116 void MCReadCat(int); 117 void MCWriteCat(int); 118 void MCDelMsg(int); 119 void MCAddMsg(int, const char *); 120 void MCAddSet(int); 121 void MCDelSet(int); 122 void usage(void); 123 int main(int, char **); 124 125 void 126 usage(void) 127 { 128 fprintf(stderr, "usage: %s catfile msgfile ...\n", getprogname()); 129 exit(1); 130 } 131 132 int 133 main(int argc, char **argv) 134 { 135 int ofd, ifd; 136 char *catfile = NULL; 137 int c; 138 139 #define DEPRECATEDMSG 1 140 141 #ifdef DEPRECATEDMSG 142 while ((c = getopt(argc, argv, "new")) != -1) { 143 #else 144 while ((c = getopt(argc, argv, "")) != -1) { 145 #endif 146 switch (c) { 147 #ifdef DEPRECATEDMSG 148 case 'n': 149 fprintf(stderr, "WARNING: Usage of \"-new\" argument is deprecated.\n"); 150 case 'e': 151 case 'w': 152 break; 153 #endif 154 case '?': 155 default: 156 usage(); 157 /* NOTREACHED */ 158 } 159 } 160 argc -= optind; 161 argv += optind; 162 163 if (argc < 2) { 164 usage(); 165 /* NOTREACHED */ 166 } 167 catfile = *argv++; 168 169 for (; *argv; argv++) { 170 if ((ifd = open(*argv, O_RDONLY)) < 0) 171 err(1, "Unable to read %s", *argv); 172 MCParse(ifd); 173 close(ifd); 174 } 175 176 if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0) 177 err(1, "Unable to create a new %s", catfile); 178 MCWriteCat(ofd); 179 exit(0); 180 } 181 182 static void 183 warning(const char *cptr, const char *msg) 184 { 185 fprintf(stderr, "%s: %s on line %ld\n", getprogname(), msg, lineno); 186 fprintf(stderr, "%s\n", curline); 187 if (cptr) { 188 char *tptr; 189 for (tptr = curline; tptr < cptr; ++tptr) 190 putc(' ', stderr); 191 fprintf(stderr, "^\n"); 192 } 193 } 194 195 #define CORRUPT() { error("corrupt message catalog"); } 196 #define NOMEM() { error("out of memory"); } 197 198 static void 199 error(const char *msg) 200 { 201 warning(NULL, msg); 202 exit(1); 203 } 204 205 static void * 206 xmalloc(size_t len) 207 { 208 void *p; 209 210 if ((p = malloc(len)) == NULL) 211 NOMEM(); 212 return (p); 213 } 214 215 static void * 216 xrealloc(void *ptr, size_t size) 217 { 218 if ((ptr = realloc(ptr, size)) == NULL) 219 NOMEM(); 220 return (ptr); 221 } 222 223 static char * 224 xstrdup(const char *str) 225 { 226 char *nstr; 227 228 if ((nstr = strdup(str)) == NULL) 229 NOMEM(); 230 return (nstr); 231 } 232 233 static char * 234 getline(int fd) 235 { 236 static long curlen = BUFSIZ; 237 static char buf[BUFSIZ], *bptr = buf, *bend = buf; 238 char *cptr, *cend; 239 long buflen; 240 241 if (!curline) { 242 curline = xmalloc(curlen); 243 } 244 ++lineno; 245 246 cptr = curline; 247 cend = curline + curlen; 248 for (;;) { 249 for (; bptr < bend && cptr < cend; ++cptr, ++bptr) { 250 if (*bptr == '\n') { 251 *cptr = '\0'; 252 ++bptr; 253 return (curline); 254 } else 255 *cptr = *bptr; 256 } 257 if (cptr == cend) { 258 cptr = curline = xrealloc(curline, curlen *= 2); 259 cend = curline + curlen; 260 } 261 if (bptr == bend) { 262 buflen = read(fd, buf, BUFSIZ); 263 if (buflen <= 0) { 264 if (cptr > curline) { 265 *cptr = '\0'; 266 return (curline); 267 } 268 return (NULL); 269 } 270 bend = buf + buflen; 271 bptr = buf; 272 } 273 } 274 } 275 276 static char * 277 wskip(char *cptr) 278 { 279 if (!*cptr || !isspace((unsigned char) *cptr)) { 280 warning(cptr, "expected a space"); 281 return (cptr); 282 } 283 while (*cptr && isspace((unsigned char) *cptr)) 284 ++cptr; 285 return (cptr); 286 } 287 288 static char * 289 cskip(char *cptr) 290 { 291 if (!*cptr || isspace((unsigned char) *cptr)) { 292 warning(cptr, "wasn't expecting a space"); 293 return (cptr); 294 } 295 while (*cptr && !isspace((unsigned char) *cptr)) 296 ++cptr; 297 return (cptr); 298 } 299 300 static char * 301 getmsg(int fd, char *cptr, char quote) 302 { 303 static char *msg = NULL; 304 static long msglen = 0; 305 long clen, i; 306 char *tptr; 307 308 if (quote && *cptr == quote) { 309 ++cptr; 310 } 311 312 clen = strlen(cptr) + 1; 313 if (clen > msglen) { 314 if (msglen) 315 msg = xrealloc(msg, clen); 316 else 317 msg = xmalloc(clen); 318 msglen = clen; 319 } 320 tptr = msg; 321 322 while (*cptr) { 323 if (quote && *cptr == quote) { 324 char *tmp; 325 tmp = cptr + 1; 326 if (*tmp && (!isspace((unsigned char) *tmp) || *wskip(tmp))) { 327 warning(cptr, "unexpected quote character, ignoring"); 328 *tptr++ = *cptr++; 329 } else { 330 *cptr = '\0'; 331 } 332 } else 333 if (*cptr == '\\') { 334 ++cptr; 335 switch (*cptr) { 336 case '\0': 337 cptr = getline(fd); 338 if (!cptr) 339 error("premature end of file"); 340 msglen += strlen(cptr); 341 i = tptr - msg; 342 msg = xrealloc(msg, msglen); 343 tptr = msg + i; 344 break; 345 346 #define CASEOF(CS, CH) \ 347 case CS: \ 348 *tptr++ = CH; \ 349 ++cptr; \ 350 break; \ 351 352 CASEOF('n', '\n'); 353 CASEOF('t', '\t'); 354 CASEOF('v', '\v'); 355 CASEOF('b', '\b'); 356 CASEOF('r', '\r'); 357 CASEOF('f', '\f'); 358 CASEOF('"', '"'); 359 CASEOF('\\', '\\'); 360 361 default: 362 if (quote && *cptr == quote) { 363 *tptr++ = *cptr++; 364 } else if (isdigit((unsigned char) *cptr)) { 365 *tptr = 0; 366 for (i = 0; i < 3; ++i) { 367 if (!isdigit((unsigned char) *cptr)) 368 break; 369 if (*cptr > '7') 370 warning(cptr, "octal number greater than 7?!"); 371 *tptr *= 8; 372 *tptr += (*cptr - '0'); 373 ++cptr; 374 } 375 } else { 376 warning(cptr, "unrecognized escape sequence"); 377 } 378 break; 379 } 380 } else { 381 *tptr++ = *cptr++; 382 } 383 } 384 *tptr = '\0'; 385 return (msg); 386 } 387 388 void 389 MCParse(int fd) 390 { 391 char *cptr, *str; 392 int setid, msgid = 0; 393 char quote = 0; 394 395 /* XXX: init sethead? */ 396 397 while ((cptr = getline(fd))) { 398 if (*cptr == '$') { 399 ++cptr; 400 if (strncmp(cptr, "set", 3) == 0) { 401 cptr += 3; 402 cptr = wskip(cptr); 403 setid = atoi(cptr); 404 MCAddSet(setid); 405 msgid = 0; 406 } else if (strncmp(cptr, "delset", 6) == 0) { 407 cptr += 6; 408 cptr = wskip(cptr); 409 setid = atoi(cptr); 410 MCDelSet(setid); 411 } else if (strncmp(cptr, "quote", 5) == 0) { 412 cptr += 5; 413 if (!*cptr) 414 quote = 0; 415 else { 416 cptr = wskip(cptr); 417 if (!*cptr) 418 quote = 0; 419 else 420 quote = *cptr; 421 } 422 } else if (isspace((unsigned char) *cptr)) { 423 ; 424 } else { 425 if (*cptr) { 426 cptr = wskip(cptr); 427 if (*cptr) 428 warning(cptr, "unrecognized line"); 429 } 430 } 431 } else { 432 /* 433 * First check for (and eat) empty lines.... 434 */ 435 if (!*cptr) 436 continue; 437 /* 438 * We have a digit? Start of a message. Else, 439 * syntax error. 440 */ 441 if (isdigit((unsigned char) *cptr)) { 442 msgid = atoi(cptr); 443 cptr = cskip(cptr); 444 cptr = wskip(cptr); 445 /* if (*cptr) ++cptr; */ 446 } else { 447 warning(cptr, "neither blank line nor start of a message id"); 448 continue; 449 } 450 /* 451 * If we have a message ID, but no message, 452 * then this means "delete this message id 453 * from the catalog". 454 */ 455 if (!*cptr) { 456 MCDelMsg(msgid); 457 } else { 458 str = getmsg(fd, cptr, quote); 459 MCAddMsg(msgid, str); 460 } 461 } 462 } 463 } 464 465 void 466 MCReadCat(int fd) 467 { 468 fd = 0; 469 #if 0 470 MCHeaderT mcHead; 471 MCMsgT mcMsg; 472 MCSetT mcSet; 473 msgT *msg; 474 setT *set; 475 int i; 476 char *data; 477 478 /* XXX init sethead? */ 479 480 if (read(fd, &mcHead, sizeof(mcHead)) != sizeof(mcHead)) 481 CORRUPT(); 482 if (strncmp(mcHead.magic, MCMagic, MCMagicLen) != 0) 483 CORRUPT(); 484 if (mcHead.majorVer != MCMajorVer) 485 error("unrecognized catalog version"); 486 if ((mcHead.flags & MCGetByteOrder()) == 0) 487 error("wrong byte order"); 488 489 if (lseek(fd, mcHead.firstSet, SEEK_SET) == -1) 490 CORRUPT(); 491 492 for (;;) { 493 if (read(fd, &mcSet, sizeof(mcSet)) != sizeof(mcSet)) 494 CORRUPT(); 495 if (mcSet.invalid) 496 continue; 497 498 set = xmalloc(sizeof(setT)); 499 memset(set, '\0', sizeof(*set)); 500 if (cat->first) { 501 cat->last->next = set; 502 set->prev = cat->last; 503 cat->last = set; 504 } else 505 cat->first = cat->last = set; 506 507 set->setId = mcSet.setId; 508 509 /* Get the data */ 510 if (mcSet.dataLen) { 511 data = xmalloc(mcSet.dataLen); 512 if (lseek(fd, mcSet.data.off, SEEK_SET) == -1) 513 CORRUPT(); 514 if (read(fd, data, mcSet.dataLen) != mcSet.dataLen) 515 CORRUPT(); 516 if (lseek(fd, mcSet.u.firstMsg, SEEK_SET) == -1) 517 CORRUPT(); 518 519 for (i = 0; i < mcSet.numMsgs; ++i) { 520 if (read(fd, &mcMsg, sizeof(mcMsg)) != sizeof(mcMsg)) 521 CORRUPT(); 522 if (mcMsg.invalid) { 523 --i; 524 continue; 525 } 526 msg = xmalloc(sizeof(msgT)); 527 memset(msg, '\0', sizeof(*msg)); 528 if (set->first) { 529 set->last->next = msg; 530 msg->prev = set->last; 531 set->last = msg; 532 } else 533 set->first = set->last = msg; 534 535 msg->msgId = mcMsg.msgId; 536 msg->str = xstrdup((char *) (data + mcMsg.msg.off)); 537 } 538 free(data); 539 } 540 if (!mcSet.nextSet) 541 break; 542 if (lseek(fd, mcSet.nextSet, SEEK_SET) == -1) 543 CORRUPT(); 544 } 545 #endif 546 } 547 548 /* 549 * Write message catalog. 550 * 551 * The message catalog is first converted from its internal to its 552 * external representation in a chunk of memory allocated for this 553 * purpose. Then the completed catalog is written. This approach 554 * avoids additional housekeeping variables and/or a lot of seeks 555 * that would otherwise be required. 556 */ 557 void 558 MCWriteCat(int fd) 559 { 560 int nsets; /* number of sets */ 561 int nmsgs; /* number of msgs */ 562 int string_size; /* total size of string pool */ 563 int msgcat_size; /* total size of message catalog */ 564 void *msgcat; /* message catalog data */ 565 struct _nls_cat_hdr *cat_hdr; 566 struct _nls_set_hdr *set_hdr; 567 struct _nls_msg_hdr *msg_hdr; 568 char *strings; 569 struct _setT *set; 570 struct _msgT *msg; 571 int msg_index; 572 int msg_offset; 573 574 /* determine number of sets, number of messages, and size of the 575 * string pool */ 576 nsets = 0; 577 nmsgs = 0; 578 string_size = 0; 579 580 for (set = sethead.lh_first; set != NULL; 581 set = set->entries.le_next) { 582 nsets++; 583 584 for (msg = set->msghead.lh_first; msg != NULL; 585 msg = msg->entries.le_next) { 586 nmsgs++; 587 string_size += strlen(msg->str) + 1; 588 } 589 } 590 591 #ifdef DEBUG 592 printf("number of sets: %d\n", nsets); 593 printf("number of msgs: %d\n", nmsgs); 594 printf("string pool size: %d\n", string_size); 595 #endif 596 597 /* determine size and then allocate buffer for constructing external 598 * message catalog representation */ 599 msgcat_size = sizeof(struct _nls_cat_hdr) 600 + (nsets * sizeof(struct _nls_set_hdr)) 601 + (nmsgs * sizeof(struct _nls_msg_hdr)) 602 + string_size; 603 604 msgcat = xmalloc(msgcat_size); 605 memset(msgcat, '\0', msgcat_size); 606 607 /* fill in msg catalog header */ 608 cat_hdr = (struct _nls_cat_hdr *) msgcat; 609 cat_hdr->__magic = htonl(_NLS_MAGIC); 610 cat_hdr->__nsets = htonl(nsets); 611 cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr)); 612 cat_hdr->__msg_hdr_offset = 613 htonl(nsets * sizeof(struct _nls_set_hdr)); 614 cat_hdr->__msg_txt_offset = 615 htonl(nsets * sizeof(struct _nls_set_hdr) + 616 nmsgs * sizeof(struct _nls_msg_hdr)); 617 618 /* compute offsets for set & msg header tables and string pool */ 619 set_hdr = (struct _nls_set_hdr *)(void *)((char *)msgcat + 620 sizeof(struct _nls_cat_hdr)); 621 msg_hdr = (struct _nls_msg_hdr *)(void *)((char *)msgcat + 622 sizeof(struct _nls_cat_hdr) + 623 nsets * sizeof(struct _nls_set_hdr)); 624 strings = (char *) msgcat + 625 sizeof(struct _nls_cat_hdr) + 626 nsets * sizeof(struct _nls_set_hdr) + 627 nmsgs * sizeof(struct _nls_msg_hdr); 628 629 msg_index = 0; 630 msg_offset = 0; 631 for (set = sethead.lh_first; set != NULL; 632 set = set->entries.le_next) { 633 634 nmsgs = 0; 635 for (msg = set->msghead.lh_first; msg != NULL; 636 msg = msg->entries.le_next) { 637 int msg_len = strlen(msg->str) + 1; 638 639 msg_hdr->__msgno = htonl(msg->msgId); 640 msg_hdr->__msglen = htonl(msg_len); 641 msg_hdr->__offset = htonl(msg_offset); 642 643 memcpy(strings, msg->str, msg_len); 644 strings += msg_len; 645 msg_offset += msg_len; 646 647 nmsgs++; 648 msg_hdr++; 649 } 650 651 set_hdr->__setno = htonl(set->setId); 652 set_hdr->__nmsgs = htonl(nmsgs); 653 set_hdr->__index = htonl(msg_index); 654 msg_index += nmsgs; 655 set_hdr++; 656 } 657 658 /* write out catalog. XXX: should this be done in small chunks? */ 659 write(fd, msgcat, msgcat_size); 660 } 661 662 void 663 MCAddSet(int setId) 664 { 665 struct _setT *p, *q; 666 667 if (setId <= 0) { 668 error("setId's must be greater than zero"); 669 /* NOTREACHED */ 670 } 671 if (setId > NL_SETMAX) { 672 error("setId exceeds limit"); 673 /* NOTREACHED */ 674 } 675 676 p = sethead.lh_first; 677 q = NULL; 678 for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next); 679 680 if (p && p->setId == setId) { 681 ; 682 } else { 683 p = xmalloc(sizeof(struct _setT)); 684 memset(p, '\0', sizeof(struct _setT)); 685 LIST_INIT(&p->msghead); 686 687 p->setId = setId; 688 689 if (q == NULL) { 690 LIST_INSERT_HEAD(&sethead, p, entries); 691 } else { 692 LIST_INSERT_AFTER(q, p, entries); 693 } 694 } 695 696 curSet = p; 697 } 698 699 void 700 MCAddMsg(int msgId, const char *str) 701 { 702 struct _msgT *p, *q; 703 704 if (!curSet) 705 error("can't specify a message when no set exists"); 706 707 if (msgId <= 0) { 708 error("msgId's must be greater than zero"); 709 /* NOTREACHED */ 710 } 711 if (msgId > NL_MSGMAX) { 712 error("msgID exceeds limit"); 713 /* NOTREACHED */ 714 } 715 716 p = curSet->msghead.lh_first; 717 q = NULL; 718 for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next); 719 720 if (p && p->msgId == msgId) { 721 free(p->str); 722 } else { 723 p = xmalloc(sizeof(struct _msgT)); 724 memset(p, '\0', sizeof(struct _msgT)); 725 726 if (q == NULL) { 727 LIST_INSERT_HEAD(&curSet->msghead, p, entries); 728 } else { 729 LIST_INSERT_AFTER(q, p, entries); 730 } 731 } 732 733 p->msgId = msgId; 734 p->str = xstrdup(str); 735 } 736 737 void 738 MCDelSet(int setId) 739 { 740 struct _setT *set; 741 struct _msgT *msg; 742 743 set = sethead.lh_first; 744 for (; set != NULL && set->setId < setId; set = set->entries.le_next); 745 746 if (set && set->setId == setId) { 747 748 msg = set->msghead.lh_first; 749 while (msg) { 750 free(msg->str); 751 LIST_REMOVE(msg, entries); 752 } 753 754 LIST_REMOVE(set, entries); 755 return; 756 } 757 warning(NULL, "specified set doesn't exist"); 758 } 759 760 void 761 MCDelMsg(int msgId) 762 { 763 struct _msgT *msg; 764 765 if (!curSet) 766 error("you can't delete a message before defining the set"); 767 768 msg = curSet->msghead.lh_first; 769 for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next); 770 771 if (msg && msg->msgId == msgId) { 772 free(msg->str); 773 LIST_REMOVE(msg, entries); 774 return; 775 } 776 warning(NULL, "specified msg doesn't exist"); 777 } 778