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