xref: /netbsd-src/usr.bin/gencat/gencat.c (revision 3b01aba77a7a698587faaae455bbfe740923c1f5)
1 /*	$NetBSD: gencat.c,v 1.10 2001/02/19 23:03:47 cgd 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.10 2001/02/19 23:03:47 cgd 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 #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 (bptr == bend) {
273 			buflen = read(fd, buf, BUFSIZ);
274 			if (buflen <= 0) {
275 				if (cptr > curline) {
276 					*cptr = '\0';
277 					return (curline);
278 				}
279 				return (NULL);
280 			}
281 			bend = buf + buflen;
282 			bptr = buf;
283 		}
284 		if (cptr == cend) {
285 			cptr = curline = xrealloc(curline, curlen *= 2);
286 			cend = curline + curlen;
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