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