Complete Citrus import. Import message catalog implement from
[dragonfly.git] / usr.bin / gencat / gencat.c
index afda5ae..99ae51c 100644 (file)
@@ -1,5 +1,41 @@
-/* $FreeBSD: src/usr.bin/gencat/gencat.c,v 1.5.2.1 2001/12/17 12:09:35 phantom Exp $ */
-/* $DragonFly: src/usr.bin/gencat/gencat.c,v 1.2 2003/06/17 04:29:27 dillon Exp $ */
+/*     $NetBSD: src/usr.bin/gencat/gencat.c,v 1.19 2004/01/05 23:23:34 jmmv Exp $      */
+/*     $DragonFly: src/usr.bin/gencat/gencat.c,v 1.3 2005/04/21 16:36:35 joerg Exp $ */
+
+/*
+ * Copyright (c) 1996 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by J.T. Conklin.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *        This product includes software developed by the NetBSD 
+ *       Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its 
+ *    contributors may be used to endorse or promote products derived 
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
 
 /***********************************************************
 Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
@@ -34,167 +70,752 @@ up-to-date.  Many thanks.
 ******************************************************************/
 
 #include <sys/types.h>
-#include <sys/file.h>
-#include <sys/stat.h>
+#include <sys/queue.h>
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
 #include <err.h>
+#include <fcntl.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include "gencat.h"
 
-/*
- * The spec says the syntax is "gencat catfile msgfile...".
- * We extend it to:
- *     gencat [-lang C|C++|ANSIC] catfile msgfile [-h <header-file>]...
- * Flags are order dependant, we'll take whatever lang was most recently chosen
- * and use it to generate the next header file.  The header files are generated
- * at the point in the command line they are listed.  Thus the sequence:
- *     gencat -lang C foo.cat foo.mcs -h foo.h -lang C++ bar.mcs -h bar.H
- * will put constants from foo.mcs into foo.h and constants from bar.mcs into
- * bar.h.  Constants are not saved in the catalog file, so nothing will come
- * from that, even if things have been defined before.  The constants in foo.h
- * will be in C syntax, in bar.H in C++ syntax.
- */
+#define _NLS_PRIVATE
+#include <nl_types.h>
 
-static void writeIfChanged(char *, int, int);
+#ifndef NL_SETMAX
+#define NL_SETMAX 255
+#endif
+#ifndef NL_MSGMAX
+#define NL_MSGMAX 2048
+#endif
 
-static void
-usage(void)
+struct _msgT {
+       long    msgId;
+       char   *str;
+        LIST_ENTRY(_msgT) entries;
+};
+
+struct _setT {
+       long    setId;
+        LIST_HEAD(msghead, _msgT) msghead;
+        LIST_ENTRY(_setT) entries;
+};
+
+LIST_HEAD(sethead, _setT) sethead;
+static struct _setT *curSet;
+
+static char *curline = NULL;
+static long lineno = 0;
+
+static char    *cskip(char *);
+static void     error(char *, char *);
+static void     nomem(void);
+static char    *getline(int);
+static char    *getmsg(int, char *, char);
+static void     warning(char *, char *);
+static char    *wskip(char *);
+static char    *xstrdup(const char *);
+static void    *xmalloc(size_t);
+static void    *xrealloc(void *, size_t);
+
+void   MCParse __P((int fd));
+void   MCReadCat __P((int fd));
+void   MCWriteCat __P((int fd));
+void   MCDelMsg __P((int msgId));
+void   MCAddMsg __P((int msgId, const char *msg));
+void   MCAddSet __P((int setId));
+void   MCDelSet __P((int setId));
+int    main __P((int, char **));
+void   usage __P((void));
+
+
+void
+usage()
 {
-    fprintf(stderr, "usage: gencat [-new] [-or] [-lang C|C++|ANSIC]\n"
-                    "              catfile msgfile [-h <header-file>]...\n");
-    exit(1);
+       fprintf(stderr, "usage: %s catfile msgfile ...\n", getprogname());
+       exit(1);
 }
 
 int
-main(int argc, char *argv[])
-{
-    int                ofd, ifd, i;
-    char       *catfile = NULL;
-    char       *input = NULL;
-    int                lang = MCLangC;
-    int                new = FALSE;
-    int                orConsts = FALSE;
-
-    for (i = 1; i < argc; ++i) {
-       if (argv[i][0] == '-') {
-           if (strcmp(argv[i], "-lang") == 0) {
-               ++i;
-               if (strcmp(argv[i], "C") == 0) lang = MCLangC;
-               else if (strcmp(argv[i], "C++") == 0) lang = MCLangCPlusPlus;
-               else if (strcmp(argv[i], "ANSIC") == 0) lang = MCLangANSIC;
-               else {
-                   errx(1, "unrecognized language: %s", argv[i]);
+main(argc, argv)
+       int     argc;
+       char   *argv[];
+{
+       int     ofd, ifd;
+       char   *catfile = NULL;
+       int     c;
+
+       while ((c = getopt(argc, argv, "")) != -1) {
+               switch (c) {
+               case '?':
+               default:
+                       usage();
+                       /* NOTREACHED */
                }
-           } else if (strcmp(argv[i], "-h") == 0) {
-               if (!input)
-                   errx(1, "can't write to a header before reading something");
-               ++i;
-               writeIfChanged(argv[i], lang, orConsts);
-           } else if (strcmp(argv[i], "-new") == 0) {
-               if (catfile)
-                   errx(1, "you must specify -new before the catalog file name");
-               new = TRUE;
-           } else if (strcmp(argv[i], "-or") == 0) {
-               orConsts = ~orConsts;
-           } else {
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (argc < 2) {
                usage();
-           }
-        } else {
-           if (!catfile) {
-               catfile = argv[i];
-               if (new) {
-                   if ((ofd = open(catfile, O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0)
-                               errx(1, "unable to create a new %s", catfile);
-               } else if ((ofd = open(catfile, O_RDONLY)) < 0) {
-                   if ((ofd = open(catfile, O_WRONLY|O_CREAT, 0666)) < 0)
-                               errx(1, "unable to create %s", catfile);
-               } else {
-                   MCReadCat(ofd);
-                   close(ofd);
-                   if ((ofd = open(catfile, O_WRONLY|O_TRUNC)) < 0)
-                               errx(1, "unable to truncate %s", catfile);
-               }
-           } else {
-               input = argv[i];
-               if ((ifd = open(input, O_RDONLY)) < 0)
-                   errx(1, "unable to read %s", input);
+               /* NOTREACHED */
+       }
+       catfile = *argv++;
+
+       for (; *argv; argv++) {
+               if ((ifd = open(*argv, O_RDONLY)) < 0)
+                       err(1, "Unable to read %s", *argv);
                MCParse(ifd);
                close(ifd);
-           }
        }
-    }
-    if (catfile) {
+
+       if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
+               err(1, "Unable to create a new %s", catfile);
        MCWriteCat(ofd);
        exit(0);
-    } else {
-       usage();
-    }
-    return 0;
 }
 
 static void
-writeIfChanged(char *fname, int lang, int orConsts)
-{
-    char       tmpname[32];
-    char       buf[BUFSIZ], tbuf[BUFSIZ], *cptr, *tptr;
-    int                fd, tfd;
-    int                diff = FALSE;
-    int                len, tlen;
-    struct stat        sbuf;
-
-    /* If it doesn't exist, just create it */
-    if (stat(fname, &sbuf)) {
-       if ((fd = open(fname, O_WRONLY|O_CREAT, 0666)) < 0)
-           errx(1, "unable to create header file %s", fname);
-       MCWriteConst(fd, lang, orConsts);
-       close(fd);
-       return;
-    }
-
-    /* If it does exist, create a temp file for now */
-    sprintf(tmpname, "/tmp/gencat.%d", (int) getpid());
-    if ((tfd = open(tmpname, O_RDWR|O_CREAT, 0666)) < 0)
-               errx(1, "unable to open temporary file: %s", tmpname);
-    unlink(tmpname);
-
-    /* Write to the temp file and rewind */
-    MCWriteConst(tfd, lang, orConsts);
-
-    /* Open the real header file */
-    if ((fd = open(fname, O_RDONLY)) < 0)
-               errx(1, "unable to read header file: %s", fname);
-
-    /* Backup to the start of the temp file */
-    if (lseek(tfd, (off_t)0, L_SET) < 0)
-               errx(1, "unable to seek in tempfile: %s", tmpname);
-
-    /* Now compare them */
-    while ((tlen = read(tfd, tbuf, BUFSIZ)) > 0) {
-       if ((len = read(fd, buf, BUFSIZ)) != tlen) {
-           diff = TRUE;
-           goto done;
-       }
-       for (cptr = buf, tptr = tbuf; cptr < buf+len; ++cptr, ++tptr) {
-           if (*tptr != *cptr) {
-               diff = TRUE;
-               goto done;
-           }
-       }
-    }
-done:
-    if (diff) {
-       if (lseek(tfd, (off_t)0, L_SET) < 0)
-           errx(1, "unable to seek in tempfile: %s", tmpname);
-       close(fd);
-       if ((fd = open(fname, O_WRONLY|O_TRUNC)) < 0)
-           errx(1, "unable to truncate header file: %s", fname);
-       while ((len = read(tfd, buf, BUFSIZ)) > 0) {
-           if (write(fd, buf, (size_t)len) != len)
-               warnx("error writing to header file: %s", fname);
-       }
-    }
-    close(fd);
-    close(tfd);
+warning(cptr, msg)
+       char   *cptr;
+       char   *msg;
+{
+       fprintf(stderr, "%s: %s on line %ld\n", getprogname(), msg, lineno);
+       fprintf(stderr, "%s\n", curline);
+       if (cptr) {
+               char   *tptr;
+               for (tptr = curline; tptr < cptr; ++tptr)
+                       putc(' ', stderr);
+               fprintf(stderr, "^\n");
+       }
+}
+
+static void
+error(cptr, msg)
+       char   *cptr;
+       char   *msg;
+{
+       warning(cptr, msg);
+       exit(1);
+}
+
+#if 0  /* XXX unused */
+static void
+corrupt()
+{
+       error(NULL, "corrupt message catalog");
+}
+#endif
+
+static void
+nomem()
+{
+       error(NULL, "out of memory");
+}
+
+static void *
+xmalloc(len)
+       size_t  len;
+{
+       void   *p;
+
+       if ((p = malloc(len)) == NULL)
+               nomem();
+       return (p);
+}
+
+static void *
+xrealloc(ptr, size)
+       void   *ptr;
+       size_t  size;
+{
+       if ((ptr = realloc(ptr, size)) == NULL)
+               nomem();
+       return (ptr);
+}
+
+static char *
+xstrdup(str)
+       const char   *str;
+{
+       char *nstr;
+
+       if ((nstr = strdup(str)) == NULL)
+               nomem();
+       return (nstr);
+}
+
+static char *
+getline(fd)
+       int     fd;
+{
+       static long curlen = BUFSIZ;
+       static char buf[BUFSIZ], *bptr = buf, *bend = buf;
+       char   *cptr, *cend;
+       long    buflen;
+
+       if (!curline) {
+               curline = xmalloc(curlen);
+       }
+       ++lineno;
+
+       cptr = curline;
+       cend = curline + curlen;
+       for (;;) {
+               for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
+                       if (*bptr == '\n') {
+                               *cptr = '\0';
+                               ++bptr;
+                               return (curline);
+                       } else
+                               *cptr = *bptr;
+               }
+               if (cptr == cend) {
+                       cptr = curline = xrealloc(curline, curlen *= 2);
+                       cend = curline + curlen;
+               }
+               if (bptr == bend) {
+                       buflen = read(fd, buf, BUFSIZ);
+                       if (buflen <= 0) {
+                               if (cptr > curline) {
+                                       *cptr = '\0';
+                                       return (curline);
+                               }
+                               return (NULL);
+                       }
+                       bend = buf + buflen;
+                       bptr = buf;
+               }
+       }
+}
+
+static char *
+wskip(cptr)
+       char   *cptr;
+{
+       if (!*cptr || !isspace((unsigned char) *cptr)) {
+               warning(cptr, "expected a space");
+               return (cptr);
+       }
+       while (*cptr && isspace((unsigned char) *cptr))
+               ++cptr;
+       return (cptr);
+}
+
+static char *
+cskip(cptr)
+       char   *cptr;
+{
+       if (!*cptr || isspace((unsigned char) *cptr)) {
+               warning(cptr, "wasn't expecting a space");
+               return (cptr);
+       }
+       while (*cptr && !isspace((unsigned char) *cptr))
+               ++cptr;
+       return (cptr);
+}
+
+static char *
+getmsg(fd, cptr, quote)
+       int     fd;
+       char   *cptr;
+       char    quote;
+{
+       static char *msg = NULL;
+       static long msglen = 0;
+       long    clen, i;
+       char   *tptr;
+
+       if (quote && *cptr == quote) {
+               ++cptr;
+       } 
+
+       clen = strlen(cptr) + 1;
+       if (clen > msglen) {
+               if (msglen)
+                       msg = xrealloc(msg, clen);
+               else
+                       msg = xmalloc(clen);
+               msglen = clen;
+       }
+       tptr = msg;
+
+       while (*cptr) {
+               if (quote && *cptr == quote) {
+                       char   *tmp;
+                       tmp = cptr + 1;
+                       if (*tmp && (!isspace((unsigned char) *tmp) || *wskip(tmp))) {
+                               warning(cptr, "unexpected quote character, ignoring");
+                               *tptr++ = *cptr++;
+                       } else {
+                               *cptr = '\0';
+                       }
+               } else
+                       if (*cptr == '\\') {
+                               ++cptr;
+                               switch (*cptr) {
+                               case '\0':
+                                       cptr = getline(fd);
+                                       if (!cptr)
+                                               error(NULL, "premature end of file");
+                                       msglen += strlen(cptr);
+                                       i = tptr - msg;
+                                       msg = xrealloc(msg, msglen);
+                                       tptr = msg + i;
+                                       break;
+                               case 'n':
+                                       *tptr++ = '\n';
+                                       ++cptr;
+                                       break;
+                               case 't':
+                                       *tptr++ = '\t';
+                                       ++cptr;
+                                       break;
+                               case 'v':
+                                       *tptr++ = '\v';
+                                       ++cptr;
+                                       break;
+                               case 'b':
+                                       *tptr++ = '\b';
+                                       ++cptr;
+                                       break;
+                               case 'r':
+                                       *tptr++ = '\r';
+                                       ++cptr;
+                                       break;
+                               case 'f':
+                                       *tptr++ = '\f';
+                                       ++cptr;
+                                       break;
+                               case '\\':
+                                       *tptr++ = '\\';
+                                       ++cptr;
+                                       break;
+                               default:
+                                       if (quote && *cptr == quote) {
+                                               *tptr++ = *cptr++;
+                                       } else if (isdigit((unsigned char) *cptr)) {
+                                               *tptr = 0;
+                                               for (i = 0; i < 3; ++i) {
+                                                       if (!isdigit((unsigned char) *cptr))
+                                                               break;
+                                                       if (*cptr > '7')
+                                                               warning(cptr, "octal number greater than 7?!");
+                                                       *tptr *= 8;
+                                                       *tptr += (*cptr - '0');
+                                                       ++cptr;
+                                               }
+                                       } else {
+                                               warning(cptr, "unrecognized escape sequence");
+                                       }
+                                       break;
+                               }
+                       } else {
+                               *tptr++ = *cptr++;
+                       }
+       }
+       *tptr = '\0';
+       return (msg);
+}
+
+void
+MCParse(fd)
+       int     fd;
+{
+       char   *cptr, *str;
+       int     setid, msgid = 0;
+       char    quote = 0;
+
+       /* XXX: init sethead? */
+
+       while ((cptr = getline(fd))) {
+               if (*cptr == '$') {
+                       ++cptr;
+                       if (strncmp(cptr, "set", 3) == 0) {
+                               cptr += 3;
+                               cptr = wskip(cptr);
+                               setid = atoi(cptr);
+                               MCAddSet(setid);
+                               msgid = 0;
+                       } else if (strncmp(cptr, "delset", 6) == 0) {
+                               cptr += 6;
+                               cptr = wskip(cptr);
+                               setid = atoi(cptr);
+                               MCDelSet(setid);
+                       } else if (strncmp(cptr, "quote", 5) == 0) {
+                               cptr += 5;
+                               if (!*cptr)
+                                       quote = 0;
+                               else {
+                                       cptr = wskip(cptr);
+                                       if (!*cptr)
+                                               quote = 0;
+                                       else
+                                               quote = *cptr;
+                               }
+                       } else if (isspace((unsigned char) *cptr)) {
+                               ;
+                       } else {
+                               if (*cptr) {
+                                       cptr = wskip(cptr);
+                                       if (*cptr)
+                                               warning(cptr, "unrecognized line");
+                               }
+                       }
+               } else {
+                       /*
+                        * First check for (and eat) empty lines....
+                        */
+                       if (!*cptr)
+                               continue;
+                       /*
+                        * We have a digit? Start of a message. Else,
+                        * syntax error.
+                        */
+                       if (isdigit((unsigned char) *cptr)) {
+                               msgid = atoi(cptr);
+                               cptr = cskip(cptr);
+                               cptr = wskip(cptr);
+                               /* if (*cptr) ++cptr; */
+                       } else {
+                               warning(cptr, "neither blank line nor start of a message id");
+                               continue;
+                       }
+                       /*
+                        * If we have a message ID, but no message,
+                        * then this means "delete this message id
+                        * from the catalog".
+                        */
+                       if (!*cptr) {
+                               MCDelMsg(msgid);
+                       } else {
+                               str = getmsg(fd, cptr, quote);
+                               MCAddMsg(msgid, str);
+                       }
+               }
+       }
+}
+
+void
+MCReadCat(fd)
+       int     fd;
+{
+#if 0
+       MCHeaderT mcHead;
+       MCMsgT  mcMsg;
+       MCSetT  mcSet;
+       msgT   *msg;
+       setT   *set;
+       int     i;
+       char   *data;
+
+       /* XXX init sethead? */
+
+       if (read(fd, &mcHead, sizeof(mcHead)) != sizeof(mcHead))
+               corrupt();
+       if (strncmp(mcHead.magic, MCMagic, MCMagicLen) != 0)
+               corrupt();
+       if (mcHead.majorVer != MCMajorVer)
+               error(NULL, "unrecognized catalog version");
+       if ((mcHead.flags & MCGetByteOrder()) == 0)
+               error(NULL, "wrong byte order");
+
+       if (lseek(fd, mcHead.firstSet, SEEK_SET) == -1)
+               corrupt();
+
+       for (;;) {
+               if (read(fd, &mcSet, sizeof(mcSet)) != sizeof(mcSet))
+                       corrupt();
+               if (mcSet.invalid)
+                       continue;
+
+               set = xmalloc(sizeof(setT));
+               memset(set, '\0', sizeof(*set));
+               if (cat->first) {
+                       cat->last->next = set;
+                       set->prev = cat->last;
+                       cat->last = set;
+               } else
+                       cat->first = cat->last = set;
+
+               set->setId = mcSet.setId;
+
+               /* Get the data */
+               if (mcSet.dataLen) {
+                       data = xmalloc(mcSet.dataLen);
+                       if (lseek(fd, mcSet.data.off, SEEK_SET) == -1)
+                               corrupt();
+                       if (read(fd, data, mcSet.dataLen) != mcSet.dataLen)
+                               corrupt();
+                       if (lseek(fd, mcSet.u.firstMsg, SEEK_SET) == -1)
+                               corrupt();
+
+                       for (i = 0; i < mcSet.numMsgs; ++i) {
+                               if (read(fd, &mcMsg, sizeof(mcMsg)) != sizeof(mcMsg))
+                                       corrupt();
+                               if (mcMsg.invalid) {
+                                       --i;
+                                       continue;
+                               }
+                               msg = xmalloc(sizeof(msgT));
+                               memset(msg, '\0', sizeof(*msg));
+                               if (set->first) {
+                                       set->last->next = msg;
+                                       msg->prev = set->last;
+                                       set->last = msg;
+                               } else
+                                       set->first = set->last = msg;
+
+                               msg->msgId = mcMsg.msgId;
+                               msg->str = xstrdup((char *) (data + mcMsg.msg.off));
+                       }
+                       free(data);
+               }
+               if (!mcSet.nextSet)
+                       break;
+               if (lseek(fd, mcSet.nextSet, SEEK_SET) == -1)
+                       corrupt();
+       }
+#endif
+}
+
+/*
+ * Write message catalog.
+ *
+ * The message catalog is first converted from its internal to its
+ * external representation in a chunk of memory allocated for this
+ * purpose.  Then the completed catalog is written.  This approach
+ * avoids additional housekeeping variables and/or a lot of seeks
+ * that would otherwise be required.
+ */
+void
+MCWriteCat(fd)
+       int     fd;
+{
+       int     nsets;          /* number of sets */
+       int     nmsgs;          /* number of msgs */
+       int     string_size;    /* total size of string pool */
+       int     msgcat_size;    /* total size of message catalog */
+       void   *msgcat;         /* message catalog data */
+       struct _nls_cat_hdr *cat_hdr;
+       struct _nls_set_hdr *set_hdr;
+       struct _nls_msg_hdr *msg_hdr;
+       char   *strings;
+       struct _setT *set;
+       struct _msgT *msg;
+       int     msg_index;
+       int     msg_offset;
+
+       /* determine number of sets, number of messages, and size of the
+        * string pool */
+       nsets = 0;
+       nmsgs = 0;
+       string_size = 0;
+
+       for (set = sethead.lh_first; set != NULL;
+           set = set->entries.le_next) {
+               nsets++;
+
+               for (msg = set->msghead.lh_first; msg != NULL;
+                   msg = msg->entries.le_next) {
+                       nmsgs++;
+                       string_size += strlen(msg->str) + 1;
+               }
+       }
+
+#ifdef DEBUG
+       printf("number of sets: %d\n", nsets);
+       printf("number of msgs: %d\n", nmsgs);
+       printf("string pool size: %d\n", string_size);
+#endif
+
+       /* determine size and then allocate buffer for constructing external
+        * message catalog representation */
+       msgcat_size = sizeof(struct _nls_cat_hdr)
+           + (nsets * sizeof(struct _nls_set_hdr))
+           + (nmsgs * sizeof(struct _nls_msg_hdr))
+           + string_size;
+
+       msgcat = xmalloc(msgcat_size);
+       memset(msgcat, '\0', msgcat_size);
+
+       /* fill in msg catalog header */
+       cat_hdr = (struct _nls_cat_hdr *) msgcat;
+       cat_hdr->__magic = htonl(_NLS_MAGIC);
+       cat_hdr->__nsets = htonl(nsets);
+       cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
+       cat_hdr->__msg_hdr_offset =
+           htonl(nsets * sizeof(struct _nls_set_hdr));
+       cat_hdr->__msg_txt_offset =
+           htonl(nsets * sizeof(struct _nls_set_hdr) +
+           nmsgs * sizeof(struct _nls_msg_hdr));
+
+       /* compute offsets for set & msg header tables and string pool */
+       set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
+           sizeof(struct _nls_cat_hdr));
+       msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
+           sizeof(struct _nls_cat_hdr) +
+           nsets * sizeof(struct _nls_set_hdr));
+       strings = (char *) msgcat +
+           sizeof(struct _nls_cat_hdr) +
+           nsets * sizeof(struct _nls_set_hdr) +
+           nmsgs * sizeof(struct _nls_msg_hdr);
+
+       msg_index = 0;
+       msg_offset = 0;
+       for (set = sethead.lh_first; set != NULL;
+           set = set->entries.le_next) {
+
+               nmsgs = 0;
+               for (msg = set->msghead.lh_first; msg != NULL;
+                   msg = msg->entries.le_next) {
+                       int     msg_len = strlen(msg->str) + 1;
+
+                       msg_hdr->__msgno = htonl(msg->msgId);
+                       msg_hdr->__msglen = htonl(msg_len);
+                       msg_hdr->__offset = htonl(msg_offset);
+
+                       memcpy(strings, msg->str, msg_len);
+                       strings += msg_len;
+                       msg_offset += msg_len;
+
+                       nmsgs++;
+                       msg_hdr++;
+               }
+
+               set_hdr->__setno = htonl(set->setId);
+               set_hdr->__nmsgs = htonl(nmsgs);
+               set_hdr->__index = htonl(msg_index);
+               msg_index += nmsgs;
+               set_hdr++;
+       }
+
+       /* write out catalog.  XXX: should this be done in small chunks? */
+       write(fd, msgcat, msgcat_size);
+}
+
+void
+MCAddSet(setId)
+       int     setId;
+{
+       struct _setT *p, *q;
+
+       if (setId <= 0) {
+               error(NULL, "setId's must be greater than zero");
+               /* NOTREACHED */
+       }
+       if (setId > NL_SETMAX) {
+               error(NULL, "setId exceeds limit");
+               /* NOTREACHED */
+       }
+
+       p = sethead.lh_first;
+       q = NULL;
+       for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next);
+
+       if (p && p->setId == setId) {
+               ;
+       } else {
+               p = xmalloc(sizeof(struct _setT));
+               memset(p, '\0', sizeof(struct _setT));
+               LIST_INIT(&p->msghead);
+
+               p->setId = setId;
+
+               if (q == NULL) {
+                       LIST_INSERT_HEAD(&sethead, p, entries);
+               } else {
+                       LIST_INSERT_AFTER(q, p, entries);
+               }
+       }
+
+       curSet = p;
+}
+
+void
+MCAddMsg(msgId, str)
+       int     msgId;
+       const char *str;
+{
+       struct _msgT *p, *q;
+
+       if (!curSet)
+               error(NULL, "can't specify a message when no set exists");
+
+       if (msgId <= 0) {
+               error(NULL, "msgId's must be greater than zero");
+               /* NOTREACHED */
+       }
+       if (msgId > NL_MSGMAX) {
+               error(NULL, "msgID exceeds limit");
+               /* NOTREACHED */
+       }
+
+       p = curSet->msghead.lh_first;
+       q = NULL;
+       for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next);
+
+       if (p && p->msgId == msgId) {
+               free(p->str);
+       } else {
+               p = xmalloc(sizeof(struct _msgT));
+               memset(p, '\0', sizeof(struct _msgT));
+
+               if (q == NULL) {
+                       LIST_INSERT_HEAD(&curSet->msghead, p, entries);
+               } else {
+                       LIST_INSERT_AFTER(q, p, entries);
+               }
+       }
+
+       p->msgId = msgId;
+       p->str = xstrdup(str);
+}
+
+void
+MCDelSet(setId)
+       int     setId;
+{
+       struct _setT *set;
+       struct _msgT *msg;
+
+       set = sethead.lh_first;
+       for (; set != NULL && set->setId < setId; set = set->entries.le_next);
+
+       if (set && set->setId == setId) {
+
+               msg = set->msghead.lh_first;
+               while (msg) {
+                       free(msg->str);
+                       LIST_REMOVE(msg, entries);
+               }
+
+               LIST_REMOVE(set, entries);
+               return;
+       }
+       warning(NULL, "specified set doesn't exist");
+}
+
+void
+MCDelMsg(msgId)
+       int     msgId;
+{
+       struct _msgT *msg;
+
+       if (!curSet)
+               error(NULL, "you can't delete a message before defining the set");
+
+       msg = curSet->msghead.lh_first;
+       for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next);
+
+       if (msg && msg->msgId == msgId) {
+               free(msg->str);
+               LIST_REMOVE(msg, entries);
+               return;
+       }
+       warning(NULL, "specified msg doesn't exist");
 }