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