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