xref: /freebsd/usr.bin/gencat/gencat.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
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