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