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