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