Home | History | Annotate | Line # | Download | only in gencat
      1 /*	$NetBSD: gencat.c,v 1.37 2026/01/26 15:40:33 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.37 2026/01/26 15:40:33 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 = LIST_HEAD_INITIALIZER(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 < 1) {
    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 (argc < 2 || (((*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 	int     in_quote = 0;
    350 	char   *tptr;
    351 
    352 	if (quote && *cptr == quote) {
    353 		++cptr;
    354 		in_quote = 1;
    355 	}
    356 
    357 	clen = strlen(cptr) + 1;
    358 	if (clen > msglen) {
    359 		if (msglen)
    360 			msg = xrealloc(msg, clen);
    361 		else
    362 			msg = xmalloc(clen);
    363 		msglen = clen;
    364 	}
    365 	tptr = msg;
    366 
    367 	while (*cptr) {
    368 		if (quote && *cptr == quote) {
    369 			char   *tmp;
    370 			tmp = cptr + 1;
    371 			if (!in_quote) {
    372 				/* XXX hard error? */
    373 				warning(cptr, "unexpected quote character, ignoring");
    374 				*tptr++ = *cptr++;
    375 			} else {
    376 				cptr++;
    377 				/* don't use wskip() */
    378 				while (*cptr && isspace((unsigned char) *cptr))
    379 #ifndef _BACKWARDS_COMPAT
    380 					cptr++;
    381 #else
    382 					*tptr++ = *cptr++;
    383 #endif
    384 				/* XXX hard error? */
    385 				if (*cptr)
    386 					warning(tmp, "unexpected extra characters, ignoring");
    387 				in_quote = 0;
    388 #ifndef _BACKWARDS_COMPAT
    389 				break;
    390 #endif
    391 			}
    392 		} else {
    393 			if (*cptr == '\\') {
    394 				++cptr;
    395 				switch (*cptr) {
    396 				case '\0':
    397 					cptr = get_line(fd);
    398 					if (!cptr)
    399 						error("premature end of file");
    400 					msglen += strlen(cptr);
    401 					i = tptr - msg;
    402 					msg = xrealloc(msg, msglen);
    403 					tptr = msg + i;
    404 					break;
    405 				case 'n':
    406 					*tptr++ = '\n';
    407 					++cptr;
    408 					break;
    409 				case 't':
    410 					*tptr++ = '\t';
    411 					++cptr;
    412 					break;
    413 				case 'v':
    414 					*tptr++ = '\v';
    415 					++cptr;
    416 					break;
    417 				case 'b':
    418 					*tptr++ = '\b';
    419 					++cptr;
    420 					break;
    421 				case 'r':
    422 					*tptr++ = '\r';
    423 					++cptr;
    424 					break;
    425 				case 'f':
    426 					*tptr++ = '\f';
    427 					++cptr;
    428 					break;
    429 				case '\\':
    430 					*tptr++ = '\\';
    431 					++cptr;
    432 					break;
    433 				default:
    434 					if (quote && *cptr == quote) {
    435 						*tptr++ = *cptr++;
    436 					} else if (isdigit((unsigned char) *cptr)) {
    437 						*tptr = 0;
    438 						for (i = 0; i < 3; ++i) {
    439 							if (!isdigit((unsigned char) *cptr))
    440 								break;
    441 							if (*cptr > '7')
    442 								warning(cptr, "octal number greater than 7?!");
    443 							*tptr *= 8;
    444 							*tptr += (*cptr - '0');
    445 							++cptr;
    446 						}
    447 						++tptr;
    448 					} else {
    449 						warning(cptr, "unrecognized escape sequence");
    450 					}
    451 					break;
    452 				}
    453 			} else {
    454 				*tptr++ = *cptr++;
    455 			}
    456 		}
    457 	}
    458 
    459 	if (in_quote)
    460 		warning(cptr, "unterminated quoted message, ignoring");
    461 
    462 	*tptr = '\0';
    463 	return (msg);
    464 }
    465 
    466 static void
    467 MCParse(int fd)
    468 {
    469 	char   *cptr, *str;
    470 	int	msgid = 0;
    471 	int     setid = 0;
    472 	char    quote = 0;
    473 
    474 	while ((cptr = get_line(fd))) {
    475 		if (*cptr == '$') {
    476 			++cptr;
    477 			if (strncmp(cptr, "set", 3) == 0) {
    478 				cptr += 3;
    479 				cptr = wskip(cptr);
    480 				setid = atoi(cptr);
    481 				MCAddSet(setid);
    482 				msgid = 0;
    483 			} else if (strncmp(cptr, "delset", 6) == 0) {
    484 				cptr += 6;
    485 				cptr = wskip(cptr);
    486 				setid = atoi(cptr);
    487 				MCDelSet(setid);
    488 			} else if (strncmp(cptr, "quote", 5) == 0) {
    489 				cptr += 5;
    490 				if (!*cptr)
    491 					quote = 0;
    492 				else {
    493 					cptr = wskip(cptr);
    494 					if (!*cptr)
    495 						quote = 0;
    496 					else
    497 						quote = *cptr;
    498 				}
    499 			} else if (isspace((unsigned char) *cptr)) {
    500 				;
    501 			} else {
    502 				if (*cptr) {
    503 					cptr = wskip(cptr);
    504 					if (*cptr)
    505 						warning(cptr, "unrecognized line");
    506 				}
    507 			}
    508 		} else {
    509 			/*
    510 			 * First check for (and eat) empty lines....
    511 			 */
    512 			if (!*cptr)
    513 				continue;
    514 			/*
    515 			 * We have a digit? Start of a message. Else,
    516 			 * syntax error.
    517 			 */
    518 			if (isdigit((unsigned char) *cptr)) {
    519 				msgid = atoi(cptr);
    520 				cptr = cskip(cptr);
    521 				if (*cptr) {
    522 					cptr = wskip(cptr);
    523 					if (!*cptr) {
    524 						MCAddMsg(msgid, "");
    525 						continue;
    526 					}
    527 				}
    528 			} else {
    529 				warning(cptr, "neither blank line nor start of a message id");
    530 				continue;
    531 			}
    532 			/*
    533 			 * If no set directive specified, all messages
    534 			 * shall be in default message set NL_SETD.
    535 			 */
    536 			if (setid == 0) {
    537 				setid = NL_SETD;
    538 				MCAddSet(setid);
    539 			}
    540 			/*
    541 			 * If we have a message ID, but no message,
    542 			 * then this means "delete this message id
    543 			 * from the catalog".
    544 			 */
    545 			if (!*cptr) {
    546 				MCDelMsg(msgid);
    547 			} else {
    548 				str = getmsg(fd, cptr, quote);
    549 				MCAddMsg(msgid, str);
    550 			}
    551 		}
    552 	}
    553 }
    554 
    555 static void
    556 MCReadCat(int fd)
    557 {
    558 	void   *msgcat;		/* message catalog data */
    559 	struct _nls_cat_hdr cat_hdr;
    560 	struct _nls_set_hdr *set_hdr;
    561 	struct _nls_msg_hdr *msg_hdr;
    562 	char   *strings;
    563 	ssize_t	n;
    564 	int	m, s;
    565 	int	msgno, setno;
    566 
    567 	n = read(fd, &cat_hdr, sizeof(cat_hdr));
    568 	if (n < (ssize_t)sizeof(cat_hdr)) {
    569 		if (n == 0)
    570 			return;		/* empty file */
    571 		else if (n == -1)
    572 			err(1, "header read");
    573 		else
    574 			errx(1, CORRUPT);
    575 	}
    576 	if (ntohl((uint32_t)cat_hdr.__magic) != _NLS_MAGIC)
    577 		errx(1, "%s: bad magic number (%#x)", CORRUPT, cat_hdr.__magic);
    578 
    579 	cat_hdr.__mem = ntohl(cat_hdr.__mem);
    580 
    581 	cat_hdr.__nsets = ntohl(cat_hdr.__nsets);
    582 	cat_hdr.__msg_hdr_offset = ntohl(cat_hdr.__msg_hdr_offset);
    583 	cat_hdr.__msg_txt_offset = ntohl(cat_hdr.__msg_txt_offset);
    584 	if ((cat_hdr.__mem < 0) ||
    585 	    (cat_hdr.__msg_hdr_offset < 0) ||
    586 	    (cat_hdr.__msg_txt_offset < 0) ||
    587 	    (cat_hdr.__mem < (int32_t)(cat_hdr.__nsets * sizeof(struct _nls_set_hdr))) ||
    588 	    (cat_hdr.__mem < cat_hdr.__msg_hdr_offset) ||
    589 	    (cat_hdr.__mem < cat_hdr.__msg_txt_offset))
    590 		errx(1, "%s: catalog header", CORRUPT);
    591 
    592 	msgcat = xmalloc(cat_hdr.__mem);
    593 
    594 	n = read(fd, msgcat, cat_hdr.__mem);
    595 	if (n < cat_hdr.__mem) {
    596 		if (n == -1)
    597 			err(1, "data read");
    598 		else
    599 			errx(1, CORRUPT);
    600 	}
    601 
    602 	set_hdr = (struct _nls_set_hdr *)msgcat;
    603 	msg_hdr = (struct _nls_msg_hdr *)((char *)msgcat +
    604 	    cat_hdr.__msg_hdr_offset);
    605 	strings = (char *)msgcat + cat_hdr.__msg_txt_offset;
    606 
    607 	setno = 0;
    608 	for (s = 0; s < cat_hdr.__nsets; s++, set_hdr++) {
    609 		set_hdr->__setno = ntohl(set_hdr->__setno);
    610 		if (set_hdr->__setno < setno)
    611 			errx(1, "%s: bad set number (%d)",
    612 		       	     CORRUPT, set_hdr->__setno);
    613 		setno = set_hdr->__setno;
    614 
    615 		MCAddSet(setno);
    616 
    617 		set_hdr->__nmsgs = ntohl(set_hdr->__nmsgs);
    618 		set_hdr->__index = ntohl(set_hdr->__index);
    619 		if (set_hdr->__nmsgs < 0 || set_hdr->__index < 0)
    620 			errx(1, "%s: set header", CORRUPT);
    621 
    622 		/* Get the data */
    623 		msgno = 0;
    624 		for (m = 0; m < set_hdr->__nmsgs; m++, msg_hdr++) {
    625 			msg_hdr->__msgno = ntohl(msg_hdr->__msgno);
    626 			msg_hdr->__offset = ntohl(msg_hdr->__offset);
    627 			if (msg_hdr->__msgno < msgno)
    628 				errx(1, "%s: bad message number (%d)",
    629 				     CORRUPT, msg_hdr->__msgno);
    630 		        if ((msg_hdr->__offset < 0) ||
    631 			    ((strings + msg_hdr->__offset) >
    632 			     ((char *)msgcat + cat_hdr.__mem)))
    633 				errx(1, "%s: message header", CORRUPT);
    634 
    635 			msgno = msg_hdr->__msgno;
    636 			MCAddMsg(msgno, strings + msg_hdr->__offset);
    637 		}
    638 	}
    639 	free(msgcat);
    640 }
    641 
    642 /*
    643  * Write message catalog.
    644  *
    645  * The message catalog is first converted from its internal to its
    646  * external representation in a chunk of memory allocated for this
    647  * purpose.  Then the completed catalog is written.  This approach
    648  * avoids additional housekeeping variables and/or a lot of seeks
    649  * that would otherwise be required.
    650  */
    651 static void
    652 MCWriteCat(int fd)
    653 {
    654 	int     nsets;		/* number of sets */
    655 	int     nmsgs;		/* number of msgs */
    656 	int     string_size;	/* total size of string pool */
    657 	int     msgcat_size;	/* total size of message catalog */
    658 	void   *msgcat;		/* message catalog data */
    659 	struct _nls_cat_hdr *cat_hdr;
    660 	struct _nls_set_hdr *set_hdr;
    661 	struct _nls_msg_hdr *msg_hdr;
    662 	char   *strings;
    663 	struct _setT *set;
    664 	struct _msgT *msg;
    665 	int     msg_index;
    666 	int     msg_offset;
    667 
    668 	/* determine number of sets, number of messages, and size of the
    669 	 * string pool */
    670 	nsets = 0;
    671 	nmsgs = 0;
    672 	string_size = 0;
    673 
    674 	LIST_FOREACH(set, &sethead, entries) {
    675 		nsets++;
    676 
    677 		LIST_FOREACH(msg, &set->msghead, entries) {
    678 			nmsgs++;
    679 			string_size += strlen(msg->str) + 1;
    680 		}
    681 	}
    682 
    683 #ifdef DEBUG
    684 	printf("number of sets: %d\n", nsets);
    685 	printf("number of msgs: %d\n", nmsgs);
    686 	printf("string pool size: %d\n", string_size);
    687 #endif
    688 
    689 	/* determine size and then allocate buffer for constructing external
    690 	 * message catalog representation */
    691 	msgcat_size = sizeof(struct _nls_cat_hdr)
    692 	    + (nsets * sizeof(struct _nls_set_hdr))
    693 	    + (nmsgs * sizeof(struct _nls_msg_hdr))
    694 	    + string_size;
    695 
    696 	msgcat = xmalloc(msgcat_size);
    697 	memset(msgcat, '\0', msgcat_size);
    698 
    699 	/* fill in msg catalog header */
    700 	cat_hdr = (struct _nls_cat_hdr *) msgcat;
    701 	cat_hdr->__magic = htonl(_NLS_MAGIC);
    702 	cat_hdr->__nsets = htonl(nsets);
    703 	cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
    704 	cat_hdr->__msg_hdr_offset =
    705 	    htonl(nsets * sizeof(struct _nls_set_hdr));
    706 	cat_hdr->__msg_txt_offset =
    707 	    htonl(nsets * sizeof(struct _nls_set_hdr) +
    708 	    nmsgs * sizeof(struct _nls_msg_hdr));
    709 
    710 	/* compute offsets for set & msg header tables and string pool */
    711 	set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
    712 	    sizeof(struct _nls_cat_hdr));
    713 	msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
    714 	    sizeof(struct _nls_cat_hdr) +
    715 	    nsets * sizeof(struct _nls_set_hdr));
    716 	strings = (char *) msgcat +
    717 	    sizeof(struct _nls_cat_hdr) +
    718 	    nsets * sizeof(struct _nls_set_hdr) +
    719 	    nmsgs * sizeof(struct _nls_msg_hdr);
    720 
    721 	msg_index = 0;
    722 	msg_offset = 0;
    723 	LIST_FOREACH(set, &sethead, entries) {
    724 
    725 		nmsgs = 0;
    726 		LIST_FOREACH(msg, &set->msghead, entries) {
    727 			int32_t     msg_len = strlen(msg->str) + 1;
    728 
    729 			msg_hdr->__msgno = htonl(msg->msgId);
    730 			msg_hdr->__msglen = htonl(msg_len);
    731 			msg_hdr->__offset = htonl(msg_offset);
    732 
    733 			memcpy(strings, msg->str, msg_len);
    734 			strings += msg_len;
    735 			msg_offset += msg_len;
    736 
    737 			nmsgs++;
    738 			msg_hdr++;
    739 		}
    740 
    741 		set_hdr->__setno = htonl(set->setId);
    742 		set_hdr->__nmsgs = htonl(nmsgs);
    743 		set_hdr->__index = htonl(msg_index);
    744 		msg_index += nmsgs;
    745 		set_hdr++;
    746 	}
    747 
    748 	/* write out catalog.  XXX: should this be done in small chunks? */
    749 	write(fd, msgcat, msgcat_size);
    750 }
    751 
    752 static void
    753 MCAddSet(int setId)
    754 {
    755 	struct _setT *p, *q;
    756 
    757 	if (setId <= 0) {
    758 		error("setId's must be greater than zero");
    759 		/* NOTREACHED */
    760 	}
    761 	if (setId > NL_SETMAX) {
    762 		error("setId exceeds limit");
    763 		/* NOTREACHED */
    764 	}
    765 
    766 	p = LIST_FIRST(&sethead);
    767 	q = NULL;
    768 	for (; p != NULL && p->setId < setId; q = p, p = LIST_NEXT(p, entries))
    769 		continue;
    770 
    771 	if (p && p->setId == setId) {
    772 		;
    773 	} else {
    774 		p = xmalloc(sizeof(struct _setT));
    775 		memset(p, '\0', sizeof(struct _setT));
    776 		LIST_INIT(&p->msghead);
    777 
    778 		p->setId = setId;
    779 
    780 		if (q == NULL) {
    781 			LIST_INSERT_HEAD(&sethead, p, entries);
    782 		} else {
    783 			LIST_INSERT_AFTER(q, p, entries);
    784 		}
    785 	}
    786 
    787 	curSet = p;
    788 }
    789 
    790 static void
    791 MCAddMsg(int msgId, const char *str)
    792 {
    793 	struct _msgT *p, *q;
    794 
    795 	if (!curSet)
    796 		error("can't specify a message when no set exists");
    797 
    798 	if (msgId <= 0) {
    799 		error("msgId's must be greater than zero");
    800 		/* NOTREACHED */
    801 	}
    802 	if (msgId > NL_MSGMAX) {
    803 		error("msgID exceeds limit");
    804 		/* NOTREACHED */
    805 	}
    806 
    807 	p = LIST_FIRST(&curSet->msghead);
    808 	q = NULL;
    809 	for (; p != NULL && p->msgId < msgId; q = p, p = LIST_NEXT(p, entries))
    810 		continue;
    811 
    812 	if (p && p->msgId == msgId) {
    813 		free(p->str);
    814 	} else {
    815 		p = xmalloc(sizeof(struct _msgT));
    816 		memset(p, '\0', sizeof(struct _msgT));
    817 
    818 		if (q == NULL) {
    819 			LIST_INSERT_HEAD(&curSet->msghead, p, entries);
    820 		} else {
    821 			LIST_INSERT_AFTER(q, p, entries);
    822 		}
    823 	}
    824 
    825 	p->msgId = msgId;
    826 	p->str = xstrdup(str);
    827 }
    828 
    829 static void
    830 MCDelSet(int setId)
    831 {
    832 	struct _setT *set;
    833 	struct _msgT *msg;
    834 
    835 	if (setId <= 0) {
    836 		error("setId's must be greater than zero");
    837 		/* NOTREACHED */
    838 	}
    839 	if (setId > NL_SETMAX) {
    840 		error("setId exceeds limit");
    841 		/* NOTREACHED */
    842 	}
    843 
    844 	set = LIST_FIRST(&sethead);
    845 	for (; set != NULL && set->setId < setId; set = LIST_NEXT(set, entries))
    846 		continue;
    847 
    848 	if (set && set->setId == setId) {
    849 		LIST_REMOVE(set, entries);
    850 		while ((msg = LIST_FIRST(&set->msghead)) != NULL) {
    851 			LIST_REMOVE(msg, entries);
    852 			free(msg->str);
    853 			free(msg);
    854 		}
    855 		free(set);
    856 		return;
    857 	}
    858 	warning(NULL, "specified set doesn't exist");
    859 }
    860 
    861 static void
    862 MCDelMsg(int msgId)
    863 {
    864 	struct _msgT *msg;
    865 
    866 	if (!curSet)
    867 		error("you can't delete a message before defining the set");
    868 
    869 	msg = LIST_FIRST(&curSet->msghead);
    870 	for (; msg != NULL && msg->msgId < msgId; msg = LIST_NEXT(msg, entries))
    871 		continue;
    872 
    873 	if (msg && msg->msgId == msgId) {
    874 		LIST_REMOVE(msg, entries);
    875 		free(msg->str);
    876 		free(msg);
    877 		return;
    878 	}
    879 	warning(NULL, "specified msg doesn't exist");
    880 }
    881