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