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