xref: /openbsd-src/usr.bin/gencat/gencat.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: gencat.c,v 1.18 2015/10/10 21:29:59 deraadt 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
124 usage(void)
125 {
126 	fprintf(stderr, "usage: %s catfile msgfile ...\n", __progname);
127 	exit(1);
128 }
129 
130 int
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 		case '?':
143 		default:
144 			usage();
145 			/* NOTREACHED */
146 		}
147 	}
148 	argc -= optind;
149 	argv += optind;
150 
151 	if (argc < 2) {
152 		usage();
153 		/* NOTREACHED */
154 	}
155 	catfile = *argv++;
156 
157 	for (; *argv; argv++) {
158 		if ((ifd = open(*argv, O_RDONLY)) < 0)
159 			err(1, "Unable to read %s", *argv);
160 		MCParse(ifd);
161 		close(ifd);
162 	}
163 
164 	if ((ofd = open(catfile, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
165 		err(1, "Unable to create a new %s", catfile);
166 	MCWriteCat(ofd);
167 	exit(0);
168 }
169 
170 static void
171 warning(char *cptr, char *msg)
172 {
173 	warnx("%s on line %ld\n%s", msg, lineno, curline);
174 	if (cptr) {
175 		char   *tptr;
176 		for (tptr = curline; tptr < cptr; ++tptr)
177 			putc(' ', stderr);
178 		fprintf(stderr, "^\n");
179 	}
180 }
181 
182 static void
183 error(char *cptr, char *msg)
184 {
185 	warning(cptr, msg);
186 	exit(1);
187 }
188 
189 static void
190 nomem(void)
191 {
192 	error(NULL, "out of memory");
193 }
194 
195 static void *
196 xmalloc(size_t len)
197 {
198 	void   *p;
199 
200 	if ((p = malloc(len)) == NULL)
201 		nomem();
202 	return (p);
203 }
204 
205 static void *
206 xrealloc(void *ptr, size_t size)
207 {
208 	if ((ptr = realloc(ptr, size)) == NULL)
209 		nomem();
210 	return (ptr);
211 }
212 
213 static char *
214 xstrdup(const char *str)
215 {
216 	char *nstr;
217 
218 	if ((nstr = strdup(str)) == NULL)
219 		nomem();
220 	return (nstr);
221 }
222 
223 static char *
224 get_line(int fd)
225 {
226 	static long curlen = BUFSIZ;
227 	static char buf[BUFSIZ], *bptr = buf, *bend = buf;
228 	char   *cptr, *cend;
229 	long    buflen;
230 
231 	if (!curline) {
232 		curline = xmalloc(curlen);
233 	}
234 	++lineno;
235 
236 	cptr = curline;
237 	cend = curline + curlen;
238 	for (;;) {
239 		for (; bptr < bend && cptr < cend; ++cptr, ++bptr) {
240 			if (*bptr == '\n') {
241 				*cptr = '\0';
242 				++bptr;
243 				return (curline);
244 			} else
245 				*cptr = *bptr;
246 		}
247 		if (bptr == bend) {
248 			buflen = read(fd, buf, BUFSIZ);
249 			if (buflen <= 0) {
250 				if (cptr > curline) {
251 					*cptr = '\0';
252 					return (curline);
253 				}
254 				return (NULL);
255 			}
256 			bend = buf + buflen;
257 			bptr = buf;
258 		}
259 		if (cptr == cend) {
260 			cptr = curline = xrealloc(curline, curlen *= 2);
261 			cend = curline + curlen;
262 		}
263 	}
264 }
265 
266 static char *
267 wskip(char *cptr)
268 {
269 	if (!*cptr || !ISSPACE(*cptr)) {
270 		warning(cptr, "expected a space");
271 		return (cptr);
272 	}
273 	while (*cptr && ISSPACE(*cptr))
274 		++cptr;
275 	return (cptr);
276 }
277 
278 static char *
279 cskip(char *cptr)
280 {
281 	if (!*cptr || ISSPACE(*cptr)) {
282 		warning(cptr, "wasn't expecting a space");
283 		return (cptr);
284 	}
285 	while (*cptr && !ISSPACE(*cptr))
286 		++cptr;
287 	return (cptr);
288 }
289 
290 static char *
291 getmsg(int fd, char *cptr, char quote)
292 {
293 	static char *msg = NULL;
294 	static long msglen = 0;
295 	long    clen, i;
296 	char   *tptr;
297 
298 	if (quote && *cptr == quote) {
299 		++cptr;
300 	}
301 
302 	clen = strlen(cptr) + 1;
303 	if (clen > msglen) {
304 		if (msglen)
305 			msg = xrealloc(msg, clen);
306 		else
307 			msg = xmalloc(clen);
308 		msglen = clen;
309 	}
310 	tptr = msg;
311 
312 	while (*cptr) {
313 		if (quote && *cptr == quote) {
314 			char   *tmp;
315 			tmp = cptr + 1;
316 
317 			if (*tmp && (!ISSPACE(*tmp) || *wskip(tmp))) {
318 				warning(cptr, "unexpected quote character, ignoring");
319 				*tptr++ = *cptr++;
320 			} else {
321 				*cptr = '\0';
322 			}
323 		} else if (*cptr == '\\') {
324 			++cptr;
325 			switch (*cptr) {
326 			case '\0':
327 				cptr = get_line(fd);
328 				if (!cptr)
329 					error(NULL, "premature end of file");
330 				msglen += strlen(cptr);
331 				i = tptr - msg;
332 				msg = xrealloc(msg, msglen);
333 				tptr = msg + i;
334 				break;
335 			case 'n':
336 				*tptr++ = '\n';
337 				++cptr;
338 				break;
339 			case 't':
340 				*tptr++ = '\t';
341 				++cptr;
342 				break;
343 			case 'v':
344 				*tptr++ = '\v';
345 				++cptr;
346 				break;
347 			case 'b':
348 				*tptr++ = '\b';
349 				++cptr;
350 				break;
351 			case 'r':
352 				*tptr++ = '\r';
353 				++cptr;
354 				break;
355 			case 'f':
356 				*tptr++ = '\f';
357 				++cptr;
358 				break;
359 			case '\\':
360 				*tptr++ = '\\';
361 				++cptr;
362 				break;
363 			case '"':
364 				/* FALLTHROUGH */
365 			case '\'':
366 				/*
367 				 * While it isn't necessary to
368 				 * escape ' and ", let's accept
369 				 * them escaped and not complain.
370 				 * (XPG4 states that '\' should be
371 				 * ignored when not used in a
372 				 * valid escape sequence)
373 				 */
374 				*tptr++ = '"';
375 				++cptr;
376 				break;
377 			default:
378 				if (quote && *cptr == quote) {
379 					*tptr++ = *cptr++;
380 				} else if (isdigit((unsigned char) *cptr)) {
381 					*tptr = 0;
382 					for (i = 0; i < 3; ++i) {
383 						if (!isdigit((unsigned char) *cptr))
384 							break;
385 						if (*cptr > '7')
386 							warning(cptr, "octal number greater than 7?!");
387 						*tptr *= 8;
388 						*tptr += (*cptr - '0');
389 						++cptr;
390 					}
391 				} else {
392 					warning(cptr, "unrecognized escape sequence; ignoring esacpe character");
393 				}
394 				break;
395 			}
396 		} else {
397 			*tptr++ = *cptr++;
398 		}
399 	}
400 	*tptr = '\0';
401 	return (msg);
402 }
403 
404 void
405 MCParse(int fd)
406 {
407 	char   *cptr, *str;
408 	int     setid, msgid = 0;
409 	char    quote = 0;
410 
411 	/* XXX: init sethead? */
412 
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 				msgid = 0;
422 			} else if (strncmp(cptr, "delset", 6) == 0) {
423 				cptr += 6;
424 				cptr = wskip(cptr);
425 				setid = atoi(cptr);
426 				MCDelSet(setid);
427 			} else if (strncmp(cptr, "quote", 5) == 0) {
428 				cptr += 5;
429 				if (!*cptr)
430 					quote = 0;
431 				else {
432 					cptr = wskip(cptr);
433 					if (!*cptr)
434 						quote = 0;
435 					else
436 						quote = *cptr;
437 				}
438 			} else if (ISSPACE(*cptr)) {
439 				;
440 			} else {
441 				if (*cptr) {
442 					cptr = wskip(cptr);
443 					if (*cptr)
444 						warning(cptr, "unrecognized line");
445 				}
446 			}
447 		} else {
448 			/*
449 			 * First check for (and eat) empty lines....
450 			 */
451 			if (!*cptr)
452 				continue;
453 			/*
454 			 * We have a digit? Start of a message. Else,
455 			 * syntax error.
456 			 */
457 			if (isdigit((unsigned char) *cptr)) {
458 				msgid = atoi(cptr);
459 				cptr = cskip(cptr);
460 				cptr = wskip(cptr);
461 				/* if (*cptr) ++cptr; */
462 			} else {
463 				warning(cptr, "neither blank line nor start of a message id");
464 				continue;
465 			}
466 			/*
467 			 * If we have a message ID, but no message,
468 			 * then this means "delete this message id
469 			 * from the catalog".
470 			 */
471 			if (!*cptr) {
472 				MCDelMsg(msgid);
473 			} else {
474 				str = getmsg(fd, cptr, quote);
475 				MCAddMsg(msgid, str);
476 			}
477 		}
478 	}
479 }
480 
481 /*
482  * Write message catalog.
483  *
484  * The message catalog is first converted from its internal to its
485  * external representation in a chunk of memory allocated for this
486  * purpose.  Then the completed catalog is written.  This approach
487  * avoids additional housekeeping variables and/or a lot of seeks
488  * that would otherwise be required.
489  */
490 void
491 MCWriteCat(int fd)
492 {
493 	int     nsets;		/* number of sets */
494 	int     nmsgs;		/* number of msgs */
495 	int     string_size;	/* total size of string pool */
496 	int     msgcat_size;	/* total size of message catalog */
497 	void   *msgcat;		/* message catalog data */
498 	struct _nls_cat_hdr *cat_hdr;
499 	struct _nls_set_hdr *set_hdr;
500 	struct _nls_msg_hdr *msg_hdr;
501 	char   *strings;
502 	struct _setT *set;
503 	struct _msgT *msg;
504 	int     msg_index;
505 	int     msg_offset;
506 
507 	/* determine number of sets, number of messages, and size of the
508 	 * string pool */
509 	nsets = 0;
510 	nmsgs = 0;
511 	string_size = 0;
512 
513 	LIST_FOREACH(set, &sethead, entries) {
514 		nsets++;
515 
516 		LIST_FOREACH(msg, &set->msghead, entries) {
517 			nmsgs++;
518 			string_size += strlen(msg->str) + 1;
519 		}
520 	}
521 
522 #ifdef DEBUG
523 	printf("number of sets: %d\n", nsets);
524 	printf("number of msgs: %d\n", nmsgs);
525 	printf("string pool size: %d\n", string_size);
526 #endif
527 
528 	/* determine size and then allocate buffer for constructing external
529 	 * message catalog representation */
530 	msgcat_size = sizeof(struct _nls_cat_hdr)
531 	    + (nsets * sizeof(struct _nls_set_hdr))
532 	    + (nmsgs * sizeof(struct _nls_msg_hdr))
533 	    + string_size;
534 
535 	msgcat = xmalloc(msgcat_size);
536 	memset(msgcat, '\0', msgcat_size);
537 
538 	/* fill in msg catalog header */
539 	cat_hdr = (struct _nls_cat_hdr *) msgcat;
540 	cat_hdr->__magic = htonl(_NLS_MAGIC);
541 	cat_hdr->__nsets = htonl(nsets);
542 	cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr));
543 	cat_hdr->__msg_hdr_offset =
544 	    htonl(nsets * sizeof(struct _nls_set_hdr));
545 	cat_hdr->__msg_txt_offset =
546 	    htonl(nsets * sizeof(struct _nls_set_hdr) +
547 	    nmsgs * sizeof(struct _nls_msg_hdr));
548 
549 	/* compute offsets for set & msg header tables and string pool */
550 	set_hdr = (struct _nls_set_hdr *) ((char *) msgcat +
551 	    sizeof(struct _nls_cat_hdr));
552 	msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat +
553 	    sizeof(struct _nls_cat_hdr) +
554 	    nsets * sizeof(struct _nls_set_hdr));
555 	strings = (char *) msgcat +
556 	    sizeof(struct _nls_cat_hdr) +
557 	    nsets * sizeof(struct _nls_set_hdr) +
558 	    nmsgs * sizeof(struct _nls_msg_hdr);
559 
560 	msg_index = 0;
561 	msg_offset = 0;
562 	LIST_FOREACH(set, &sethead, entries) {
563 
564 		nmsgs = 0;
565 		LIST_FOREACH(msg, &set->msghead, entries) {
566 			int     msg_len = strlen(msg->str) + 1;
567 
568 			msg_hdr->__msgno = htonl(msg->msgId);
569 			msg_hdr->__msglen = htonl(msg_len);
570 			msg_hdr->__offset = htonl(msg_offset);
571 
572 			memcpy(strings, msg->str, msg_len);
573 			strings += msg_len;
574 			msg_offset += msg_len;
575 
576 			nmsgs++;
577 			msg_hdr++;
578 		}
579 
580 		set_hdr->__setno = htonl(set->setId);
581 		set_hdr->__nmsgs = htonl(nmsgs);
582 		set_hdr->__index = htonl(msg_index);
583 		msg_index += nmsgs;
584 		set_hdr++;
585 	}
586 
587 	/* write out catalog.  XXX: should this be done in small chunks? */
588 	write(fd, msgcat, msgcat_size);
589 }
590 
591 void
592 MCAddSet(int setId)
593 {
594 	struct _setT *p, *q;
595 
596 	if (setId <= 0) {
597 		error(NULL, "setId's must be greater than zero");
598 		/* NOTREACHED */
599 	}
600 #if 0
601 	/* XXX */
602 	if (setId > NL_SETMAX) {
603 		error(NULL, "setId %d exceeds limit (%d)");
604 		/* NOTREACHED */
605 	}
606 #endif
607 
608 	p = LIST_FIRST(&sethead);
609 	q = NULL;
610 	for (; p != NULL && p->setId < setId; q = p, p = LIST_NEXT(p, entries));
611 
612 	if (p && p->setId == setId) {
613 		;
614 	} else {
615 		p = xmalloc(sizeof(struct _setT));
616 		memset(p, '\0', sizeof(struct _setT));
617 		LIST_INIT(&p->msghead);
618 
619 		p->setId = setId;
620 
621 		if (q == NULL) {
622 			LIST_INSERT_HEAD(&sethead, p, entries);
623 		} else {
624 			LIST_INSERT_AFTER(q, p, entries);
625 		}
626 	}
627 
628 	curSet = p;
629 }
630 
631 void
632 MCAddMsg(int msgId, const char *str)
633 {
634 	struct _msgT *p, *q;
635 
636 	if (!curSet)
637 		error(NULL, "can't specify a message when no set exists");
638 
639 	if (msgId <= 0) {
640 		error(NULL, "msgId's must be greater than zero");
641 		/* NOTREACHED */
642 	}
643 #if 0
644 	/* XXX */
645 	if (msgId > NL_SETMAX) {
646 		error(NULL, "msgId %d exceeds limit (%d)");
647 		/* NOTREACHED */
648 	}
649 #endif
650 
651 	p = LIST_FIRST(&curSet->msghead);
652 	q = NULL;
653 	for (; p != NULL && p->msgId < msgId; q = p, p = LIST_NEXT(p, entries));
654 
655 	if (p && p->msgId == msgId) {
656 		free(p->str);
657 	} else {
658 		p = xmalloc(sizeof(struct _msgT));
659 		memset(p, '\0', sizeof(struct _msgT));
660 
661 		if (q == NULL) {
662 			LIST_INSERT_HEAD(&curSet->msghead, p, entries);
663 		} else {
664 			LIST_INSERT_AFTER(q, p, entries);
665 		}
666 	}
667 
668 	p->msgId = msgId;
669 	p->str = xstrdup(str);
670 }
671 
672 void
673 MCDelSet(int setId)
674 {
675 	struct _setT *set;
676 	struct _msgT *msg;
677 
678 	set = LIST_FIRST(&sethead);
679 	for (; set != NULL && set->setId < setId;
680 	    set = LIST_NEXT(set, entries));
681 
682 	if (set && set->setId == setId) {
683 
684 		msg = LIST_FIRST(&set->msghead);
685 		while (msg) {
686 			free(msg->str);
687 			LIST_REMOVE(msg, entries);
688 		}
689 
690 		LIST_REMOVE(set, entries);
691 		return;
692 	}
693 	warning(NULL, "specified set doesn't exist");
694 }
695 
696 void
697 MCDelMsg(int msgId)
698 {
699 	struct _msgT *msg;
700 
701 	if (!curSet)
702 		error(NULL, "you can't delete a message before defining the set");
703 
704 	msg = LIST_FIRST(&curSet->msghead);
705 	for (; msg != NULL && msg->msgId < msgId;
706 	    msg = LIST_NEXT(msg, entries));
707 
708 	if (msg && msg->msgId == msgId) {
709 		free(msg->str);
710 		LIST_REMOVE(msg, entries);
711 		return;
712 	}
713 	warning(NULL, "specified msg doesn't exist");
714 }
715