Home | History | Annotate | Line # | Download | only in gencat
gencat.c revision 1.3
      1 /*-
      2  * Copyright (c) 1996 The NetBSD Foundation, Inc.
      3  * All rights reserved.
      4  *
      5  * This code is derived from software contributed to The NetBSD Foundation
      6  * by J.T. Conklin.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in the
     15  *    documentation and/or other materials provided with the distribution.
     16  * 3. All advertising materials mentioning features or use of this software
     17  *    must display the following acknowledgement:
     18  *        This product includes software developed by the NetBSD
     19  *	  Foundation, Inc. and its contributors.
     20  * 4. Neither the name of The NetBSD Foundation nor the names of its
     21  *    contributors may be used to endorse or promote products derived
     22  *    from this software without specific prior written permission.
     23  *
     24  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     25  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     26  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     27  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
     28  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     34  * POSSIBILITY OF SUCH DAMAGE.
     35  */
     36 
     37 /***********************************************************
     38 Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
     39 
     40                         All Rights Reserved
     41 
     42 Permission to use, copy, modify, and distribute this software and its
     43 documentation for any purpose and without fee is hereby granted,
     44 provided that the above copyright notice appear in all copies and that
     45 both that copyright notice and this permission notice appear in
     46 supporting documentation, and that Alfalfa's name not be used in
     47 advertising or publicity pertaining to distribution of the software
     48 without specific, written prior permission.
     49 
     50 ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
     51 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
     52 ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
     53 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
     54 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
     55 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
     56 SOFTWARE.
     57 
     58 If you make any modifications, bugfixes or other changes to this software
     59 we'd appreciate it if you could send a copy to us so we can keep things
     60 up-to-date.  Many thanks.
     61 				Kee Hinckley
     62 				Alfalfa Software, Inc.
     63 				267 Allston St., #3
     64 				Cambridge, MA 02139  USA
     65 				nazgul (at) alfalfa.com
     66 
     67 ******************************************************************/
     68 
     69 #define _NLS_PRIVATE
     70 
     71 #include <sys/queue.h>
     72 #include <ctype.h>
     73 #include <stdio.h>
     74 #include <stdlib.h>
     75 #include <string.h>
     76 #include <unistd.h>
     77 #include <fcntl.h>
     78 #include <nl_types.h>
     79 
     80 extern void MCAddSet __P((int setId));
     81 extern void MCDelSet __P((int setId));
     82 extern void MCAddMsg __P((int msgId, const char *msg));
     83 extern void MCDelMsg __P((int msgId));
     84 extern void MCParse __P((int fd));
     85 extern void MCReadCat __P((int fd));
     86 extern void MCWriteCat __P((int fd));
     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 void
    107 usage()
    108 {
    109 	fprintf(stderr, "Use: gencat catfile msgfile ...\n");
    110 	exit(1);
    111 }
    112 
    113 int
    114 main(argc, argv)
    115 	int     argc;
    116 	char   *argv[];
    117 {
    118 	int     ofd, ifd;
    119 	char   *catfile = NULL;
    120 	int     c;
    121 
    122 	while ((c = getopt(argc, argv, "")) != -1) {
    123 		switch (c) {
    124 		case '?':
    125 		default:
    126 			usage();
    127 			/* NOTREACHED */
    128 		}
    129 	}
    130 	argc -= optind;
    131 	argv += optind;
    132 
    133 	if (argc < 2) {
    134 		usage();
    135 		/* NOTREACHED */
    136 	}
    137 	catfile = *argv++;
    138 
    139 	for (; *argv; argv++) {
    140 		if ((ifd = open(*argv, O_RDONLY)) < 0) {
    141 			fprintf(stderr, "gencat: Unable to read %s\n", *argv);
    142 			exit(1);
    143 		}
    144 		MCParse(ifd);
    145 		close(ifd);
    146 	}
    147 
    148 	if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0) {
    149 		fprintf(stderr, "gencat: Unable to create a new %s.\n",
    150 		    catfile);
    151 		exit(1);
    152 	}
    153 	MCWriteCat(ofd);
    154 	exit(0);
    155 }
    156 
    157 static void
    158 warning(cptr, msg)
    159 	char   *cptr;
    160 	char   *msg;
    161 {
    162 	fprintf(stderr, "gencat: %s on line %ld\n", msg, lineno);
    163 	fprintf(stderr, "%s\n", curline);
    164 	if (cptr) {
    165 		char   *tptr;
    166 		for (tptr = curline; tptr < cptr; ++tptr)
    167 			putc(' ', stderr);
    168 		fprintf(stderr, "^\n");
    169 	}
    170 }
    171 
    172 static void
    173 error(cptr, msg)
    174 	char   *cptr;
    175 	char   *msg;
    176 {
    177 	warning(cptr, msg);
    178 	exit(1);
    179 }
    180 
    181 static void
    182 corrupt()
    183 {
    184 	error(NULL, "corrupt message catalog");
    185 }
    186 
    187 static void
    188 nomem()
    189 {
    190 	error(NULL, "out of memory");
    191 }
    192 
    193 static void *
    194 xmalloc(len)
    195 	size_t  len;
    196 {
    197 	void   *p;
    198 
    199 	if ((p = malloc(len)) == NULL)
    200 		nomem();
    201 	return (p);
    202 }
    203 
    204 static void *
    205 xrealloc(ptr, size)
    206 	void   *ptr;
    207 	size_t  size;
    208 {
    209 	if ((ptr = realloc(ptr, size)) == NULL)
    210 		nomem();
    211 	return (ptr);
    212 }
    213 
    214 static char *
    215 xstrdup(str)
    216 	char   *str;
    217 {
    218 	if ((str = strdup(str)) == NULL)
    219 		nomem();
    220 	return (str);
    221 }
    222 
    223 static char *
    224 getline(fd)
    225 	int     fd;
    226 {
    227 	static long curlen = BUFSIZ;
    228 	static char buf[BUFSIZ], *bptr = buf, *bend = buf;
    229 	char   *cptr, *cend;
    230 	long    buflen;
    231 
    232 	if (!curline) {
    233 		curline = xmalloc(curlen);
    234 	}
    235 	++lineno;
    236 
    237 	cptr = curline;
    238 	cend = curline + curlen;
    239 	for (;;) {
    240 		for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
    241 			if (*bptr == '\n') {
    242 				*cptr = '\0';
    243 				++bptr;
    244 				return (curline);
    245 			} else
    246 				*cptr = *bptr;
    247 		}
    248 		if (bptr == bend) {
    249 			buflen = read(fd, buf, BUFSIZ);
    250 			if (buflen <= 0) {
    251 				if (cptr > curline) {
    252 					*cptr = '\0';
    253 					return (curline);
    254 				}
    255 				return (NULL);
    256 			}
    257 			bend = buf + buflen;
    258 			bptr = buf;
    259 		}
    260 		if (cptr == cend) {
    261 			cptr = curline = xrealloc(curline, curlen *= 2);
    262 			cend = curline + curlen;
    263 		}
    264 	}
    265 }
    266 
    267 static char *
    268 wskip(cptr)
    269 	char   *cptr;
    270 {
    271 	if (!*cptr || !isspace(*cptr)) {
    272 		warning(cptr, "expected a space");
    273 		return (cptr);
    274 	}
    275 	while (*cptr && isspace(*cptr))
    276 		++cptr;
    277 	return (cptr);
    278 }
    279 
    280 static char *
    281 cskip(cptr)
    282 	char   *cptr;
    283 {
    284 	if (!*cptr || isspace(*cptr)) {
    285 		warning(cptr, "wasn't expecting a space");
    286 		return (cptr);
    287 	}
    288 	while (*cptr && !isspace(*cptr))
    289 		++cptr;
    290 	return (cptr);
    291 }
    292 
    293 static char *
    294 getmsg(fd, cptr, quote)
    295 	int     fd;
    296 	char   *cptr;
    297 	char    quote;
    298 {
    299 	static char *msg = NULL;
    300 	static long msglen = 0;
    301 	long    clen, i;
    302 	char   *tptr;
    303 
    304 	if (quote && *cptr == quote) {
    305 		++cptr;
    306 	}
    307 
    308 	clen = strlen(cptr) + 1;
    309 	if (clen > msglen) {
    310 		if (msglen)
    311 			msg = xrealloc(msg, clen);
    312 		else
    313 			msg = xmalloc(clen);
    314 		msglen = clen;
    315 	}
    316 	tptr = msg;
    317 
    318 	while (*cptr) {
    319 		if (quote && *cptr == quote) {
    320 			char   *tmp;
    321 			tmp = cptr + 1;
    322 			if (*tmp && (!isspace(*tmp) || *wskip(tmp))) {
    323 				warning(cptr, "unexpected quote character, ignoreing");
    324 				*tptr++ = *cptr++;
    325 			} else {
    326 				*cptr = '\0';
    327 			}
    328 		} else
    329 			if (*cptr == '\\') {
    330 				++cptr;
    331 				switch (*cptr) {
    332 				case '\0':
    333 					cptr = getline(fd);
    334 					if (!cptr)
    335 						error(NULL, "premature end of file");
    336 					msglen += strlen(cptr);
    337 					i = tptr - msg;
    338 					msg = xrealloc(msg, msglen);
    339 					tptr = msg + i;
    340 					break;
    341 				case 'n':
    342 					*tptr++ = '\n';
    343 					++cptr;
    344 					break;
    345 				case 't':
    346 					*tptr++ = '\t';
    347 					++cptr;
    348 					break;
    349 				case 'v':
    350 					*tptr++ = '\v';
    351 					++cptr;
    352 					break;
    353 				case 'b':
    354 					*tptr++ = '\b';
    355 					++cptr;
    356 					break;
    357 				case 'r':
    358 					*tptr++ = '\r';
    359 					++cptr;
    360 					break;
    361 				case 'f':
    362 					*tptr++ = '\f';
    363 					++cptr;
    364 					break;
    365 				case '\\':
    366 					*tptr++ = '\\';
    367 					++cptr;
    368 					break;
    369 				default:
    370 					if (isdigit(*cptr)) {
    371 						*tptr = 0;
    372 						for (i = 0; i < 3; ++i) {
    373 							if (!isdigit(*cptr))
    374 								break;
    375 							if (*cptr > '7')
    376 								warning(cptr, "octal number greater than 7?!");
    377 							*tptr *= 8;
    378 							*tptr += (*cptr - '0');
    379 							++cptr;
    380 						}
    381 					} else {
    382 						warning(cptr, "unrecognized escape sequence");
    383 					}
    384 				}
    385 			} else {
    386 				*tptr++ = *cptr++;
    387 			}
    388 	}
    389 	*tptr = '\0';
    390 	return (msg);
    391 }
    392 
    393 void
    394 MCParse(fd)
    395 	int     fd;
    396 {
    397 	char   *cptr, *str;
    398 	int     setid, msgid = 0;
    399 	char    quote = 0;
    400 
    401 	/* XXX: init sethead? */
    402 
    403 	while ((cptr = getline(fd))) {
    404 		if (*cptr == '$') {
    405 			++cptr;
    406 			if (strncmp(cptr, "set", 3) == 0) {
    407 				cptr += 3;
    408 				cptr = wskip(cptr);
    409 				setid = atoi(cptr);
    410 				MCAddSet(setid);
    411 				msgid = 0;
    412 			} else if (strncmp(cptr, "delset", 6) == 0) {
    413 				cptr += 6;
    414 				cptr = wskip(cptr);
    415 				setid = atoi(cptr);
    416 				MCDelSet(setid);
    417 			} else if (strncmp(cptr, "quote", 5) == 0) {
    418 				cptr += 5;
    419 				if (!*cptr)
    420 					quote = 0;
    421 				else {
    422 					cptr = wskip(cptr);
    423 					if (!*cptr)
    424 						quote = 0;
    425 					else
    426 						quote = *cptr;
    427 				}
    428 			} else if (isspace(*cptr)) {
    429 				;
    430 			} else {
    431 				if (*cptr) {
    432 					cptr = wskip(cptr);
    433 					if (*cptr)
    434 						warning(cptr, "unrecognized line");
    435 				}
    436 			}
    437 		} else {
    438 			if (isdigit(*cptr)) {
    439 				msgid = atoi(cptr);
    440 				cptr = cskip(cptr);
    441 				cptr = wskip(cptr);
    442 				/* if (*cptr) ++cptr; */
    443 			}
    444 			if (!*cptr)
    445 				MCDelMsg(msgid);
    446 			else {
    447 				str = getmsg(fd, cptr, quote);
    448 				MCAddMsg(msgid, str);
    449 			}
    450 		}
    451 	}
    452 }
    453 
    454 void
    455 MCReadCat(fd)
    456 	int     fd;
    457 {
    458 #if 0
    459 	MCHeaderT mcHead;
    460 	MCMsgT  mcMsg;
    461 	MCSetT  mcSet;
    462 	msgT   *msg;
    463 	setT   *set;
    464 	int     i;
    465 	char   *data;
    466 
    467 	/* XXX init sethead? */
    468 
    469 	if (read(fd, &mcHead, sizeof(mcHead)) != sizeof(mcHead))
    470 		corrupt();
    471 	if (strncmp(mcHead.magic, MCMagic, MCMagicLen) != 0)
    472 		corrupt();
    473 	if (mcHead.majorVer != MCMajorVer)
    474 		error(NULL, "unrecognized catalog version");
    475 	if ((mcHead.flags & MCGetByteOrder()) == 0)
    476 		error(NULL, "wrong byte order");
    477 
    478 	if (lseek(fd, mcHead.firstSet, SEEK_SET) == -1)
    479 		corrupt();
    480 
    481 	for (;;) {
    482 		if (read(fd, &mcSet, sizeof(mcSet)) != sizeof(mcSet))
    483 			corrupt();
    484 		if (mcSet.invalid)
    485 			continue;
    486 
    487 		set = xmalloc(sizeof(setT));
    488 		memset(set, '\0', sizeof(*set));
    489 		if (cat->first) {
    490 			cat->last->next = set;
    491 			set->prev = cat->last;
    492 			cat->last = set;
    493 		} else
    494 			cat->first = cat->last = set;
    495 
    496 		set->setId = mcSet.setId;
    497 
    498 		/* Get the data */
    499 		if (mcSet.dataLen) {
    500 			data = xmalloc(mcSet.dataLen);
    501 			if (lseek(fd, mcSet.data.off, SEEK_SET) == -1)
    502 				corrupt();
    503 			if (read(fd, data, mcSet.dataLen) != mcSet.dataLen)
    504 				corrupt();
    505 			if (lseek(fd, mcSet.u.firstMsg, SEEK_SET) == -1)
    506 				corrupt();
    507 
    508 			for (i = 0; i < mcSet.numMsgs; ++i) {
    509 				if (read(fd, &mcMsg, sizeof(mcMsg)) != sizeof(mcMsg))
    510 					corrupt();
    511 				if (mcMsg.invalid) {
    512 					--i;
    513 					continue;
    514 				}
    515 				msg = xmalloc(sizeof(msgT));
    516 				memset(msg, '\0', sizeof(*msg));
    517 				if (set->first) {
    518 					set->last->next = msg;
    519 					msg->prev = set->last;
    520 					set->last = msg;
    521 				} else
    522 					set->first = set->last = msg;
    523 
    524 				msg->msgId = mcMsg.msgId;
    525 				msg->str = xstrdup((char *) (data + mcMsg.msg.off));
    526 			}
    527 			free(data);
    528 		}
    529 		if (!mcSet.nextSet)
    530 			break;
    531 		if (lseek(fd, mcSet.nextSet, SEEK_SET) == -1)
    532 			corrupt();
    533 	}
    534 #endif
    535 }
    536 
    537 /*
    538  * Write message catalog.
    539  *
    540  * The message catalog is first converted from its internal to its
    541  * external representation in a chunk of memory allocated for this
    542  * purpose.  Then the completed catalog is written.  This approach
    543  * avoids additional housekeeping variables and/or a lot of seeks
    544  * that would otherwise be required.
    545  */
    546 void
    547 MCWriteCat(fd)
    548 	int     fd;
    549 {
    550 	int     nsets;		/* number of sets */
    551 	int     nmsgs;		/* number of msgs */
    552 	int     string_size;	/* total size of string pool */
    553 	int     msgcat_size;	/* total size of message catalog */
    554 	void   *msgcat;		/* message catalog data */
    555 	struct _nls_cat_hdr *cat_hdr;
    556 	struct _nls_set_hdr *set_hdr;
    557 	struct _nls_msg_hdr *msg_hdr;
    558 	char   *strings;
    559 	struct _setT *set;
    560 	struct _msgT *msg;
    561 	int     msg_index;
    562 	int     msg_offset;
    563 
    564 	/* determine number of sets, number of messages, and size of the
    565 	 * string pool */
    566 	nsets = 0;
    567 	nmsgs = 0;
    568 	string_size = 0;
    569 
    570 	for (set = sethead.lh_first; set != NULL;
    571 	    set = set->entries.le_next) {
    572 		nsets++;
    573 
    574 		for (msg = set->msghead.lh_first; msg != NULL;
    575 		    msg = msg->entries.le_next) {
    576 			nmsgs++;
    577 			string_size += strlen(msg->str) + 1;
    578 		}
    579 	}
    580 
    581 #ifdef DEBUG
    582 	printf("number of sets: %d\n", nsets);
    583 	printf("number of msgs: %d\n", nmsgs);
    584 	printf("string pool size: %d\n", string_size);
    585 #endif
    586 
    587 	/* determine size and then allocate buffer for constructing external
    588 	 * message catalog representation */
    589 	msgcat_size = sizeof(struct _nls_cat_hdr)
    590 	    + (nsets * sizeof(struct _nls_set_hdr))
    591 	    + (nmsgs * sizeof(struct _nls_msg_hdr))
    592 	    + string_size;
    593 
    594 	msgcat = xmalloc(msgcat_size);
    595 	memset(msgcat, '\0', msgcat_size);
    596 
    597 	/* fill in msg catalog header */
    598 	cat_hdr = (struct _nls_cat_hdr *) msgcat;
    599 	cat_hdr->__magic = htonl(_NLS_MAGIC);
    600 	cat_hdr->__nsets = htonl(nsets);
    601 	cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
    602 	cat_hdr->__msg_hdr_offset =
    603 	    htonl(nsets * sizeof(struct _nls_set_hdr));
    604 	cat_hdr->__msg_txt_offset =
    605 	    htonl(nsets * sizeof(struct _nls_set_hdr) +
    606 	    nmsgs * sizeof(struct _nls_msg_hdr));
    607 
    608 	/* compute offsets for set & msg header tables and string pool */
    609 	set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
    610 	    sizeof(struct _nls_cat_hdr));
    611 	msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
    612 	    sizeof(struct _nls_cat_hdr) +
    613 	    nsets * sizeof(struct _nls_set_hdr));
    614 	strings = (char *) msgcat +
    615 	    sizeof(struct _nls_cat_hdr) +
    616 	    nsets * sizeof(struct _nls_set_hdr) +
    617 	    nmsgs * sizeof(struct _nls_msg_hdr);
    618 
    619 	msg_index = 0;
    620 	msg_offset = 0;
    621 	for (set = sethead.lh_first; set != NULL;
    622 	    set = set->entries.le_next) {
    623 
    624 		nmsgs = 0;
    625 		for (msg = set->msghead.lh_first; msg != NULL;
    626 		    msg = msg->entries.le_next) {
    627 			int     msg_len = strlen(msg->str) + 1;
    628 
    629 			msg_hdr->__msgno = htonl(msg->msgId);
    630 			msg_hdr->__msglen = htonl(msg_len);
    631 			msg_hdr->__offset = htonl(msg_offset);
    632 
    633 			memcpy(strings, msg->str, msg_len);
    634 			strings += msg_len;
    635 			msg_offset += msg_len;
    636 
    637 			nmsgs++;
    638 			msg_hdr++;
    639 		}
    640 
    641 		set_hdr->__setno = htonl(set->setId);
    642 		set_hdr->__nmsgs = htonl(nmsgs);
    643 		set_hdr->__index = htonl(msg_index);
    644 		msg_index += nmsgs;
    645 		set_hdr++;
    646 	}
    647 
    648 	/* write out catalog.  XXX: should this be done in small chunks? */
    649 	write(fd, msgcat, msgcat_size);
    650 }
    651 
    652 void
    653 MCAddSet(setId)
    654 	int     setId;
    655 {
    656 	struct _setT *p, *q;
    657 
    658 	if (setId <= 0) {
    659 		error(NULL, "setId's must be greater than zero");
    660 		/* NOTREACHED */
    661 	}
    662 #if 0
    663 	/* XXX */
    664 	if (setId > NL_SETMAX) {
    665 		error(NULL, "setId %d exceeds limit (%d)");
    666 		/* NOTREACHED */
    667 	}
    668 #endif
    669 
    670 	p = sethead.lh_first;
    671 	q = NULL;
    672 	for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next);
    673 
    674 	if (p && p->setId == setId) {
    675 		;
    676 	} else {
    677 		p = xmalloc(sizeof(struct _setT));
    678 		memset(p, '\0', sizeof(struct _setT));
    679 		LIST_INIT(&p->msghead);
    680 
    681 		p->setId = setId;
    682 
    683 		if (q == NULL) {
    684 			LIST_INSERT_HEAD(&sethead, p, entries);
    685 		} else {
    686 			LIST_INSERT_AFTER(q, p, entries);
    687 		}
    688 	}
    689 
    690 	curSet = p;
    691 }
    692 
    693 void
    694 MCAddMsg(msgId, str)
    695 	int     msgId;
    696 	const char *str;
    697 {
    698 	struct _msgT *p, *q;
    699 
    700 	if (!curSet)
    701 		error(NULL, "can't specify a message when no set exists");
    702 
    703 	if (msgId <= 0) {
    704 		error(NULL, "msgId's must be greater than zero");
    705 		/* NOTREACHED */
    706 	}
    707 #if 0
    708 	/* XXX */
    709 	if (msgId > NL_SETMAX) {
    710 		error(NULL, "msgID %d exceeds limit (%d)");
    711 		/* NOTREACHED */
    712 	}
    713 #endif
    714 
    715 	p = curSet->msghead.lh_first;
    716 	q = NULL;
    717 	for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next);
    718 
    719 	if (p && p->msgId == msgId) {
    720 		free(p->str);
    721 	} else {
    722 		p = xmalloc(sizeof(struct _msgT));
    723 		memset(p, '\0', sizeof(struct _msgT));
    724 
    725 		if (q == NULL) {
    726 			LIST_INSERT_HEAD(&curSet->msghead, p, entries);
    727 		} else {
    728 			LIST_INSERT_AFTER(q, p, entries);
    729 		}
    730 	}
    731 
    732 	p->msgId = msgId;
    733 	p->str = xstrdup(str);
    734 }
    735 
    736 void
    737 MCDelSet(setId)
    738 	int     setId;
    739 {
    740 	struct _setT *set;
    741 	struct _msgT *msg;
    742 
    743 	set = sethead.lh_first;
    744 	for (; set != NULL && set->setId < setId; set = set->entries.le_next);
    745 
    746 	if (set && set->setId == setId) {
    747 
    748 		msg = set->msghead.lh_first;
    749 		while (msg) {
    750 			free(msg->str);
    751 			LIST_REMOVE(msg, entries)
    752 		}
    753 
    754 		LIST_REMOVE(set, entries);
    755 		return;
    756 	}
    757 	warning(NULL, "specified set doesn't exist");
    758 }
    759 
    760 void
    761 MCDelMsg(msgId)
    762 	int     msgId;
    763 {
    764 	struct _msgT *msg;
    765 
    766 	if (!curSet)
    767 		error(NULL, "you can't delete a message before defining the set");
    768 
    769 	msg = curSet->msghead.lh_first;
    770 	for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next);
    771 
    772 	if (msg && msg->msgId == msgId) {
    773 		free(msg->str);
    774 		LIST_REMOVE(msg, entries);
    775 		return;
    776 	}
    777 	warning(NULL, "specified msg doesn't exist");
    778 }
    779