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