gencat.c revision 1.11 1 /* $NetBSD: gencat.c,v 1.11 2001/09/20 15:56:06 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.11 2001/09/20 15:56:06 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 <nl_types.h>
83 #include <stdio.h>
84 #include <stdlib.h>
85 #include <string.h>
86 #include <unistd.h>
87
88 struct _msgT {
89 long msgId;
90 char *str;
91 LIST_ENTRY(_msgT) entries;
92 };
93
94 struct _setT {
95 long setId;
96 LIST_HEAD(msghead, _msgT) msghead;
97 LIST_ENTRY(_setT) entries;
98 };
99
100 LIST_HEAD(sethead, _setT) sethead;
101 static struct _setT *curSet;
102
103 static char *curline = NULL;
104 static long lineno = 0;
105
106 #if 0 /* XXX unused */
107 static void corrupt __P((void));
108 #endif
109 static char *cskip __P((char *));
110 static void error __P((char *, char *));
111 static void nomem __P((void));
112 static char *getline __P((int));
113 static char *getmsg __P((int, char *, char));
114 static void warning __P((char *, char *));
115 static char *wskip __P((char *));
116 static char *xstrdup __P((const char *));
117 static void *xmalloc __P((size_t));
118 static void *xrealloc __P((void *, size_t));
119
120 void MCParse __P((int fd));
121 void MCReadCat __P((int fd));
122 void MCWriteCat __P((int fd));
123 void MCDelMsg __P((int msgId));
124 void MCAddMsg __P((int msgId, const char *msg));
125 void MCAddSet __P((int setId));
126 void MCDelSet __P((int setId));
127 int main __P((int, char **));
128 void usage __P((void));
129
130
131 void
132 usage()
133 {
134 fprintf(stderr, "Usage: %s catfile msgfile ...\n", getprogname());
135 exit(1);
136 }
137
138 int
139 main(argc, argv)
140 int argc;
141 char *argv[];
142 {
143 int ofd, ifd;
144 char *catfile = NULL;
145 int c;
146
147 while ((c = getopt(argc, argv, "")) != -1) {
148 switch (c) {
149 case '?':
150 default:
151 usage();
152 /* NOTREACHED */
153 }
154 }
155 argc -= optind;
156 argv += optind;
157
158 if (argc < 2) {
159 usage();
160 /* NOTREACHED */
161 }
162 catfile = *argv++;
163
164 for (; *argv; argv++) {
165 if ((ifd = open(*argv, O_RDONLY)) < 0)
166 err(1, "Unable to read %s", *argv);
167 MCParse(ifd);
168 close(ifd);
169 }
170
171 if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
172 err(1, "Unable to create a new %s", catfile);
173 MCWriteCat(ofd);
174 exit(0);
175 }
176
177 static void
178 warning(cptr, msg)
179 char *cptr;
180 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(cptr, msg)
194 char *cptr;
195 char *msg;
196 {
197 warning(cptr, msg);
198 exit(1);
199 }
200
201 #if 0 /* XXX unused */
202 static void
203 corrupt()
204 {
205 error(NULL, "corrupt message catalog");
206 }
207 #endif
208
209 static void
210 nomem()
211 {
212 error(NULL, "out of memory");
213 }
214
215 static void *
216 xmalloc(len)
217 size_t len;
218 {
219 void *p;
220
221 if ((p = malloc(len)) == NULL)
222 nomem();
223 return (p);
224 }
225
226 static void *
227 xrealloc(ptr, size)
228 void *ptr;
229 size_t size;
230 {
231 if ((ptr = realloc(ptr, size)) == NULL)
232 nomem();
233 return (ptr);
234 }
235
236 static char *
237 xstrdup(str)
238 const char *str;
239 {
240 char *nstr;
241
242 if ((nstr = strdup(str)) == NULL)
243 nomem();
244 return (nstr);
245 }
246
247 static char *
248 getline(fd)
249 int fd;
250 {
251 static long curlen = BUFSIZ;
252 static char buf[BUFSIZ], *bptr = buf, *bend = buf;
253 char *cptr, *cend;
254 long buflen;
255
256 if (!curline) {
257 curline = xmalloc(curlen);
258 }
259 ++lineno;
260
261 cptr = curline;
262 cend = curline + curlen;
263 for (;;) {
264 for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
265 if (*bptr == '\n') {
266 *cptr = '\0';
267 ++bptr;
268 return (curline);
269 } else
270 *cptr = *bptr;
271 }
272 if (cptr == cend) {
273 cptr = curline = xrealloc(curline, curlen *= 2);
274 cend = curline + curlen;
275 }
276 if (bptr == bend) {
277 buflen = read(fd, buf, BUFSIZ);
278 if (buflen <= 0) {
279 if (cptr > curline) {
280 *cptr = '\0';
281 return (curline);
282 }
283 return (NULL);
284 }
285 bend = buf + buflen;
286 bptr = buf;
287 }
288 }
289 }
290
291 static char *
292 wskip(cptr)
293 char *cptr;
294 {
295 if (!*cptr || !isspace((unsigned char) *cptr)) {
296 warning(cptr, "expected a space");
297 return (cptr);
298 }
299 while (*cptr && isspace((unsigned char) *cptr))
300 ++cptr;
301 return (cptr);
302 }
303
304 static char *
305 cskip(cptr)
306 char *cptr;
307 {
308 if (!*cptr || isspace((unsigned char) *cptr)) {
309 warning(cptr, "wasn't expecting a space");
310 return (cptr);
311 }
312 while (*cptr && !isspace((unsigned char) *cptr))
313 ++cptr;
314 return (cptr);
315 }
316
317 static char *
318 getmsg(fd, cptr, quote)
319 int fd;
320 char *cptr;
321 char quote;
322 {
323 static char *msg = NULL;
324 static long msglen = 0;
325 long clen, i;
326 char *tptr;
327
328 if (quote && *cptr == quote) {
329 ++cptr;
330 }
331
332 clen = strlen(cptr) + 1;
333 if (clen > msglen) {
334 if (msglen)
335 msg = xrealloc(msg, clen);
336 else
337 msg = xmalloc(clen);
338 msglen = clen;
339 }
340 tptr = msg;
341
342 while (*cptr) {
343 if (quote && *cptr == quote) {
344 char *tmp;
345 tmp = cptr + 1;
346 if (*tmp && (!isspace((unsigned char) *tmp) || *wskip(tmp))) {
347 warning(cptr, "unexpected quote character, ignoring");
348 *tptr++ = *cptr++;
349 } else {
350 *cptr = '\0';
351 }
352 } else
353 if (*cptr == '\\') {
354 ++cptr;
355 switch (*cptr) {
356 case '\0':
357 cptr = getline(fd);
358 if (!cptr)
359 error(NULL, "premature end of file");
360 msglen += strlen(cptr);
361 i = tptr - msg;
362 msg = xrealloc(msg, msglen);
363 tptr = msg + i;
364 break;
365 case 'n':
366 *tptr++ = '\n';
367 ++cptr;
368 break;
369 case 't':
370 *tptr++ = '\t';
371 ++cptr;
372 break;
373 case 'v':
374 *tptr++ = '\v';
375 ++cptr;
376 break;
377 case 'b':
378 *tptr++ = '\b';
379 ++cptr;
380 break;
381 case 'r':
382 *tptr++ = '\r';
383 ++cptr;
384 break;
385 case 'f':
386 *tptr++ = '\f';
387 ++cptr;
388 break;
389 case '\\':
390 *tptr++ = '\\';
391 ++cptr;
392 break;
393 default:
394 if (quote && *cptr == quote) {
395 *tptr++ = *cptr++;
396 } else if (isdigit((unsigned char) *cptr)) {
397 *tptr = 0;
398 for (i = 0; i < 3; ++i) {
399 if (!isdigit((unsigned char) *cptr))
400 break;
401 if (*cptr > '7')
402 warning(cptr, "octal number greater than 7?!");
403 *tptr *= 8;
404 *tptr += (*cptr - '0');
405 ++cptr;
406 }
407 } else {
408 warning(cptr, "unrecognized escape sequence");
409 }
410 break;
411 }
412 } else {
413 *tptr++ = *cptr++;
414 }
415 }
416 *tptr = '\0';
417 return (msg);
418 }
419
420 void
421 MCParse(fd)
422 int fd;
423 {
424 char *cptr, *str;
425 int setid, msgid = 0;
426 char quote = 0;
427
428 /* XXX: init sethead? */
429
430 while ((cptr = getline(fd))) {
431 if (*cptr == '$') {
432 ++cptr;
433 if (strncmp(cptr, "set", 3) == 0) {
434 cptr += 3;
435 cptr = wskip(cptr);
436 setid = atoi(cptr);
437 MCAddSet(setid);
438 msgid = 0;
439 } else if (strncmp(cptr, "delset", 6) == 0) {
440 cptr += 6;
441 cptr = wskip(cptr);
442 setid = atoi(cptr);
443 MCDelSet(setid);
444 } else if (strncmp(cptr, "quote", 5) == 0) {
445 cptr += 5;
446 if (!*cptr)
447 quote = 0;
448 else {
449 cptr = wskip(cptr);
450 if (!*cptr)
451 quote = 0;
452 else
453 quote = *cptr;
454 }
455 } else if (isspace((unsigned char) *cptr)) {
456 ;
457 } else {
458 if (*cptr) {
459 cptr = wskip(cptr);
460 if (*cptr)
461 warning(cptr, "unrecognized line");
462 }
463 }
464 } else {
465 /*
466 * First check for (and eat) empty lines....
467 */
468 if (!*cptr)
469 continue;
470 /*
471 * We have a digit? Start of a message. Else,
472 * syntax error.
473 */
474 if (isdigit((unsigned char) *cptr)) {
475 msgid = atoi(cptr);
476 cptr = cskip(cptr);
477 cptr = wskip(cptr);
478 /* if (*cptr) ++cptr; */
479 } else {
480 warning(cptr, "neither blank line nor start of a message id");
481 continue;
482 }
483 /*
484 * If we have a message ID, but no message,
485 * then this means "delete this message id
486 * from the catalog".
487 */
488 if (!*cptr) {
489 MCDelMsg(msgid);
490 } else {
491 str = getmsg(fd, cptr, quote);
492 MCAddMsg(msgid, str);
493 }
494 }
495 }
496 }
497
498 void
499 MCReadCat(fd)
500 int fd;
501 {
502 #if 0
503 MCHeaderT mcHead;
504 MCMsgT mcMsg;
505 MCSetT mcSet;
506 msgT *msg;
507 setT *set;
508 int i;
509 char *data;
510
511 /* XXX init sethead? */
512
513 if (read(fd, &mcHead, sizeof(mcHead)) != sizeof(mcHead))
514 corrupt();
515 if (strncmp(mcHead.magic, MCMagic, MCMagicLen) != 0)
516 corrupt();
517 if (mcHead.majorVer != MCMajorVer)
518 error(NULL, "unrecognized catalog version");
519 if ((mcHead.flags & MCGetByteOrder()) == 0)
520 error(NULL, "wrong byte order");
521
522 if (lseek(fd, mcHead.firstSet, SEEK_SET) == -1)
523 corrupt();
524
525 for (;;) {
526 if (read(fd, &mcSet, sizeof(mcSet)) != sizeof(mcSet))
527 corrupt();
528 if (mcSet.invalid)
529 continue;
530
531 set = xmalloc(sizeof(setT));
532 memset(set, '\0', sizeof(*set));
533 if (cat->first) {
534 cat->last->next = set;
535 set->prev = cat->last;
536 cat->last = set;
537 } else
538 cat->first = cat->last = set;
539
540 set->setId = mcSet.setId;
541
542 /* Get the data */
543 if (mcSet.dataLen) {
544 data = xmalloc(mcSet.dataLen);
545 if (lseek(fd, mcSet.data.off, SEEK_SET) == -1)
546 corrupt();
547 if (read(fd, data, mcSet.dataLen) != mcSet.dataLen)
548 corrupt();
549 if (lseek(fd, mcSet.u.firstMsg, SEEK_SET) == -1)
550 corrupt();
551
552 for (i = 0; i < mcSet.numMsgs; ++i) {
553 if (read(fd, &mcMsg, sizeof(mcMsg)) != sizeof(mcMsg))
554 corrupt();
555 if (mcMsg.invalid) {
556 --i;
557 continue;
558 }
559 msg = xmalloc(sizeof(msgT));
560 memset(msg, '\0', sizeof(*msg));
561 if (set->first) {
562 set->last->next = msg;
563 msg->prev = set->last;
564 set->last = msg;
565 } else
566 set->first = set->last = msg;
567
568 msg->msgId = mcMsg.msgId;
569 msg->str = xstrdup((char *) (data + mcMsg.msg.off));
570 }
571 free(data);
572 }
573 if (!mcSet.nextSet)
574 break;
575 if (lseek(fd, mcSet.nextSet, SEEK_SET) == -1)
576 corrupt();
577 }
578 #endif
579 }
580
581 /*
582 * Write message catalog.
583 *
584 * The message catalog is first converted from its internal to its
585 * external representation in a chunk of memory allocated for this
586 * purpose. Then the completed catalog is written. This approach
587 * avoids additional housekeeping variables and/or a lot of seeks
588 * that would otherwise be required.
589 */
590 void
591 MCWriteCat(fd)
592 int fd;
593 {
594 int nsets; /* number of sets */
595 int nmsgs; /* number of msgs */
596 int string_size; /* total size of string pool */
597 int msgcat_size; /* total size of message catalog */
598 void *msgcat; /* message catalog data */
599 struct _nls_cat_hdr *cat_hdr;
600 struct _nls_set_hdr *set_hdr;
601 struct _nls_msg_hdr *msg_hdr;
602 char *strings;
603 struct _setT *set;
604 struct _msgT *msg;
605 int msg_index;
606 int msg_offset;
607
608 /* determine number of sets, number of messages, and size of the
609 * string pool */
610 nsets = 0;
611 nmsgs = 0;
612 string_size = 0;
613
614 for (set = sethead.lh_first; set != NULL;
615 set = set->entries.le_next) {
616 nsets++;
617
618 for (msg = set->msghead.lh_first; msg != NULL;
619 msg = msg->entries.le_next) {
620 nmsgs++;
621 string_size += strlen(msg->str) + 1;
622 }
623 }
624
625 #ifdef DEBUG
626 printf("number of sets: %d\n", nsets);
627 printf("number of msgs: %d\n", nmsgs);
628 printf("string pool size: %d\n", string_size);
629 #endif
630
631 /* determine size and then allocate buffer for constructing external
632 * message catalog representation */
633 msgcat_size = sizeof(struct _nls_cat_hdr)
634 + (nsets * sizeof(struct _nls_set_hdr))
635 + (nmsgs * sizeof(struct _nls_msg_hdr))
636 + string_size;
637
638 msgcat = xmalloc(msgcat_size);
639 memset(msgcat, '\0', msgcat_size);
640
641 /* fill in msg catalog header */
642 cat_hdr = (struct _nls_cat_hdr *) msgcat;
643 cat_hdr->__magic = htonl(_NLS_MAGIC);
644 cat_hdr->__nsets = htonl(nsets);
645 cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
646 cat_hdr->__msg_hdr_offset =
647 htonl(nsets * sizeof(struct _nls_set_hdr));
648 cat_hdr->__msg_txt_offset =
649 htonl(nsets * sizeof(struct _nls_set_hdr) +
650 nmsgs * sizeof(struct _nls_msg_hdr));
651
652 /* compute offsets for set & msg header tables and string pool */
653 set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
654 sizeof(struct _nls_cat_hdr));
655 msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
656 sizeof(struct _nls_cat_hdr) +
657 nsets * sizeof(struct _nls_set_hdr));
658 strings = (char *) msgcat +
659 sizeof(struct _nls_cat_hdr) +
660 nsets * sizeof(struct _nls_set_hdr) +
661 nmsgs * sizeof(struct _nls_msg_hdr);
662
663 msg_index = 0;
664 msg_offset = 0;
665 for (set = sethead.lh_first; set != NULL;
666 set = set->entries.le_next) {
667
668 nmsgs = 0;
669 for (msg = set->msghead.lh_first; msg != NULL;
670 msg = msg->entries.le_next) {
671 int msg_len = strlen(msg->str) + 1;
672
673 msg_hdr->__msgno = htonl(msg->msgId);
674 msg_hdr->__msglen = htonl(msg_len);
675 msg_hdr->__offset = htonl(msg_offset);
676
677 memcpy(strings, msg->str, msg_len);
678 strings += msg_len;
679 msg_offset += msg_len;
680
681 nmsgs++;
682 msg_hdr++;
683 }
684
685 set_hdr->__setno = htonl(set->setId);
686 set_hdr->__nmsgs = htonl(nmsgs);
687 set_hdr->__index = htonl(msg_index);
688 msg_index += nmsgs;
689 set_hdr++;
690 }
691
692 /* write out catalog. XXX: should this be done in small chunks? */
693 write(fd, msgcat, msgcat_size);
694 }
695
696 void
697 MCAddSet(setId)
698 int setId;
699 {
700 struct _setT *p, *q;
701
702 if (setId <= 0) {
703 error(NULL, "setId's must be greater than zero");
704 /* NOTREACHED */
705 }
706 #if 0
707 /* XXX */
708 if (setId > NL_SETMAX) {
709 error(NULL, "setId %d exceeds limit (%d)");
710 /* NOTREACHED */
711 }
712 #endif
713
714 p = sethead.lh_first;
715 q = NULL;
716 for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next);
717
718 if (p && p->setId == setId) {
719 ;
720 } else {
721 p = xmalloc(sizeof(struct _setT));
722 memset(p, '\0', sizeof(struct _setT));
723 LIST_INIT(&p->msghead);
724
725 p->setId = setId;
726
727 if (q == NULL) {
728 LIST_INSERT_HEAD(&sethead, p, entries);
729 } else {
730 LIST_INSERT_AFTER(q, p, entries);
731 }
732 }
733
734 curSet = p;
735 }
736
737 void
738 MCAddMsg(msgId, str)
739 int msgId;
740 const char *str;
741 {
742 struct _msgT *p, *q;
743
744 if (!curSet)
745 error(NULL, "can't specify a message when no set exists");
746
747 if (msgId <= 0) {
748 error(NULL, "msgId's must be greater than zero");
749 /* NOTREACHED */
750 }
751 #if 0
752 /* XXX */
753 if (msgId > NL_SETMAX) {
754 error(NULL, "msgID %d exceeds limit (%d)");
755 /* NOTREACHED */
756 }
757 #endif
758
759 p = curSet->msghead.lh_first;
760 q = NULL;
761 for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next);
762
763 if (p && p->msgId == msgId) {
764 free(p->str);
765 } else {
766 p = xmalloc(sizeof(struct _msgT));
767 memset(p, '\0', sizeof(struct _msgT));
768
769 if (q == NULL) {
770 LIST_INSERT_HEAD(&curSet->msghead, p, entries);
771 } else {
772 LIST_INSERT_AFTER(q, p, entries);
773 }
774 }
775
776 p->msgId = msgId;
777 p->str = xstrdup(str);
778 }
779
780 void
781 MCDelSet(setId)
782 int setId;
783 {
784 struct _setT *set;
785 struct _msgT *msg;
786
787 set = sethead.lh_first;
788 for (; set != NULL && set->setId < setId; set = set->entries.le_next);
789
790 if (set && set->setId == setId) {
791
792 msg = set->msghead.lh_first;
793 while (msg) {
794 free(msg->str);
795 LIST_REMOVE(msg, entries);
796 }
797
798 LIST_REMOVE(set, entries);
799 return;
800 }
801 warning(NULL, "specified set doesn't exist");
802 }
803
804 void
805 MCDelMsg(msgId)
806 int msgId;
807 {
808 struct _msgT *msg;
809
810 if (!curSet)
811 error(NULL, "you can't delete a message before defining the set");
812
813 msg = curSet->msghead.lh_first;
814 for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next);
815
816 if (msg && msg->msgId == msgId) {
817 free(msg->str);
818 LIST_REMOVE(msg, entries);
819 return;
820 }
821 warning(NULL, "specified msg doesn't exist");
822 }
823