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