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