xref: /dragonfly/usr.bin/gencat/gencat.c (revision a563ca70)
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