xref: /netbsd-src/usr.bin/gencat/gencat.c (revision 274254cdae52594c1aa480a736aef78313d15c9c)
1 /*	$NetBSD: gencat.c,v 1.27 2009/02/18 20:04:43 christos 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.27 2009/02/18 20:04:43 christos 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   *getline(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 getline(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 long 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 = getline(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 = getline(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 	int	m, n, s;
545 	int	msgno, setno;
546 
547 	/* XXX init sethead? */
548 
549 	n = read(fd, &cat_hdr, sizeof(cat_hdr));
550 	if (n < sizeof(cat_hdr)) {
551 		if (n == 0)
552 			return;		/* empty file */
553 		else if (n == -1)
554 			err(1, "header read");
555 		else
556 			errx(1, CORRUPT);
557 	}
558 	if (ntohl(cat_hdr.__magic) != _NLS_MAGIC)
559 		errx(1, "%s: bad magic number (%#x)", CORRUPT, cat_hdr.__magic);
560 
561 	cat_hdr.__mem = ntohl(cat_hdr.__mem);
562 
563 	cat_hdr.__nsets = ntohl(cat_hdr.__nsets);
564 	cat_hdr.__msg_hdr_offset = ntohl(cat_hdr.__msg_hdr_offset);
565 	cat_hdr.__msg_txt_offset = ntohl(cat_hdr.__msg_txt_offset);
566 	if ((cat_hdr.__mem < 0) ||
567 	    (cat_hdr.__msg_hdr_offset < 0) ||
568 	    (cat_hdr.__msg_txt_offset < 0) ||
569 	    (cat_hdr.__mem < (cat_hdr.__nsets * sizeof(struct _nls_set_hdr))) ||
570 	    (cat_hdr.__mem < cat_hdr.__msg_hdr_offset) ||
571 	    (cat_hdr.__mem < cat_hdr.__msg_txt_offset))
572 		errx(1, "%s: catalog header", CORRUPT);
573 
574 	msgcat = xmalloc(cat_hdr.__mem);
575 
576 	n = read(fd, msgcat, cat_hdr.__mem);
577 	if (n < cat_hdr.__mem) {
578 		if (n == -1)
579 			err(1, "data read");
580 		else
581 			errx(1, CORRUPT);
582 	}
583 
584 	set_hdr = (struct _nls_set_hdr *)msgcat;
585 	msg_hdr = (struct _nls_msg_hdr *)((char *)msgcat +
586 	    cat_hdr.__msg_hdr_offset);
587 	strings = (char *)msgcat + cat_hdr.__msg_txt_offset;
588 
589 	setno = 0;
590 	for (s = 0; s < cat_hdr.__nsets; s++, set_hdr++) {
591 		set_hdr->__setno = ntohl(set_hdr->__setno);
592 		if (set_hdr->__setno < setno)
593 			errx(1, "%s: bad set number (%d)",
594 		       	     CORRUPT, set_hdr->__setno);
595 		setno = set_hdr->__setno;
596 
597 		MCAddSet(setno);
598 
599 		set_hdr->__nmsgs = ntohl(set_hdr->__nmsgs);
600 		set_hdr->__index = ntohl(set_hdr->__index);
601 		if (set_hdr->__nmsgs < 0 || set_hdr->__index < 0)
602 			errx(1, "%s: set header", CORRUPT);
603 
604 		/* Get the data */
605 		msgno = 0;
606 		for (m = 0; m < set_hdr->__nmsgs; m++, msg_hdr++) {
607 			msg_hdr->__msgno = ntohl(msg_hdr->__msgno);
608 			msg_hdr->__offset = ntohl(msg_hdr->__offset);
609 			if (msg_hdr->__msgno < msgno)
610 				errx(1, "%s: bad message number (%d)",
611 				     CORRUPT, msg_hdr->__msgno);
612 		        if ((msg_hdr->__offset < 0) ||
613 			    ((strings + msg_hdr->__offset) >
614 			     ((char *)msgcat + cat_hdr.__mem)))
615 				errx(1, "%s: message header", CORRUPT);
616 
617 			msgno = msg_hdr->__msgno;
618 			MCAddMsg(msgno, strings + msg_hdr->__offset);
619 		}
620 	}
621 	free(msgcat);
622 }
623 
624 /*
625  * Write message catalog.
626  *
627  * The message catalog is first converted from its internal to its
628  * external representation in a chunk of memory allocated for this
629  * purpose.  Then the completed catalog is written.  This approach
630  * avoids additional housekeeping variables and/or a lot of seeks
631  * that would otherwise be required.
632  */
633 void
634 MCWriteCat(int fd)
635 {
636 	int     nsets;		/* number of sets */
637 	int     nmsgs;		/* number of msgs */
638 	int     string_size;	/* total size of string pool */
639 	int     msgcat_size;	/* total size of message catalog */
640 	void   *msgcat;		/* message catalog data */
641 	struct _nls_cat_hdr *cat_hdr;
642 	struct _nls_set_hdr *set_hdr;
643 	struct _nls_msg_hdr *msg_hdr;
644 	char   *strings;
645 	struct _setT *set;
646 	struct _msgT *msg;
647 	int     msg_index;
648 	int     msg_offset;
649 
650 	/* determine number of sets, number of messages, and size of the
651 	 * string pool */
652 	nsets = 0;
653 	nmsgs = 0;
654 	string_size = 0;
655 
656 	for (set = sethead.lh_first; set != NULL;
657 	    set = set->entries.le_next) {
658 		nsets++;
659 
660 		for (msg = set->msghead.lh_first; msg != NULL;
661 		    msg = msg->entries.le_next) {
662 			nmsgs++;
663 			string_size += strlen(msg->str) + 1;
664 		}
665 	}
666 
667 #ifdef DEBUG
668 	printf("number of sets: %d\n", nsets);
669 	printf("number of msgs: %d\n", nmsgs);
670 	printf("string pool size: %d\n", string_size);
671 #endif
672 
673 	/* determine size and then allocate buffer for constructing external
674 	 * message catalog representation */
675 	msgcat_size = sizeof(struct _nls_cat_hdr)
676 	    + (nsets * sizeof(struct _nls_set_hdr))
677 	    + (nmsgs * sizeof(struct _nls_msg_hdr))
678 	    + string_size;
679 
680 	msgcat = xmalloc(msgcat_size);
681 	memset(msgcat, '\0', msgcat_size);
682 
683 	/* fill in msg catalog header */
684 	cat_hdr = (struct _nls_cat_hdr *) msgcat;
685 	cat_hdr->__magic = htonl(_NLS_MAGIC);
686 	cat_hdr->__nsets = htonl(nsets);
687 	cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
688 	cat_hdr->__msg_hdr_offset =
689 	    htonl(nsets * sizeof(struct _nls_set_hdr));
690 	cat_hdr->__msg_txt_offset =
691 	    htonl(nsets * sizeof(struct _nls_set_hdr) +
692 	    nmsgs * sizeof(struct _nls_msg_hdr));
693 
694 	/* compute offsets for set & msg header tables and string pool */
695 	set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
696 	    sizeof(struct _nls_cat_hdr));
697 	msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
698 	    sizeof(struct _nls_cat_hdr) +
699 	    nsets * sizeof(struct _nls_set_hdr));
700 	strings = (char *) msgcat +
701 	    sizeof(struct _nls_cat_hdr) +
702 	    nsets * sizeof(struct _nls_set_hdr) +
703 	    nmsgs * sizeof(struct _nls_msg_hdr);
704 
705 	msg_index = 0;
706 	msg_offset = 0;
707 	for (set = sethead.lh_first; set != NULL;
708 	    set = set->entries.le_next) {
709 
710 		nmsgs = 0;
711 		for (msg = set->msghead.lh_first; msg != NULL;
712 		    msg = msg->entries.le_next) {
713 			int     msg_len = strlen(msg->str) + 1;
714 
715 			msg_hdr->__msgno = htonl(msg->msgId);
716 			msg_hdr->__msglen = htonl(msg_len);
717 			msg_hdr->__offset = htonl(msg_offset);
718 
719 			memcpy(strings, msg->str, msg_len);
720 			strings += msg_len;
721 			msg_offset += msg_len;
722 
723 			nmsgs++;
724 			msg_hdr++;
725 		}
726 
727 		set_hdr->__setno = htonl(set->setId);
728 		set_hdr->__nmsgs = htonl(nmsgs);
729 		set_hdr->__index = htonl(msg_index);
730 		msg_index += nmsgs;
731 		set_hdr++;
732 	}
733 
734 	/* write out catalog.  XXX: should this be done in small chunks? */
735 	write(fd, msgcat, msgcat_size);
736 }
737 
738 void
739 MCAddSet(int setId)
740 {
741 	struct _setT *p, *q;
742 
743 	if (setId <= 0) {
744 		error("setId's must be greater than zero");
745 		/* NOTREACHED */
746 	}
747 	if (setId > NL_SETMAX) {
748 		error("setId exceeds limit");
749 		/* NOTREACHED */
750 	}
751 
752 	p = sethead.lh_first;
753 	q = NULL;
754 	for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next);
755 
756 	if (p && p->setId == setId) {
757 		;
758 	} else {
759 		p = xmalloc(sizeof(struct _setT));
760 		memset(p, '\0', sizeof(struct _setT));
761 		LIST_INIT(&p->msghead);
762 
763 		p->setId = setId;
764 
765 		if (q == NULL) {
766 			LIST_INSERT_HEAD(&sethead, p, entries);
767 		} else {
768 			LIST_INSERT_AFTER(q, p, entries);
769 		}
770 	}
771 
772 	curSet = p;
773 }
774 
775 void
776 MCAddMsg(int msgId, const char *str)
777 {
778 	struct _msgT *p, *q;
779 
780 	if (!curSet)
781 		error("can't specify a message when no set exists");
782 
783 	if (msgId <= 0) {
784 		error("msgId's must be greater than zero");
785 		/* NOTREACHED */
786 	}
787 	if (msgId > NL_MSGMAX) {
788 		error("msgID exceeds limit");
789 		/* NOTREACHED */
790 	}
791 
792 	p = curSet->msghead.lh_first;
793 	q = NULL;
794 	for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next);
795 
796 	if (p && p->msgId == msgId) {
797 		free(p->str);
798 	} else {
799 		p = xmalloc(sizeof(struct _msgT));
800 		memset(p, '\0', sizeof(struct _msgT));
801 
802 		if (q == NULL) {
803 			LIST_INSERT_HEAD(&curSet->msghead, p, entries);
804 		} else {
805 			LIST_INSERT_AFTER(q, p, entries);
806 		}
807 	}
808 
809 	p->msgId = msgId;
810 	p->str = xstrdup(str);
811 }
812 
813 void
814 MCDelSet(int setId)
815 {
816 	struct _setT *set;
817 	struct _msgT *msg;
818 
819 	if (setId <= 0) {
820 		error("setId's must be greater than zero");
821 		/* NOTREACHED */
822 	}
823 	if (setId > NL_SETMAX) {
824 		error("setId exceeds limit");
825 		/* NOTREACHED */
826 	}
827 
828 	set = sethead.lh_first;
829 	for (; set != NULL && set->setId < setId; set = set->entries.le_next);
830 
831 	if (set && set->setId == setId) {
832 		LIST_REMOVE(set, entries);
833 		while ((msg = set->msghead.lh_first) != NULL) {
834 			LIST_REMOVE(msg, entries);
835 			free(msg->str);
836 			free(msg);
837 		}
838 		free(set);
839 		return;
840 	}
841 	warning(NULL, "specified set doesn't exist");
842 }
843 
844 void
845 MCDelMsg(int msgId)
846 {
847 	struct _msgT *msg;
848 
849 	if (!curSet)
850 		error("you can't delete a message before defining the set");
851 
852 	msg = curSet->msghead.lh_first;
853 	for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next);
854 
855 	if (msg && msg->msgId == msgId) {
856 		LIST_REMOVE(msg, entries);
857 		free(msg->str);
858 		free(msg);
859 		return;
860 	}
861 	warning(NULL, "specified msg doesn't exist");
862 }
863