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