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