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