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