xref: /netbsd-src/usr.bin/gencat/gencat.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: gencat.c,v 1.24 2007/11/21 13:40:09 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  * 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 #if defined(__RCSID) && !defined(lint)
41 __RCSID("$NetBSD: gencat.c,v 1.24 2007/11/21 13:40:09 ginsbach 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@alfalfa.com
73 
74 ******************************************************************/
75 
76 #if HAVE_NBTOOL_CONFIG_H
77 #include "nbtool_config.h"
78 #endif
79 
80 #define _NLS_PRIVATE
81 
82 #include <sys/types.h>
83 #include <sys/queue.h>
84 
85 #include <netinet/in.h>	/* Needed by arpa/inet.h on NetBSD */
86 #include <arpa/inet.h>	/* Needed for htonl() on POSIX systems */
87 
88 #include <ctype.h>
89 #include <err.h>
90 #include <errno.h>
91 #include <fcntl.h>
92 #include <limits.h>
93 #include <nl_types.h>
94 #include <stdio.h>
95 #include <stdlib.h>
96 #include <string.h>
97 #include <unistd.h>
98 
99 #ifndef NL_SETMAX
100 #define NL_SETMAX 255
101 #endif
102 #ifndef NL_MSGMAX
103 #define NL_MSGMAX 2048
104 #endif
105 
106 struct _msgT {
107 	long    msgId;
108 	char   *str;
109         LIST_ENTRY(_msgT) entries;
110 };
111 
112 struct _setT {
113 	long    setId;
114         LIST_HEAD(msghead, _msgT) msghead;
115         LIST_ENTRY(_setT) entries;
116 };
117 
118 LIST_HEAD(sethead, _setT) sethead;
119 static struct _setT *curSet;
120 
121 static const char *curfile;
122 static char *curline = NULL;
123 static long lineno = 0;
124 
125 static	char   *cskip(char *);
126 static	void	error(const char *);
127 static	char   *getline(int);
128 static	char   *getmsg(int, char *, char);
129 static	void	warning(const char *, const char *);
130 static	char   *wskip(char *);
131 static	char   *xstrdup(const char *);
132 static	void   *xmalloc(size_t);
133 static	void   *xrealloc(void *, size_t);
134 
135 void	MCParse(int fd);
136 void	MCReadCat(int fd);
137 void	MCWriteCat(int fd);
138 void	MCDelMsg(int msgId);
139 void	MCAddMsg(int msgId, const char *msg);
140 void	MCAddSet(int setId);
141 void	MCDelSet(int setId);
142 int	main(int, char **);
143 void	usage(void);
144 
145 #define CORRUPT			"corrupt message catalog"
146 #define NOMEMORY		"out of memory"
147 
148 void
149 usage(void)
150 {
151 	fprintf(stderr, "usage: %s catfile msgfile ...\n", getprogname());
152 	exit(1);
153 }
154 
155 int
156 main(int argc, char *argv[])
157 {
158 	int     ofd, ifd;
159 	char   *catfile = NULL;
160 	int     c;
161 	int	updatecat = 0;
162 
163 	while ((c = getopt(argc, argv, "")) != -1) {
164 		switch (c) {
165 		case '?':
166 		default:
167 			usage();
168 			/* NOTREACHED */
169 		}
170 	}
171 	argc -= optind;
172 	argv += optind;
173 
174 	if (argc < 2) {
175 		usage();
176 		/* NOTREACHED */
177 	}
178 	catfile = *argv++;
179 
180 	if ((catfile[0] == '-') && (catfile[1] == '\0')) {
181 		ofd = STDOUT_FILENO;
182 	} else {
183 		ofd = open(catfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
184 		if (ofd < 0) {
185 			if (errno == EEXIST) {
186 				if ((ofd = open(catfile, O_RDWR)) < 0) {
187 					err(1, "Unable to open %s", catfile);
188 					/* NOTREACHED */
189 				}
190 			} else {
191 				err(1, "Unable to create new %s", catfile);
192 				/* NOTREACHED */
193 			}
194 			curfile = catfile;
195 			updatecat = 1;
196 			MCReadCat(ofd);
197 			if (lseek(ofd, SEEK_SET, 0) < 0) {
198 				err(1, "Unable to seek on %s", catfile);
199 				/* NOTREACHED */
200 			}
201 		}
202 	}
203 
204 	if (((*argv)[0] == '-') && ((*argv)[1] == '\0')) {
205 		if (argc != 2)
206 			usage();
207 			/* NOTREACHED */
208 		MCParse(STDIN_FILENO);
209 	} else {
210 		for (; *argv; argv++) {
211 			if ((ifd = open(*argv, O_RDONLY)) < 0)
212 				err(1, "Unable to read %s", *argv);
213 			curfile = *argv;
214 			lineno = 0;
215 			MCParse(ifd);
216 			close(ifd);
217 		}
218 	}
219 
220 	if (updatecat) {
221 		if (ftruncate(ofd, 0) != 0) {
222 			err(1, "Unable to truncate %s", catfile);
223 			/* NOTREACHED */
224 		}
225 	}
226 
227 	MCWriteCat(ofd);
228 	exit(0);
229 }
230 
231 static void
232 warning(const char *cptr, const char *msg)
233 {
234 	if (lineno) {
235 		fprintf(stderr, "%s: %s on line %ld, %s\n",
236 			getprogname(), msg, lineno, curfile);
237 		fprintf(stderr, "%s\n", curline);
238 		if (cptr) {
239 			char   *tptr;
240 			for (tptr = curline; tptr < cptr; ++tptr)
241 				putc(' ', stderr);
242 			fprintf(stderr, "^\n");
243 		}
244 	} else {
245 		fprintf(stderr, "%s: %s, %s\n", getprogname(), msg, curfile);
246 	}
247 }
248 
249 static void
250 error(const char *msg)
251 {
252 	warning(NULL, msg);
253 	exit(1);
254 }
255 
256 static void *
257 xmalloc(size_t len)
258 {
259 	void   *p;
260 
261 	if ((p = malloc(len)) == NULL)
262 		errx(1, NOMEMORY);
263 	return (p);
264 }
265 
266 static void *
267 xrealloc(void *ptr, size_t size)
268 {
269 	if ((ptr = realloc(ptr, size)) == NULL)
270 		errx(1, NOMEMORY);
271 	return (ptr);
272 }
273 
274 static char *
275 xstrdup(const char *str)
276 {
277 	char *nstr;
278 
279 	if ((nstr = strdup(str)) == NULL)
280 		errx(1, NOMEMORY);
281 	return (nstr);
282 }
283 
284 static char *
285 getline(int fd)
286 {
287 	static long curlen = BUFSIZ;
288 	static char buf[BUFSIZ], *bptr = buf, *bend = buf;
289 	char   *cptr, *cend;
290 	long    buflen;
291 
292 	if (!curline) {
293 		curline = xmalloc(curlen);
294 	}
295 	++lineno;
296 
297 	cptr = curline;
298 	cend = curline + curlen;
299 	for (;;) {
300 		for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
301 			if (*bptr == '\n') {
302 				*cptr = '\0';
303 				++bptr;
304 				return (curline);
305 			} else
306 				*cptr = *bptr;
307 		}
308 		if (cptr == cend) {
309 			cptr = curline = xrealloc(curline, curlen *= 2);
310 			cend = curline + curlen;
311 		}
312 		if (bptr == bend) {
313 			buflen = read(fd, buf, BUFSIZ);
314 			if (buflen <= 0) {
315 				if (cptr > curline) {
316 					*cptr = '\0';
317 					return (curline);
318 				}
319 				return (NULL);
320 			}
321 			bend = buf + buflen;
322 			bptr = buf;
323 		}
324 	}
325 }
326 
327 static char *
328 wskip(char *cptr)
329 {
330 	if (!*cptr || !isspace((unsigned char) *cptr)) {
331 		warning(cptr, "expected a space");
332 		return (cptr);
333 	}
334 	while (*cptr && isspace((unsigned char) *cptr))
335 		++cptr;
336 	return (cptr);
337 }
338 
339 static char *
340 cskip(char *cptr)
341 {
342 	if (!*cptr || isspace((unsigned char) *cptr)) {
343 		warning(cptr, "wasn't expecting a space");
344 		return (cptr);
345 	}
346 	while (*cptr && !isspace((unsigned char) *cptr))
347 		++cptr;
348 	return (cptr);
349 }
350 
351 static char *
352 getmsg(int fd, char *cptr, char quote)
353 {
354 	static char *msg = NULL;
355 	static long msglen = 0;
356 	long    clen, i;
357 	char   *tptr;
358 
359 	if (quote && *cptr == quote) {
360 		++cptr;
361 	}
362 
363 	clen = strlen(cptr) + 1;
364 	if (clen > msglen) {
365 		if (msglen)
366 			msg = xrealloc(msg, clen);
367 		else
368 			msg = xmalloc(clen);
369 		msglen = clen;
370 	}
371 	tptr = msg;
372 
373 	while (*cptr) {
374 		if (quote && *cptr == quote) {
375 			char   *tmp;
376 			tmp = cptr + 1;
377 			if (*tmp && (!isspace((unsigned char) *tmp) || *wskip(tmp))) {
378 				warning(cptr, "unexpected quote character, ignoring");
379 				*tptr++ = *cptr++;
380 			} else {
381 				*cptr = '\0';
382 			}
383 		} else
384 			if (*cptr == '\\') {
385 				++cptr;
386 				switch (*cptr) {
387 				case '\0':
388 					cptr = getline(fd);
389 					if (!cptr)
390 						error("premature end of file");
391 					msglen += strlen(cptr);
392 					i = tptr - msg;
393 					msg = xrealloc(msg, msglen);
394 					tptr = msg + i;
395 					break;
396 				case 'n':
397 					*tptr++ = '\n';
398 					++cptr;
399 					break;
400 				case 't':
401 					*tptr++ = '\t';
402 					++cptr;
403 					break;
404 				case 'v':
405 					*tptr++ = '\v';
406 					++cptr;
407 					break;
408 				case 'b':
409 					*tptr++ = '\b';
410 					++cptr;
411 					break;
412 				case 'r':
413 					*tptr++ = '\r';
414 					++cptr;
415 					break;
416 				case 'f':
417 					*tptr++ = '\f';
418 					++cptr;
419 					break;
420 				case '\\':
421 					*tptr++ = '\\';
422 					++cptr;
423 					break;
424 				default:
425 					if (quote && *cptr == quote) {
426 						*tptr++ = *cptr++;
427 					} else if (isdigit((unsigned char) *cptr)) {
428 						*tptr = 0;
429 						for (i = 0; i < 3; ++i) {
430 							if (!isdigit((unsigned char) *cptr))
431 								break;
432 							if (*cptr > '7')
433 								warning(cptr, "octal number greater than 7?!");
434 							*tptr *= 8;
435 							*tptr += (*cptr - '0');
436 							++cptr;
437 						}
438 					} else {
439 						warning(cptr, "unrecognized escape sequence");
440 					}
441 					break;
442 				}
443 			} else {
444 				*tptr++ = *cptr++;
445 			}
446 	}
447 	*tptr = '\0';
448 	return (msg);
449 }
450 
451 void
452 MCParse(int fd)
453 {
454 	char   *cptr, *str;
455 	int	msgid = 0;
456 	int     setid = 0;
457 	char    quote = 0;
458 
459 	/* XXX: init sethead? */
460 
461 	while ((cptr = getline(fd))) {
462 		if (*cptr == '$') {
463 			++cptr;
464 			if (strncmp(cptr, "set", 3) == 0) {
465 				cptr += 3;
466 				cptr = wskip(cptr);
467 				setid = atoi(cptr);
468 				MCAddSet(setid);
469 				msgid = 0;
470 			} else if (strncmp(cptr, "delset", 6) == 0) {
471 				cptr += 6;
472 				cptr = wskip(cptr);
473 				setid = atoi(cptr);
474 				MCDelSet(setid);
475 			} else if (strncmp(cptr, "quote", 5) == 0) {
476 				cptr += 5;
477 				if (!*cptr)
478 					quote = 0;
479 				else {
480 					cptr = wskip(cptr);
481 					if (!*cptr)
482 						quote = 0;
483 					else
484 						quote = *cptr;
485 				}
486 			} else if (isspace((unsigned char) *cptr)) {
487 				;
488 			} else {
489 				if (*cptr) {
490 					cptr = wskip(cptr);
491 					if (*cptr)
492 						warning(cptr, "unrecognized line");
493 				}
494 			}
495 		} else {
496 			/*
497 			 * First check for (and eat) empty lines....
498 			 */
499 			if (!*cptr)
500 				continue;
501 			/*
502 			 * We have a digit? Start of a message. Else,
503 			 * syntax error.
504 			 */
505 			if (isdigit((unsigned char) *cptr)) {
506 				msgid = atoi(cptr);
507 				cptr = cskip(cptr);
508 				if (*cptr)
509 					cptr = wskip(cptr);
510 				/* if (*cptr) ++cptr; */
511 			} else {
512 				warning(cptr, "neither blank line nor start of a message id");
513 				continue;
514 			}
515 			/*
516 			 * If no set directive specified, all messages
517 			 * shall be in default message set NL_SETD.
518 			 */
519 			if (setid == 0) {
520 				setid = NL_SETD;
521 				MCAddSet(setid);
522 			}
523 			/*
524 			 * If we have a message ID, but no message,
525 			 * then this means "delete this message id
526 			 * from the catalog".
527 			 */
528 			if (!*cptr) {
529 				MCDelMsg(msgid);
530 			} else {
531 				str = getmsg(fd, cptr, quote);
532 				MCAddMsg(msgid, str);
533 			}
534 		}
535 	}
536 }
537 
538 void
539 MCReadCat(int fd)
540 {
541 	void   *msgcat;		/* message catalog data */
542 	struct _nls_cat_hdr cat_hdr;
543 	struct _nls_set_hdr *set_hdr;
544 	struct _nls_msg_hdr *msg_hdr;
545 	char   *strings;
546 	int	m, n, s;
547 	int	msgno, setno;
548 
549 	/* XXX init sethead? */
550 
551 	n = read(fd, &cat_hdr, sizeof(cat_hdr));
552 	if (n < sizeof(cat_hdr)) {
553 		if (n == 0)
554 			return;		/* empty file */
555 		else if (n == -1)
556 			err(1, "header read");
557 		else
558 			errx(1, CORRUPT);
559 	}
560 	if (ntohl(cat_hdr.__magic) != _NLS_MAGIC)
561 		errx(1, "%s: bad magic number (%#x)", CORRUPT, cat_hdr.__magic);
562 
563 	cat_hdr.__mem = ntohl(cat_hdr.__mem);
564 	msgcat = xmalloc(cat_hdr.__mem);
565 
566 	cat_hdr.__nsets = ntohl(cat_hdr.__nsets);
567 	cat_hdr.__msg_hdr_offset = ntohl(cat_hdr.__msg_hdr_offset);
568 	cat_hdr.__msg_txt_offset = ntohl(cat_hdr.__msg_txt_offset);
569 	if ((cat_hdr.__mem < 0) ||
570 	    (cat_hdr.__msg_hdr_offset < 0) ||
571 	    (cat_hdr.__msg_txt_offset < 0) ||
572 	    (cat_hdr.__mem < (cat_hdr.__nsets * sizeof(struct _nls_set_hdr))) ||
573 	    (cat_hdr.__mem < cat_hdr.__msg_hdr_offset) ||
574 	    (cat_hdr.__mem < cat_hdr.__msg_txt_offset))
575 		errx(1, "%s: catalog header", CORRUPT);
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 			int     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