1213317Sgordon/* ex:ts=4
2213317Sgordon */
3213317Sgordon
4213317Sgordon/*	$NetBSD: gencat.c,v 1.18 2003/10/27 00:12:43 lukem Exp $	*/
5213317Sgordon
6213317Sgordon/*-
7213317Sgordon * SPDX-License-Identifier: BSD-2-Clause-NetBSD AND MIT
8213317Sgordon *
9213317Sgordon * Copyright (c) 1996 The NetBSD Foundation, Inc.
10213317Sgordon * All rights reserved.
11213317Sgordon *
12213317Sgordon * This code is derived from software contributed to The NetBSD Foundation
13213317Sgordon * by J.T. Conklin.
14213317Sgordon *
15213317Sgordon * Redistribution and use in source and binary forms, with or without
16213317Sgordon * modification, are permitted provided that the following conditions
17213317Sgordon * are met:
18213317Sgordon * 1. Redistributions of source code must retain the above copyright
19213317Sgordon *    notice, this list of conditions and the following disclaimer.
20213317Sgordon * 2. Redistributions in binary form must reproduce the above copyright
21213317Sgordon *    notice, this list of conditions and the following disclaimer in the
22213317Sgordon *    documentation and/or other materials provided with the distribution.
23213317Sgordon *
24213317Sgordon * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
25213317Sgordon * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
26213317Sgordon * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27213317Sgordon * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
28222650Sru * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29233520Sjoel * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30213317Sgordon * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31213317Sgordon * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32213317Sgordon * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33213317Sgordon * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34213317Sgordon * POSSIBILITY OF SUCH DAMAGE.
35213317Sgordon */
36213317Sgordon
37213317Sgordon/***********************************************************
38213317SgordonCopyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts.
39213317Sgordon
40213317Sgordon                        All Rights Reserved
41213317Sgordon
42213317SgordonPermission to use, copy, modify, and distribute this software and its
43213317Sgordondocumentation for any purpose and without fee is hereby granted,
44213317Sgordonprovided that the above copyright notice appear in all copies and that
45213317Sgordonboth that copyright notice and this permission notice appear in
46213317Sgordonsupporting documentation, and that Alfalfa's name not be used in
47213317Sgordonadvertising or publicity pertaining to distribution of the software
48213317Sgordonwithout specific, written prior permission.
49213317Sgordon
50213317SgordonALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
51213317SgordonALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
52213317SgordonALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
53213317SgordonANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
54213317SgordonWHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
55213317SgordonARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
56213317SgordonSOFTWARE.
57213317Sgordon
58213317SgordonIf you make any modifications, bugfixes or other changes to this software
59213317Sgordonwe'd appreciate it if you could send a copy to us so we can keep things
60236508Sjoelup-to-date.  Many thanks.
61213317Sgordon				Kee Hinckley
62213317Sgordon				Alfalfa Software, Inc.
63213317Sgordon				267 Allston St., #3
64213317Sgordon				Cambridge, MA 02139  USA
65213317Sgordon				nazgul@alfalfa.com
66213317Sgordon
67213317Sgordon******************************************************************/
68213317Sgordon
69213317Sgordon#include <sys/cdefs.h>
70213317Sgordon__FBSDID("$FreeBSD: stable/11/usr.bin/gencat/gencat.c 330449 2018-03-05 07:26:05Z eadler $");
71213317Sgordon
72213317Sgordon#define _NLS_PRIVATE
73236508Sjoel
74236508Sjoel#include <sys/types.h>
75213317Sgordon#include <sys/queue.h>
76213317Sgordon
77213317Sgordon#include <arpa/inet.h>		/* for htonl() */
78213317Sgordon
79213317Sgordon#include <ctype.h>
80213317Sgordon#include <err.h>
81213317Sgordon#include <fcntl.h>
82213317Sgordon#include <limits.h>
83213317Sgordon#include <nl_types.h>
84213317Sgordon#include <stdio.h>
85213317Sgordon#include <stdlib.h>
86213317Sgordon#include <string.h>
87213317Sgordon#include <unistd.h>
88213317Sgordon
89213317Sgordonstruct _msgT {
90213317Sgordon	long    msgId;
91236508Sjoel	char   *str;
92213317Sgordon	LIST_ENTRY(_msgT) entries;
93213317Sgordon};
94213317Sgordon
95213317Sgordonstruct _setT {
96213317Sgordon	long    setId;
97213317Sgordon	LIST_HEAD(msghead, _msgT) msghead;
98213317Sgordon	LIST_ENTRY(_setT) entries;
99213317Sgordon};
100213317Sgordon
101213317Sgordonstatic LIST_HEAD(sethead, _setT) sethead;
102213317Sgordonstatic struct _setT *curSet;
103213317Sgordon
104213317Sgordonstatic char *curline = NULL;
105213317Sgordonstatic long lineno = 0;
106213317Sgordon
107213317Sgordonstatic	char   *cskip(char *);
108213317Sgordonstatic	void	error(const char *);
109213317Sgordonstatic	char   *get_line(int);
110213317Sgordonstatic	char   *getmsg(int, char *, char);
111213317Sgordonstatic	void	warning(const char *, const char *);
112213317Sgordonstatic	char   *wskip(char *);
113213317Sgordonstatic	char   *xstrdup(const char *);
114213317Sgordonstatic	void   *xmalloc(size_t);
115222636Srustatic	void   *xrealloc(void *, size_t);
116213317Sgordon
117213317Sgordonvoid	MCParse(int);
118213317Sgordonvoid	MCReadCat(int);
119213317Sgordonvoid	MCWriteCat(int);
120213317Sgordonvoid	MCDelMsg(int);
121213317Sgordonvoid	MCAddMsg(int, const char *);
122213317Sgordonvoid	MCAddSet(int);
123213317Sgordonvoid	MCDelSet(int);
124213317Sgordonvoid	usage(void);
125213317Sgordonint	main(int, char **);
126213317Sgordon
127213317Sgordonvoid
128213317Sgordonusage(void)
129213317Sgordon{
130213317Sgordon	fprintf(stderr, "usage: %s catfile msgfile ...\n", getprogname());
131213317Sgordon	exit(1);
132213317Sgordon}
133213317Sgordon
134213317Sgordonint
135213317Sgordonmain(int argc, char **argv)
136213317Sgordon{
137213317Sgordon	int     ofd, ifd;
138213317Sgordon	char	*catfile = NULL;
139213317Sgordon	int     c;
140213317Sgordon
141213317Sgordon#define DEPRECATEDMSG	1
142213317Sgordon
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
184static void
185warning(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
200static void
201error(const char *msg)
202{
203	warning(NULL, msg);
204	exit(1);
205}
206
207static void *
208xmalloc(size_t len)
209{
210	void   *p;
211
212	if ((p = malloc(len)) == NULL)
213		NOMEM();
214	return (p);
215}
216
217static void *
218xrealloc(void *ptr, size_t size)
219{
220	if ((ptr = realloc(ptr, size)) == NULL)
221		NOMEM();
222	return (ptr);
223}
224
225static char *
226xstrdup(const char *str)
227{
228	char *nstr;
229
230	if ((nstr = strdup(str)) == NULL)
231		NOMEM();
232	return (nstr);
233}
234
235static char *
236get_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
278static char *
279wskip(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
290static char *
291cskip(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
302static char *
303getmsg(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
390void
391MCParse(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 */
476void
477MCWriteCat(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
581void
582MCAddSet(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
618void
619MCAddMsg(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
656void
657MCDelSet(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
679void
680MCDelMsg(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