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