0c8f3bc91bcd6e82fa3ebc2a8a4cca93de390e07
[dragonfly.git] / usr.bin / gencat / gencat.c
1 /* ex:ts=4
2  */
3
4 /*      $NetBSD: gencat.c,v 1.18 2003/10/27 00:12:43 lukem Exp $        */
5
6 /*
7  * Copyright (c) 1996 The NetBSD Foundation, Inc.
8  * All rights reserved.
9  *
10  * This code is derived from software contributed to The NetBSD Foundation
11  * by J.T. Conklin.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 
26  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * $FreeBSD: head/usr.bin/gencat/gencat.c 241737 2012-10-19 14:49:42Z ed $
35  */
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
70 #define _NLS_PRIVATE
71
72 #include <sys/types.h>
73 #include <sys/queue.h>
74
75 #include <arpa/inet.h>          /* for htonl() */
76
77 #include <ctype.h>
78 #include <err.h>
79 #include <fcntl.h>
80 #include <limits.h>
81 #include <nl_types.h>
82 #include <stdio.h>
83 #include <stdlib.h>
84 #include <string.h>
85 #include <unistd.h>
86
87 #ifdef BOOTSTRAPPING
88 #define getline get_line /* help bootstrap previous stdio.h */
89 #endif
90
91 struct _msgT {
92         long    msgId;
93         char   *str;
94         LIST_ENTRY(_msgT) entries;
95 };
96
97 struct _setT {
98         long    setId;
99         LIST_HEAD(msghead, _msgT) msghead;
100         LIST_ENTRY(_setT) entries;
101 };
102
103 static LIST_HEAD(sethead, _setT) sethead;
104 static struct _setT *curSet;
105
106 static char *curline = NULL;
107 static long lineno = 0;
108
109 static  char   *cskip(char *);
110 static  void    error(const char *);
111 static  char   *getline(int);
112 static  char   *getmsg(int, char *, char);
113 static  void    warning(const char *, const char *);
114 static  char   *wskip(char *);
115 static  char   *xstrdup(const char *);
116 static  void   *xmalloc(size_t);
117 static  void   *xrealloc(void *, size_t);
118
119 void    MCParse(int);
120 void    MCReadCat(int);
121 void    MCWriteCat(int);
122 void    MCDelMsg(int);
123 void    MCAddMsg(int, const char *);
124 void    MCAddSet(int);
125 void    MCDelSet(int);
126 void    usage(void);
127
128 void
129 usage(void)
130 {
131         fprintf(stderr, "usage: %s catfile msgfile ...\n", getprogname());
132         exit(1);
133 }
134
135 int
136 main(int argc, char **argv)
137 {
138         int     ofd, ifd;
139         char    *catfile = NULL;
140         int     c;
141
142 #define DEPRECATEDMSG   1
143
144 #ifdef DEPRECATEDMSG
145         while ((c = getopt(argc, argv, "new")) != -1) {
146 #else
147         while ((c = getopt(argc, argv, "")) != -1) {
148 #endif
149                 switch (c) {
150 #ifdef DEPRECATEDMSG
151                 case 'n':
152                         fprintf(stderr, "WARNING: Usage of \"-new\" argument is deprecated.\n");
153                 case 'e':
154                 case 'w':
155                         break;
156 #endif
157                 case '?':
158                 default:
159                         usage();
160                         /* NOTREACHED */
161                 }
162         }
163         argc -= optind;
164         argv += optind;
165
166         if (argc < 2) {
167                 usage();
168                 /* NOTREACHED */
169         }
170         catfile = *argv++;
171
172         for (; *argv; argv++) {
173                 if ((ifd = open(*argv, O_RDONLY)) < 0)
174                         err(1, "Unable to read %s", *argv);
175                 MCParse(ifd);
176                 close(ifd);
177         }
178
179         if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
180                 err(1, "Unable to create a new %s", catfile);
181         MCWriteCat(ofd);
182         exit(0);
183 }
184
185 static void
186 warning(const char *cptr, const char *msg)
187 {
188         fprintf(stderr, "%s: %s on line %ld\n", getprogname(), msg, lineno);
189         fprintf(stderr, "%s\n", curline);
190         if (cptr) {
191                 char   *tptr;
192                 for (tptr = curline; tptr < cptr; ++tptr)
193                         putc(' ', stderr);
194                 fprintf(stderr, "^\n");
195         }
196 }
197
198 #define CORRUPT()       { error("corrupt message catalog"); }
199 #define NOMEM()         { error("out of memory"); }
200
201 static void
202 error(const char *msg)
203 {
204         warning(NULL, msg);
205         exit(1);
206 }
207
208 static void *
209 xmalloc(size_t len)
210 {
211         void   *p;
212
213         if ((p = malloc(len)) == NULL)
214                 NOMEM();
215         return (p);
216 }
217
218 static void *
219 xrealloc(void *ptr, size_t size)
220 {
221         if ((ptr = realloc(ptr, size)) == NULL)
222                 NOMEM();
223         return (ptr);
224 }
225
226 static char *
227 xstrdup(const char *str)
228 {
229         char *nstr;
230
231         if ((nstr = strdup(str)) == NULL)
232                 NOMEM();
233         return (nstr);
234 }
235
236 static char *
237 getline(int fd)
238 {
239         static long curlen = BUFSIZ;
240         static char buf[BUFSIZ], *bptr = buf, *bend = buf;
241         char   *cptr, *cend;
242         long    buflen;
243
244         if (!curline) {
245                 curline = xmalloc(curlen);
246         }
247         ++lineno;
248
249         cptr = curline;
250         cend = curline + curlen;
251         for (;;) {
252                 for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
253                         if (*bptr == '\n') {
254                                 *cptr = '\0';
255                                 ++bptr;
256                                 return (curline);
257                         } else
258                                 *cptr = *bptr;
259                 }
260                 if (cptr == cend) {
261                         cptr = curline = xrealloc(curline, curlen *= 2);
262                         cend = curline + curlen;
263                 }
264                 if (bptr == bend) {
265                         buflen = read(fd, buf, BUFSIZ);
266                         if (buflen <= 0) {
267                                 if (cptr > curline) {
268                                         *cptr = '\0';
269                                         return (curline);
270                                 }
271                                 return (NULL);
272                         }
273                         bend = buf + buflen;
274                         bptr = buf;
275                 }
276         }
277 }
278
279 static char *
280 wskip(char *cptr)
281 {
282         if (!*cptr || !isspace((unsigned char) *cptr)) {
283                 warning(cptr, "expected a space");
284                 return (cptr);
285         }
286         while (*cptr && isspace((unsigned char) *cptr))
287                 ++cptr;
288         return (cptr);
289 }
290
291 static char *
292 cskip(char *cptr)
293 {
294         if (!*cptr || isspace((unsigned char) *cptr)) {
295                 warning(cptr, "wasn't expecting a space");
296                 return (cptr);
297         }
298         while (*cptr && !isspace((unsigned char) *cptr))
299                 ++cptr;
300         return (cptr);
301 }
302
303 static char *
304 getmsg(int fd, char *cptr, char quote)
305 {
306         static char *msg = NULL;
307         static long msglen = 0;
308         long    clen, i;
309         char   *tptr;
310
311         if (quote && *cptr == quote) {
312                 ++cptr;
313         } 
314
315         clen = strlen(cptr) + 1;
316         if (clen > msglen) {
317                 if (msglen)
318                         msg = xrealloc(msg, clen);
319                 else
320                         msg = xmalloc(clen);
321                 msglen = clen;
322         }
323         tptr = msg;
324
325         while (*cptr) {
326                 if (quote && *cptr == quote) {
327                         char   *tmp;
328                         tmp = cptr + 1;
329                         if (*tmp && (!isspace((unsigned char) *tmp) || *wskip(tmp))) {
330                                 warning(cptr, "unexpected quote character, ignoring");
331                                 *tptr++ = *cptr++;
332                         } else {
333                                 *cptr = '\0';
334                         }
335                 } else
336                         if (*cptr == '\\') {
337                                 ++cptr;
338                                 switch (*cptr) {
339                                 case '\0':
340                                         cptr = getline(fd);
341                                         if (!cptr)
342                                                 error("premature end of file");
343                                         msglen += strlen(cptr);
344                                         i = tptr - msg;
345                                         msg = xrealloc(msg, msglen);
346                                         tptr = msg + i;
347                                         break;
348
349                 #define CASEOF(CS, CH)          \
350                         case CS:                \
351                                 *tptr++ = CH;   \
352                                 ++cptr;         \
353                                 break;          \
354
355                                 CASEOF('n', '\n');
356                                 CASEOF('t', '\t');
357                                 CASEOF('v', '\v');
358                                 CASEOF('b', '\b');
359                                 CASEOF('r', '\r');
360                                 CASEOF('f', '\f');
361                                 CASEOF('"', '"');
362                                 CASEOF('\\', '\\');
363
364                                 default:
365                                         if (quote && *cptr == quote) {
366                                                 *tptr++ = *cptr++;
367                                         } else if (isdigit((unsigned char) *cptr)) {
368                                                 *tptr = 0;
369                                                 for (i = 0; i < 3; ++i) {
370                                                         if (!isdigit((unsigned char) *cptr))
371                                                                 break;
372                                                         if (*cptr > '7')
373                                                                 warning(cptr, "octal number greater than 7?!");
374                                                         *tptr *= 8;
375                                                         *tptr += (*cptr - '0');
376                                                         ++cptr;
377                                                 }
378                                         } else {
379                                                 warning(cptr, "unrecognized escape sequence");
380                                         }
381                                         break;
382                                 }
383                         } else {
384                                 *tptr++ = *cptr++;
385                         }
386         }
387         *tptr = '\0';
388         return (msg);
389 }
390
391 void
392 MCParse(int fd)
393 {
394         char   *cptr, *str;
395         int     setid, msgid = 0;
396         char    quote = 0;
397
398         /* XXX: init sethead? */
399
400         while ((cptr = getline(fd))) {
401                 if (*cptr == '$') {
402                         ++cptr;
403                         if (strncmp(cptr, "set", 3) == 0) {
404                                 cptr += 3;
405                                 cptr = wskip(cptr);
406                                 setid = atoi(cptr);
407                                 MCAddSet(setid);
408                                 msgid = 0;
409                         } else if (strncmp(cptr, "delset", 6) == 0) {
410                                 cptr += 6;
411                                 cptr = wskip(cptr);
412                                 setid = atoi(cptr);
413                                 MCDelSet(setid);
414                         } else if (strncmp(cptr, "quote", 5) == 0) {
415                                 cptr += 5;
416                                 if (!*cptr)
417                                         quote = 0;
418                                 else {
419                                         cptr = wskip(cptr);
420                                         if (!*cptr)
421                                                 quote = 0;
422                                         else
423                                                 quote = *cptr;
424                                 }
425                         } else if (isspace((unsigned char) *cptr)) {
426                                 ;
427                         } else {
428                                 if (*cptr) {
429                                         cptr = wskip(cptr);
430                                         if (*cptr)
431                                                 warning(cptr, "unrecognized line");
432                                 }
433                         }
434                 } else {
435                         /*
436                          * First check for (and eat) empty lines....
437                          */
438                         if (!*cptr)
439                                 continue;
440                         /*
441                          * We have a digit? Start of a message. Else,
442                          * syntax error.
443                          */
444                         if (isdigit((unsigned char) *cptr)) {
445                                 msgid = atoi(cptr);
446                                 cptr = cskip(cptr);
447                                 cptr = wskip(cptr);
448                                 /* if (*cptr) ++cptr; */
449                         } else {
450                                 warning(cptr, "neither blank line nor start of a message id");
451                                 continue;
452                         }
453                         /*
454                          * If we have a message ID, but no message,
455                          * then this means "delete this message id
456                          * from the catalog".
457                          */
458                         if (!*cptr) {
459                                 MCDelMsg(msgid);
460                         } else {
461                                 str = getmsg(fd, cptr, quote);
462                                 MCAddMsg(msgid, str);
463                         }
464                 }
465         }
466 }
467
468 /*
469  * Write message catalog.
470  *
471  * The message catalog is first converted from its internal to its
472  * external representation in a chunk of memory allocated for this
473  * purpose.  Then the completed catalog is written.  This approach
474  * avoids additional housekeeping variables and/or a lot of seeks
475  * that would otherwise be required.
476  */
477 void
478 MCWriteCat(int fd)
479 {
480         int     nsets;          /* number of sets */
481         int     nmsgs;          /* number of msgs */
482         int     string_size;    /* total size of string pool */
483         int     msgcat_size;    /* total size of message catalog */
484         void   *msgcat;         /* message catalog data */
485         struct _nls_cat_hdr *cat_hdr;
486         struct _nls_set_hdr *set_hdr;
487         struct _nls_msg_hdr *msg_hdr;
488         char   *strings;
489         struct _setT *set;
490         struct _msgT *msg;
491         int     msg_index;
492         int     msg_offset;
493
494         /* determine number of sets, number of messages, and size of the
495          * string pool */
496         nsets = 0;
497         nmsgs = 0;
498         string_size = 0;
499
500         for (set = sethead.lh_first; set != NULL;
501             set = set->entries.le_next) {
502                 nsets++;
503
504                 for (msg = set->msghead.lh_first; msg != NULL;
505                     msg = msg->entries.le_next) {
506                         nmsgs++;
507                         string_size += strlen(msg->str) + 1;
508                 }
509         }
510
511 #ifdef DEBUG
512         printf("number of sets: %d\n", nsets);
513         printf("number of msgs: %d\n", nmsgs);
514         printf("string pool size: %d\n", string_size);
515 #endif
516
517         /* determine size and then allocate buffer for constructing external
518          * message catalog representation */
519         msgcat_size = sizeof(struct _nls_cat_hdr)
520             + (nsets * sizeof(struct _nls_set_hdr))
521             + (nmsgs * sizeof(struct _nls_msg_hdr))
522             + string_size;
523
524         msgcat = xmalloc(msgcat_size);
525         memset(msgcat, '\0', msgcat_size);
526
527         /* fill in msg catalog header */
528         cat_hdr = (struct _nls_cat_hdr *) msgcat;
529         cat_hdr->__magic = htonl(_NLS_MAGIC);
530         cat_hdr->__nsets = htonl(nsets);
531         cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
532         cat_hdr->__msg_hdr_offset =
533             htonl(nsets * sizeof(struct _nls_set_hdr));
534         cat_hdr->__msg_txt_offset =
535             htonl(nsets * sizeof(struct _nls_set_hdr) +
536             nmsgs * sizeof(struct _nls_msg_hdr));
537
538         /* compute offsets for set & msg header tables and string pool */
539         set_hdr = (struct _nls_set_hdr *)(void *)((char *)msgcat +
540             sizeof(struct _nls_cat_hdr));
541         msg_hdr = (struct _nls_msg_hdr *)(void *)((char *)msgcat +
542             sizeof(struct _nls_cat_hdr) +
543             nsets * sizeof(struct _nls_set_hdr));
544         strings = (char *) msgcat +
545             sizeof(struct _nls_cat_hdr) +
546             nsets * sizeof(struct _nls_set_hdr) +
547             nmsgs * sizeof(struct _nls_msg_hdr);
548
549         msg_index = 0;
550         msg_offset = 0;
551         for (set = sethead.lh_first; set != NULL;
552             set = set->entries.le_next) {
553
554                 nmsgs = 0;
555                 for (msg = set->msghead.lh_first; msg != NULL;
556                     msg = msg->entries.le_next) {
557                         int     msg_len = strlen(msg->str) + 1;
558
559                         msg_hdr->__msgno = htonl(msg->msgId);
560                         msg_hdr->__msglen = htonl(msg_len);
561                         msg_hdr->__offset = htonl(msg_offset);
562
563                         memcpy(strings, msg->str, msg_len);
564                         strings += msg_len;
565                         msg_offset += msg_len;
566
567                         nmsgs++;
568                         msg_hdr++;
569                 }
570
571                 set_hdr->__setno = htonl(set->setId);
572                 set_hdr->__nmsgs = htonl(nmsgs);
573                 set_hdr->__index = htonl(msg_index);
574                 msg_index += nmsgs;
575                 set_hdr++;
576         }
577
578         /* write out catalog.  XXX: should this be done in small chunks? */
579         write(fd, msgcat, msgcat_size);
580 }
581
582 void
583 MCAddSet(int setId)
584 {
585         struct _setT *p, *q;
586
587         if (setId <= 0) {
588                 error("setId's must be greater than zero");
589                 /* NOTREACHED */
590         }
591         if (setId > NL_SETMAX) {
592                 error("setId exceeds limit");
593                 /* NOTREACHED */
594         }
595
596         p = sethead.lh_first;
597         q = NULL;
598         for (; p != NULL && p->setId < setId; q = p, p = p->entries.le_next);
599
600         if (p && p->setId == setId) {
601                 ;
602         } else {
603                 p = xmalloc(sizeof(struct _setT));
604                 memset(p, '\0', sizeof(struct _setT));
605                 LIST_INIT(&p->msghead);
606
607                 p->setId = setId;
608
609                 if (q == NULL) {
610                         LIST_INSERT_HEAD(&sethead, p, entries);
611                 } else {
612                         LIST_INSERT_AFTER(q, p, entries);
613                 }
614         }
615
616         curSet = p;
617 }
618
619 void
620 MCAddMsg(int msgId, const char *str)
621 {
622         struct _msgT *p, *q;
623
624         if (!curSet)
625                 error("can't specify a message when no set exists");
626
627         if (msgId <= 0) {
628                 error("msgId's must be greater than zero");
629                 /* NOTREACHED */
630         }
631         if (msgId > NL_MSGMAX) {
632                 error("msgID exceeds limit");
633                 /* NOTREACHED */
634         }
635
636         p = curSet->msghead.lh_first;
637         q = NULL;
638         for (; p != NULL && p->msgId < msgId; q = p, p = p->entries.le_next);
639
640         if (p && p->msgId == msgId) {
641                 free(p->str);
642         } else {
643                 p = xmalloc(sizeof(struct _msgT));
644                 memset(p, '\0', sizeof(struct _msgT));
645
646                 if (q == NULL) {
647                         LIST_INSERT_HEAD(&curSet->msghead, p, entries);
648                 } else {
649                         LIST_INSERT_AFTER(q, p, entries);
650                 }
651         }
652
653         p->msgId = msgId;
654         p->str = xstrdup(str);
655 }
656
657 void
658 MCDelSet(int setId)
659 {
660         struct _setT *set;
661         struct _msgT *msg;
662
663         set = sethead.lh_first;
664         for (; set != NULL && set->setId < setId; set = set->entries.le_next);
665
666         if (set && set->setId == setId) {
667
668                 msg = set->msghead.lh_first;
669                 while (msg) {
670                         free(msg->str);
671                         LIST_REMOVE(msg, entries);
672                 }
673
674                 LIST_REMOVE(set, entries);
675                 return;
676         }
677         warning(NULL, "specified set doesn't exist");
678 }
679
680 void
681 MCDelMsg(int msgId)
682 {
683         struct _msgT *msg;
684
685         if (!curSet)
686                 error("you can't delete a message before defining the set");
687
688         msg = curSet->msghead.lh_first;
689         for (; msg != NULL && msg->msgId < msgId; msg = msg->entries.le_next);
690
691         if (msg && msg->msgId == msgId) {
692                 free(msg->str);
693                 LIST_REMOVE(msg, entries);
694                 return;
695         }
696         warning(NULL, "specified msg doesn't exist");
697 }