1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1985-2010 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                   Phong Vo <kpv@research.att.com>                    *
20*                                                                      *
21***********************************************************************/
22#pragma prototyped
23
24/*
25 * Glenn Fowler
26 * AT&T Research
27 *
28 * machine independent binary message catalog implementation
29 */
30
31#include "sfhdr.h"
32#include "lclib.h"
33
34#include <iconv.h>
35
36#define _MC_PRIVATE_ \
37	size_t		nstrs; \
38	size_t		nmsgs; \
39	iconv_t		cvt; \
40	Sfio_t*		tmp; \
41	Vmalloc_t*	vm;
42
43#include <vmalloc.h>
44#include <error.h>
45#include <mc.h>
46#include <nl_types.h>
47
48/*
49 * find the binary message catalog path for <locale,catalog>
50 * result placed in path of size PATH_MAX
51 * pointer to path returned
52 * catalog==0 tests for category directory or file
53 * nls!=0 enables NLSPATH+LANG hack (not implemented yet)
54 */
55
56char*
57mcfind(char* path, const char* locale, const char* catalog, int category, int nls)
58{
59	register int		c;
60	register char*		s;
61	register char*		e;
62	register char*		p;
63	register const char*	v;
64	int			i;
65	int			first;
66	int			next;
67	int			last;
68	int			oerrno;
69	Lc_t*			lc;
70	char			file[PATH_MAX];
71	char*			paths[5];
72
73	static char		lc_messages[] = "LC_MESSAGES";
74
75	if ((category = lcindex(category, 1)) < 0)
76		return 0;
77	if (!(lc = locale ? lcmake(locale) : locales[category]))
78		return 0;
79	oerrno = errno;
80	if (catalog && *catalog == '/')
81	{
82		i = eaccess(catalog, R_OK);
83		errno = oerrno;
84		if (i)
85			return 0;
86		strncpy(path, catalog, PATH_MAX-1);
87		return path;
88	}
89	i = 0;
90#if !_lib_catopen
91	if ((p = getenv("NLSPATH")) && *p)
92		paths[i++] = p;
93#endif
94	paths[i++] = "share/lib/locale/%l/%C/%N";
95	paths[i++] = "share/locale/%l/%C/%N";
96	paths[i++] = "lib/locale/%l/%C/%N";
97	paths[i] = 0;
98	next = 1;
99	for (i = 0; p = paths[i]; i += next)
100	{
101		first = 1;
102		last = 0;
103		e = &file[elementsof(file) - 1];
104		while (*p)
105		{
106			s = file;
107			for (;;)
108			{
109				switch (c = *p++)
110				{
111				case 0:
112					p--;
113					break;
114				case ':':
115					break;
116				case '%':
117					if (s < e)
118					{
119						switch (c = *p++)
120						{
121						case 0:
122							p--;
123							continue;
124						case 'N':
125							v = catalog;
126							break;
127						case 'L':
128							if (first)
129							{
130								first = 0;
131								if (next)
132								{
133									v = lc->code;
134									if (lc->code != lc->language->code)
135										next = 0;
136								}
137								else
138								{
139									next = 1;
140									v = lc->language->code;
141								}
142							}
143							break;
144						case 'l':
145							v = lc->language->code;
146							break;
147						case 't':
148							v = lc->territory->code;
149							break;
150						case 'c':
151							v = lc->charset->code;
152							break;
153						case 'C':
154						case_C:
155							if (!catalog)
156								last = 1;
157							v = lc_categories[category].name;
158							break;
159						default:
160							*s++ = c;
161							continue;
162						}
163						if (v)
164							while (*v && s < e)
165								*s++ = *v++;
166					}
167					continue;
168				case '/':
169					if (last)
170						break;
171					if (category != AST_LC_MESSAGES && strneq(p, lc_messages, sizeof(lc_messages) - 1) && p[sizeof(lc_messages)-1] == '/')
172					{
173						p += sizeof(lc_messages) - 1;
174						goto case_C;
175					}
176					/*FALLTHROUGH*/
177				default:
178					if (s < e)
179						*s++ = c;
180					continue;
181				}
182				break;
183			}
184			if (s > file)
185				*s = 0;
186			else if (!catalog)
187				continue;
188			else
189				strncpy(file, catalog, elementsof(file));
190			if (ast.locale.set & AST_LC_find)
191				sfprintf(sfstderr, "locale find %s\n", file);
192			if (s = pathpath(path, file, "", (!catalog && category == AST_LC_MESSAGES) ? PATH_READ : (PATH_REGULAR|PATH_READ|PATH_ABSOLUTE)))
193			{
194				if (ast.locale.set & (AST_LC_find|AST_LC_setlocale))
195					sfprintf(sfstderr, "locale path %s\n", s);
196				errno = oerrno;
197				return s;
198			}
199		}
200	}
201	errno = oerrno;
202	return 0;
203}
204
205/*
206 * allocate and read the binary message catalog ip
207 * if ip==0 then space is allocated for mcput()
208 * 0 returned on any error
209 */
210
211Mc_t*
212mcopen(register Sfio_t* ip)
213{
214	register Mc_t*		mc;
215	register char**		mp;
216	register char*		sp;
217	Vmalloc_t*		vm;
218	char*			rp;
219	int			i;
220	int			j;
221	int			oerrno;
222	size_t			n;
223	char			buf[MC_MAGIC_SIZE];
224
225	oerrno = errno;
226	if (ip)
227	{
228		/*
229		 * check the magic
230		 */
231
232		if (sfread(ip, buf, MC_MAGIC_SIZE) != MC_MAGIC_SIZE)
233		{
234			errno = oerrno;
235			return 0;
236		}
237		if (memcmp(buf, MC_MAGIC, MC_MAGIC_SIZE))
238			return 0;
239	}
240
241	/*
242	 * allocate the region
243	 */
244
245	if (!(vm = vmopen(Vmdcheap, Vmbest, 0)) || !(mc = vmnewof(vm, 0, Mc_t, 1, 0)))
246	{
247		errno = oerrno;
248		return 0;
249	}
250	mc->vm = vm;
251	mc->cvt = (iconv_t)(-1);
252	if (ip)
253	{
254		/*
255		 * read the translation record
256		 */
257
258		if (!(sp = sfgetr(ip, 0, 0)) || !(mc->translation = vmstrdup(vm, sp)))
259			goto bad;
260
261		/*
262		 * read the optional header records
263		 */
264
265		do
266		{
267			if (!(sp = sfgetr(ip, 0, 0)))
268				goto bad;
269		} while (*sp);
270
271		/*
272		 * get the component dimensions
273		 */
274
275		mc->nstrs = sfgetu(ip);
276		mc->nmsgs = sfgetu(ip);
277		mc->num = sfgetu(ip);
278		if (sfeof(ip))
279			goto bad;
280	}
281	else if (!(mc->translation = vmnewof(vm, 0, char, 1, 0)))
282		goto bad;
283
284	/*
285	 * allocate the remaining space
286	 */
287
288	if (!(mc->set = vmnewof(vm, 0, Mcset_t, mc->num + 1, 0)))
289		goto bad;
290	if (!ip)
291		return mc;
292	if (!(mp = vmnewof(vm, 0, char*, mc->nmsgs + mc->num + 1, 0)))
293		goto bad;
294	if (!(rp = sp = vmalloc(vm, mc->nstrs + 1)))
295		goto bad;
296
297	/*
298	 * get the set dimensions and initialize the msg pointers
299	 */
300
301	while (i = sfgetu(ip))
302	{
303		if (i > mc->num)
304			goto bad;
305		n = sfgetu(ip);
306		mc->set[i].num = n;
307		mc->set[i].msg = mp;
308		mp += n + 1;
309	}
310
311	/*
312	 * read the msg sizes and set up the msg pointers
313	 */
314
315	for (i = 1; i <= mc->num; i++)
316		for (j = 1; j <= mc->set[i].num; j++)
317			if (n = sfgetu(ip))
318			{
319				mc->set[i].msg[j] = sp;
320				sp += n;
321			}
322
323	/*
324	 * read the string table
325	 */
326
327	if (sfread(ip, rp, mc->nstrs) != mc->nstrs || sfgetc(ip) != EOF)
328		goto bad;
329	if (!(mc->tmp = sfstropen()))
330		goto bad;
331	mc->cvt = iconv_open("", "utf");
332	errno = oerrno;
333	return mc;
334 bad:
335	vmclose(vm);
336	errno = oerrno;
337	return 0;
338}
339
340/*
341 * return the <set,num> message in mc
342 * msg returned on error
343 * utf message text converted to ucs
344 */
345
346char*
347mcget(register Mc_t* mc, int set, int num, const char* msg)
348{
349	char*		s;
350	size_t		n;
351	int		p;
352
353	if (!mc || set < 0 || set > mc->num || num < 1 || num > mc->set[set].num || !(s = mc->set[set].msg[num]))
354		return (char*)msg;
355	if (mc->cvt == (iconv_t)(-1))
356		return s;
357	if ((p = sfstrtell(mc->tmp)) > sfstrsize(mc->tmp) / 2)
358	{
359		p = 0;
360		sfstrseek(mc->tmp, p, SEEK_SET);
361	}
362	n = strlen(s) + 1;
363	iconv_write(mc->cvt, mc->tmp, &s, &n, NiL);
364	return sfstrbase(mc->tmp) + p;
365}
366
367/*
368 * set message <set,num> to msg
369 * msg==0 deletes the message
370 * the message and set counts are adjusted
371 * 0 returned on success, -1 otherwise
372 */
373
374int
375mcput(register Mc_t* mc, int set, int num, const char* msg)
376{
377	register int		i;
378	register char*		s;
379	register Mcset_t*	sp;
380	register char**		mp;
381
382	/*
383	 * validate the arguments
384	 */
385
386	if (!mc || set > MC_SET_MAX || num > MC_NUM_MAX)
387		return -1;
388
389	/*
390	 * deletions don't kick in allocations (duh)
391	 */
392
393	if (!msg)
394	{
395		if (set <= mc->num && num <= mc->set[set].num && (s = mc->set[set].msg[num]))
396		{
397			/*
398			 * decrease the string table size
399			 */
400
401			mc->set[set].msg[num] = 0;
402			mc->nstrs -= strlen(s) + 1;
403			if (mc->set[set].num == num)
404			{
405				/*
406				 * decrease the max msg num
407				 */
408
409				mp = mc->set[set].msg + num;
410				while (num && !mp[--num]);
411				mc->nmsgs -= mc->set[set].num - num;
412				if (!(mc->set[set].num = num) && mc->num == set)
413				{
414					/*
415					 * decrease the max set num
416					 */
417
418					while (num && !mc->set[--num].num);
419					mc->num = num;
420				}
421			}
422		}
423		return 0;
424	}
425
426	/*
427	 * keep track of the highest set and allocate if necessary
428	 */
429
430	if (set > mc->num)
431	{
432		if (set > mc->gen)
433		{
434			i = MC_SET_MAX;
435			if (!(sp = vmnewof(mc->vm, 0, Mcset_t, i + 1, 0)))
436				return -1;
437			mc->gen = i;
438			for (i = 1; i <= mc->num; i++)
439				sp[i] = mc->set[i];
440			mc->set = sp;
441		}
442		mc->num = set;
443	}
444	sp = mc->set + set;
445
446	/*
447	 * keep track of the highest msg and allocate if necessary
448	 */
449
450	if (num > sp->num)
451	{
452		if (num > sp->gen)
453		{
454			if (!mc->gen)
455			{
456				i = (MC_NUM_MAX + 1) / 32;
457				if (i <= num)
458					i = 2 * num;
459				if (i > MC_NUM_MAX)
460					i = MC_NUM_MAX;
461				if (!(mp = vmnewof(mc->vm, 0, char*, i + 1, 0)))
462					return -1;
463				mc->gen = i;
464				sp->msg = mp;
465				for (i = 1; i <= sp->num; i++)
466					mp[i] = sp->msg[i];
467			}
468			else
469			{
470				i = 2 * mc->gen;
471				if (i > MC_NUM_MAX)
472					i = MC_NUM_MAX;
473				if (!(mp = vmnewof(mc->vm, sp->msg, char*, i + 1, 0)))
474					return -1;
475				sp->gen = i;
476				sp->msg = mp;
477			}
478		}
479		mc->nmsgs += num - sp->num;
480		sp->num = num;
481	}
482
483	/*
484	 * decrease the string table size
485	 */
486
487	if (s = sp->msg[num])
488	{
489		/*
490		 * no-op if no change
491		 */
492
493		if (streq(s, msg))
494			return 0;
495		mc->nstrs -= strlen(s) + 1;
496	}
497
498	/*
499	 * allocate, add and adjust the string table size
500	 */
501
502	if (!(s = vmstrdup(mc->vm, msg)))
503		return -1;
504	sp->msg[num] = s;
505	mc->nstrs += strlen(s) + 1;
506	return 0;
507}
508
509/*
510 * dump message catalog mc to op
511 * 0 returned on success, -1 otherwise
512 */
513
514int
515mcdump(register Mc_t* mc, register Sfio_t* op)
516{
517	register int		i;
518	register int		j;
519	register int		n;
520	register char*		s;
521	register Mcset_t*	sp;
522
523	/*
524	 * write the magic
525	 */
526
527	if (sfwrite(op, MC_MAGIC, MC_MAGIC_SIZE) != MC_MAGIC_SIZE)
528		return -1;
529
530	/*
531	 * write the translation record
532	 */
533
534	sfputr(op, mc->translation, 0);
535
536	/* optional header records here */
537
538	/*
539	 * end of optional header records
540	 */
541
542	sfputu(op, 0);
543
544	/*
545	 * write the global dimensions
546	 */
547
548	sfputu(op, mc->nstrs);
549	sfputu(op, mc->nmsgs);
550	sfputu(op, mc->num);
551
552	/*
553	 * write the set dimensions
554	 */
555
556	for (i = 1; i <= mc->num; i++)
557		if (mc->set[i].num)
558		{
559			sfputu(op, i);
560			sfputu(op, mc->set[i].num);
561		}
562	sfputu(op, 0);
563
564	/*
565	 * write the message sizes
566	 */
567
568	for (i = 1; i <= mc->num; i++)
569		if (mc->set[i].num)
570		{
571			sp = mc->set + i;
572			for (j = 1; j <= sp->num; j++)
573			{
574				n = (s = sp->msg[j]) ? (strlen(s) + 1) : 0;
575				sfputu(op, n);
576			}
577		}
578
579	/*
580	 * write the string table
581	 */
582
583	for (i = 1; i <= mc->num; i++)
584		if (mc->set[i].num)
585		{
586			sp = mc->set + i;
587			for (j = 1; j <= sp->num; j++)
588				if (s = sp->msg[j])
589					sfputr(op, s, 0);
590		}
591
592	/*
593	 * sync and return
594	 */
595
596	return sfsync(op);
597}
598
599/*
600 * parse <set,msg> number from s
601 * e!=0 is set to the next char after the parse
602 * set!=0 is set to message set number
603 * msg!=0 is set to message number
604 * the message set number is returned
605 *
606 * the base 36 hash gives reasonable values for these:
607 *
608 *	"ast" : ((((36#a^36#s^36#t)-9)&63)+1) = 3
609 *	"gnu" : ((((36#g^36#n^36#u)-9)&63)+1) = 17
610 *	"sgi" : ((((36#s^36#g^36#i)-9)&63)+1) = 22
611 *	"sun" : ((((36#s^36#u^36#n)-9)&63)+1) = 13
612 */
613
614int
615mcindex(register const char* s, char** e, int* set, int* msg)
616{
617	register int		c;
618	register int		m;
619	register int		n;
620	register int		r;
621	register unsigned char*	cv;
622	char*			t;
623
624	m = 0;
625	n = strtol(s, &t, 0);
626	if (t == (char*)s)
627	{
628		SFCVINIT();
629		cv = _Sfcv36;
630		for (n = m = 0; (c = cv[*s]) < 36; s++)
631		{
632			m++;
633			n ^= c;
634		}
635		m = (m <= 3) ? 63 : ((1 << (m + 3)) - 1);
636		n = ((n - 9) & m) + 1;
637	}
638	else
639		s = (const char*)t;
640	r = n;
641	if (*s)
642		m = strtol(s + 1, e, 0);
643	else
644	{
645		if (e)
646			*e = (char*)s;
647		if (m)
648			m = 0;
649		else
650		{
651			m = n;
652			n = 1;
653		}
654	}
655	if (set)
656		*set = n;
657	if (msg)
658		*msg = m;
659	return r;
660}
661
662/*
663 * close the message catalog mc
664 */
665
666int
667mcclose(register Mc_t* mc)
668{
669	if (!mc)
670		return -1;
671	if (mc->tmp)
672		sfclose(mc->tmp);
673	if (mc->cvt != (iconv_t)(-1))
674		iconv_close(mc->cvt);
675	vmclose(mc->vm);
676	return 0;
677}
678