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