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