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