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