Home | History | Annotate | Line # | Download | only in gencat
gencat.c revision 1.22
      1 /*	$NetBSD: gencat.c,v 1.22 2007/10/10 02:34:18 ginsbach 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.22 2007/10/10 02:34:18 ginsbach 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_NBTOOL_CONFIG_H
     77 #include "nbtool_config.h"
     78 #endif
     79 
     80 #define _NLS_PRIVATE
     81 
     82 #include <sys/types.h>
     83 #include <sys/queue.h>
     84 
     85 #include <netinet/in.h>	/* Needed by arpa/inet.h on NetBSD */
     86 #include <arpa/inet.h>	/* Needed for htonl() on POSIX systems */
     87 
     88 #include <ctype.h>
     89 #include <err.h>
     90 #include <fcntl.h>
     91 #include <limits.h>
     92 #include <nl_types.h>
     93 #include <stdio.h>
     94 #include <stdlib.h>
     95 #include <string.h>
     96 #include <unistd.h>
     97 
     98 #ifndef NL_SETMAX
     99 #define NL_SETMAX 255
    100 #endif
    101 #ifndef NL_MSGMAX
    102 #define NL_MSGMAX 2048
    103 #endif
    104 
    105 struct _msgT {
    106 	long    msgId;
    107 	char   *str;
    108         LIST_ENTRY(_msgT) entries;
    109 };
    110 
    111 struct _setT {
    112 	long    setId;
    113         LIST_HEAD(msghead, _msgT) msghead;
    114         LIST_ENTRY(_setT) entries;
    115 };
    116 
    117 LIST_HEAD(sethead, _setT) sethead;
    118 static struct _setT *curSet;
    119 
    120 static char *curline = NULL;
    121 static long lineno = 0;
    122 
    123 #if 0	/* XXX unused */
    124 static	void	corrupt __P((void));
    125 #endif
    126 static	char   *cskip __P((char *));
    127 static	void	error __P((const char *));
    128 static	void	nomem __P((void));
    129 static	char   *getline __P((int));
    130 static	char   *getmsg __P((int, char *, char));
    131 static	void	warning __P((const char *, const char *));
    132 static	char   *wskip __P((char *));
    133 static	char   *xstrdup __P((const char *));
    134 static	void   *xmalloc __P((size_t));
    135 static	void   *xrealloc __P((void *, size_t));
    136 
    137 void	MCParse __P((int fd));
    138 void	MCReadCat __P((int fd));
    139 void	MCWriteCat __P((int fd));
    140 void	MCDelMsg __P((int msgId));
    141 void	MCAddMsg __P((int msgId, const char *msg));
    142 void	MCAddSet __P((int setId));
    143 void	MCDelSet __P((int setId));
    144 int	main __P((int, char **));
    145 void	usage __P((void));
    146 
    147 
    148 void
    149 usage()
    150 {
    151 	fprintf(stderr, "usage: %s catfile msgfile ...\n", getprogname());
    152 	exit(1);
    153 }
    154 
    155 int
    156 main(argc, argv)
    157 	int     argc;
    158 	char   *argv[];
    159 {
    160 	int     ofd, ifd;
    161 	char   *catfile = NULL;
    162 	int     c;
    163 
    164 	while ((c = getopt(argc, argv, "")) != -1) {
    165 		switch (c) {
    166 		case '?':
    167 		default:
    168 			usage();
    169 			/* NOTREACHED */
    170 		}
    171 	}
    172 	argc -= optind;
    173 	argv += optind;
    174 
    175 	if (argc < 2) {
    176 		usage();
    177 		/* NOTREACHED */
    178 	}
    179 	catfile = *argv++;
    180 
    181 	for (; *argv; argv++) {
    182 		if ((ifd = open(*argv, O_RDONLY)) < 0)
    183 			err(1, "Unable to read %s", *argv);
    184 		MCParse(ifd);
    185 		close(ifd);
    186 	}
    187 
    188 	if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
    189 		err(1, "Unable to create a new %s", catfile);
    190 	MCWriteCat(ofd);
    191 	exit(0);
    192 }
    193 
    194 static void
    195 warning(cptr, msg)
    196 	const char   *cptr;
    197 	const char   *msg;
    198 {
    199 	fprintf(stderr, "%s: %s on line %ld\n", getprogname(), msg, lineno);
    200 	fprintf(stderr, "%s\n", curline);
    201 	if (cptr) {
    202 		char   *tptr;
    203 		for (tptr = curline; tptr < cptr; ++tptr)
    204 			putc(' ', stderr);
    205 		fprintf(stderr, "^\n");
    206 	}
    207 }
    208 
    209 static void
    210 error(msg)
    211 	const char   *msg;
    212 {
    213 	warning(NULL, msg);
    214 	exit(1);
    215 }
    216 
    217 #if 0	/* XXX unused */
    218 static void
    219 corrupt()
    220 {
    221 	error("corrupt message catalog");
    222 }
    223 #endif
    224 
    225 static void
    226 nomem()
    227 {
    228 	error("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("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	msgid = 0;
    442 	int     setid = 0;
    443 	char    quote = 0;
    444 
    445 	/* XXX: init sethead? */
    446 
    447 	while ((cptr = getline(fd))) {
    448 		if (*cptr == '$') {
    449 			++cptr;
    450 			if (strncmp(cptr, "set", 3) == 0) {
    451 				cptr += 3;
    452 				cptr = wskip(cptr);
    453 				setid = atoi(cptr);
    454 				MCAddSet(setid);
    455 				msgid = 0;
    456 			} else if (strncmp(cptr, "delset", 6) == 0) {
    457 				cptr += 6;
    458 				cptr = wskip(cptr);
    459 				setid = atoi(cptr);
    460 				MCDelSet(setid);
    461 			} else if (strncmp(cptr, "quote", 5) == 0) {
    462 				cptr += 5;
    463 				if (!*cptr)
    464 					quote = 0;
    465 				else {
    466 					cptr = wskip(cptr);
    467 					if (!*cptr)
    468 						quote = 0;
    469 					else
    470 						quote = *cptr;
    471 				}
    472 			} else if (isspace((unsigned char) *cptr)) {
    473 				;
    474 			} else {
    475 				if (*cptr) {
    476 					cptr = wskip(cptr);
    477 					if (*cptr)
    478 						warning(cptr, "unrecognized line");
    479 				}
    480 			}
    481 		} else {
    482 			/*
    483 			 * First check for (and eat) empty lines....
    484 			 */
    485 			if (!*cptr)
    486 				continue;
    487 			/*
    488 			 * We have a digit? Start of a message. Else,
    489 			 * syntax error.
    490 			 */
    491 			if (isdigit((unsigned char) *cptr)) {
    492 				msgid = atoi(cptr);
    493 				cptr = cskip(cptr);
    494 				cptr = wskip(cptr);
    495 				/* if (*cptr) ++cptr; */
    496 			} else {
    497 				warning(cptr, "neither blank line nor start of a message id");
    498 				continue;
    499 			}
    500 			/*
    501 			 * If no set directive specified, all messages
    502 			 * shall be in default message set NL_SETD.
    503 			 */
    504 			if (setid == 0) {
    505 				setid = NL_SETD;
    506 				MCAddSet(setid);
    507 			}
    508 			/*
    509 			 * If we have a message ID, but no message,
    510 			 * then this means "delete this message id
    511 			 * from the catalog".
    512 			 */
    513 			if (!*cptr) {
    514 				MCDelMsg(msgid);
    515 			} else {
    516 				str = getmsg(fd, cptr, quote);
    517 				MCAddMsg(msgid, str);
    518 			}
    519 		}
    520 	}
    521 }
    522 
    523 void
    524 MCReadCat(fd)
    525 	int     fd;
    526 {
    527 #if 0
    528 	MCHeaderT mcHead;
    529 	MCMsgT  mcMsg;
    530 	MCSetT  mcSet;
    531 	msgT   *msg;
    532 	setT   *set;
    533 	int     i;
    534 	char   *data;
    535 
    536 	/* XXX init sethead? */
    537 
    538 	if (read(fd, &mcHead, sizeof(mcHead)) != sizeof(mcHead))
    539 		corrupt();
    540 	if (strncmp(mcHead.magic, MCMagic, MCMagicLen) != 0)
    541 		corrupt();
    542 	if (mcHead.majorVer != MCMajorVer)
    543 		error("unrecognized catalog version");
    544 	if ((mcHead.flags & MCGetByteOrder()) == 0)
    545 		error("wrong byte order");
    546 
    547 	if (lseek(fd, mcHead.firstSet, SEEK_SET) == -1)
    548 		corrupt();
    549 
    550 	for (;;) {
    551 		if (read(fd, &mcSet, sizeof(mcSet)) != sizeof(mcSet))
    552 			corrupt();
    553 		if (mcSet.invalid)
    554 			continue;
    555 
    556 		set = xmalloc(sizeof(setT));
    557 		memset(set, '\0', sizeof(*set));
    558 		if (cat->first) {
    559 			cat->last->next = set;
    560 			set->prev = cat->last;
    561 			cat->last = set;
    562 		} else
    563 			cat->first = cat->last = set;
    564 
    565 		set->setId = mcSet.setId;
    566 
    567 		/* Get the data */
    568 		if (mcSet.dataLen) {
    569 			data = xmalloc(mcSet.dataLen);
    570 			if (lseek(fd, mcSet.data.off, SEEK_SET) == -1)
    571 				corrupt();
    572 			if (read(fd, data, mcSet.dataLen) != mcSet.dataLen)
    573 				corrupt();
    574 			if (lseek(fd, mcSet.u.firstMsg, SEEK_SET) == -1)
    575 				corrupt();
    576 
    577 			for (i = 0; i < mcSet.numMsgs; ++i) {
    578 				if (read(fd, &mcMsg, sizeof(mcMsg)) != sizeof(mcMsg))
    579 					corrupt();
    580 				if (mcMsg.invalid) {
    581 					--i;
    582 					continue;
    583 				}
    584 				msg = xmalloc(sizeof(msgT));
    585 				memset(msg, '\0', sizeof(*msg));
    586 				if (set->first) {
    587 					set->last->next = msg;
    588 					msg->prev = set->last;
    589 					set->last = msg;
    590 				} else
    591 					set->first = set->last = msg;
    592 
    593 				msg->msgId = mcMsg.msgId;
    594 				msg->str = xstrdup((char *) (data + mcMsg.msg.off));
    595 			}
    596 			free(data);
    597 		}
    598 		if (!mcSet.nextSet)
    599 			break;
    600 		if (lseek(fd, mcSet.nextSet, SEEK_SET) == -1)
    601 			corrupt();
    602 	}
    603 #endif
    604 }
    605 
    606 /*
    607  * Write message catalog.
    608  *
    609  * The message catalog is first converted from its internal to its
    610  * external representation in a chunk of memory allocated for this
    611  * purpose.  Then the completed catalog is written.  This approach
    612  * avoids additional housekeeping variables and/or a lot of seeks
    613  * that would otherwise be required.
    614  */
    615 void
    616 MCWriteCat(fd)
    617 	int     fd;
    618 {
    619 	int     nsets;		/* number of sets */
    620 	int     nmsgs;		/* number of msgs */
    621 	int     string_size;	/* total size of string pool */
    622 	int     msgcat_size;	/* total size of message catalog */
    623 	void   *msgcat;		/* message catalog data */
    624 	struct _nls_cat_hdr *cat_hdr;
    625 	struct _nls_set_hdr *set_hdr;
    626 	struct _nls_msg_hdr *msg_hdr;
    627 	char   *strings;
    628 	struct _setT *set;
    629 	struct _msgT *msg;
    630 	int     msg_index;
    631 	int     msg_offset;
    632 
    633 	/* determine number of sets, number of messages, and size of the
    634 	 * string pool */
    635 	nsets = 0;
    636 	nmsgs = 0;
    637 	string_size = 0;
    638 
    639 	for (set = sethead.lh_first; set != NULL;
    640 	    set = set->entries.le_next) {
    641 		nsets++;
    642 
    643 		for (msg = set->msghead.lh_first; msg != NULL;
    644 		    msg = msg->entries.le_next) {
    645 			nmsgs++;
    646 			string_size += strlen(msg->str) + 1;
    647 		}
    648 	}
    649 
    650 #ifdef DEBUG
    651 	printf("number of sets: %d\n", nsets);
    652 	printf("number of msgs: %d\n", nmsgs);
    653 	printf("string pool size: %d\n", string_size);
    654 #endif
    655 
    656 	/* determine size and then allocate buffer for constructing external
    657 	 * message catalog representation */
    658 	msgcat_size = sizeof(struct _nls_cat_hdr)
    659 	    + (nsets * sizeof(struct _nls_set_hdr))
    660 	    + (nmsgs * sizeof(struct _nls_msg_hdr))
    661 	    + string_size;
    662 
    663 	msgcat = xmalloc(msgcat_size);
    664 	memset(msgcat, '\0', msgcat_size);
    665 
    666 	/* fill in msg catalog header */
    667 	cat_hdr = (struct _nls_cat_hdr *) msgcat;
    668 	cat_hdr->__magic = htonl(_NLS_MAGIC);
    669 	cat_hdr->__nsets = htonl(nsets);
    670 	cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
    671 	cat_hdr->__msg_hdr_offset =
    672 	    htonl(nsets * sizeof(struct _nls_set_hdr));
    673 	cat_hdr->__msg_txt_offset =
    674 	    htonl(nsets * sizeof(struct _nls_set_hdr) +
    675 	    nmsgs * sizeof(struct _nls_msg_hdr));
    676 
    677 	/* compute offsets for set & msg header tables and string pool */
    678 	set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
    679 	    sizeof(struct _nls_cat_hdr));
    680 	msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
    681 	    sizeof(struct _nls_cat_hdr) +
    682 	    nsets * sizeof(struct _nls_set_hdr));
    683 	strings = (char *) msgcat +
    684 	    sizeof(struct _nls_cat_hdr) +
    685 	    nsets * sizeof(struct _nls_set_hdr) +
    686 	    nmsgs * sizeof(struct _nls_msg_hdr);
    687 
    688 	msg_index = 0;
    689 	msg_offset = 0;
    690 	for (set = sethead.lh_first; set != NULL;
    691 	    set = set->entries.le_next) {
    692 
    693 		nmsgs = 0;
    694 		for (msg = set->msghead.lh_first; msg != NULL;
    695 		    msg = msg->entries.le_next) {
    696 			int     msg_len = strlen(msg->str) + 1;
    697 
    698 			msg_hdr->__msgno = htonl(msg->msgId);
    699 			msg_hdr->__msglen = htonl(msg_len);
    700 			msg_hdr->__offset = htonl(msg_offset);
    701 
    702 			memcpy(strings, msg->str, msg_len);
    703 			strings += msg_len;
    704 			msg_offset += msg_len;
    705 
    706 			nmsgs++;
    707 			msg_hdr++;
    708 		}
    709 
    710 		set_hdr->__setno = htonl(set->setId);
    711 		set_hdr->__nmsgs = htonl(nmsgs);
    712 		set_hdr->__index = htonl(msg_index);
    713 		msg_index += nmsgs;
    714 		set_hdr++;
    715 	}
    716 
    717 	/* write out catalog.  XXX: should this be done in small chunks? */
    718 	write(fd, msgcat, msgcat_size);
    719 }
    720 
    721 void
    722 MCAddSet(setId)
    723 	int     setId;
    724 {
    725 	struct _setT *p, *q;
    726 
    727 	if (setId <= 0) {
    728 		error("setId's must be greater than zero");
    729 		/* NOTREACHED */
    730 	}
    731 	if (setId > NL_SETMAX) {
    732 		error("setId exceeds limit");
    733 		/* NOTREACHED */
    734 	}
    735 
    736 	p = sethead.lh_first;
    737 	q = NULL;
    738 	for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next);
    739 
    740 	if (p && p->setId == setId) {
    741 		;
    742 	} else {
    743 		p = xmalloc(sizeof(struct _setT));
    744 		memset(p, '\0', sizeof(struct _setT));
    745 		LIST_INIT(&p->msghead);
    746 
    747 		p->setId = setId;
    748 
    749 		if (q == NULL) {
    750 			LIST_INSERT_HEAD(&sethead, p, entries);
    751 		} else {
    752 			LIST_INSERT_AFTER(q, p, entries);
    753 		}
    754 	}
    755 
    756 	curSet = p;
    757 }
    758 
    759 void
    760 MCAddMsg(msgId, str)
    761 	int     msgId;
    762 	const char *str;
    763 {
    764 	struct _msgT *p, *q;
    765 
    766 	if (!curSet)
    767 		error("can't specify a message when no set exists");
    768 
    769 	if (msgId <= 0) {
    770 		error("msgId's must be greater than zero");
    771 		/* NOTREACHED */
    772 	}
    773 	if (msgId > NL_MSGMAX) {
    774 		error("msgID exceeds limit");
    775 		/* NOTREACHED */
    776 	}
    777 
    778 	p = curSet->msghead.lh_first;
    779 	q = NULL;
    780 	for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next);
    781 
    782 	if (p && p->msgId == msgId) {
    783 		free(p->str);
    784 	} else {
    785 		p = xmalloc(sizeof(struct _msgT));
    786 		memset(p, '\0', sizeof(struct _msgT));
    787 
    788 		if (q == NULL) {
    789 			LIST_INSERT_HEAD(&curSet->msghead, p, entries);
    790 		} else {
    791 			LIST_INSERT_AFTER(q, p, entries);
    792 		}
    793 	}
    794 
    795 	p->msgId = msgId;
    796 	p->str = xstrdup(str);
    797 }
    798 
    799 void
    800 MCDelSet(setId)
    801 	int     setId;
    802 {
    803 	struct _setT *set;
    804 	struct _msgT *msg;
    805 
    806 	if (setId <= 0) {
    807 		error("setId's must be greater than zero");
    808 		/* NOTREACHED */
    809 	}
    810 	if (setId > NL_SETMAX) {
    811 		error("setId exceeds limit");
    812 		/* NOTREACHED */
    813 	}
    814 
    815 	set = sethead.lh_first;
    816 	for (; set != NULL && set->setId < setId; set = set->entries.le_next);
    817 
    818 	if (set && set->setId == setId) {
    819 		LIST_REMOVE(set, entries);
    820 		while ((msg = set->msghead.lh_first) != NULL) {
    821 			LIST_REMOVE(msg, entries);
    822 			free(msg->str);
    823 			free(msg);
    824 		}
    825 		free(set);
    826 		return;
    827 	}
    828 	warning(NULL, "specified set doesn't exist");
    829 }
    830 
    831 void
    832 MCDelMsg(msgId)
    833 	int     msgId;
    834 {
    835 	struct _msgT *msg;
    836 
    837 	if (!curSet)
    838 		error("you can't delete a message before defining the set");
    839 
    840 	msg = curSet->msghead.lh_first;
    841 	for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next);
    842 
    843 	if (msg && msg->msgId == msgId) {
    844 		LIST_REMOVE(msg, entries);
    845 		free(msg->str);
    846 		free(msg);
    847 		return;
    848 	}
    849 	warning(NULL, "specified msg doesn't exist");
    850 }
    851