1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1985-2012 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
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  * AT&T Research and SCO
26  * ast l10n message translation
27  */
28 
29 #include "lclib.h"
30 
31 #include <cdt.h>
32 #include <error.h>
33 #include <mc.h>
34 #include <nl_types.h>
35 
36 #ifndef DEBUG_trace
37 #define DEBUG_trace		0
38 #endif
39 
40 #define NOCAT			((nl_catd)-1)
41 #define GAP			100
42 
43 typedef	struct
44 {
45 	Dtlink_t	link;		/* dictionary link		*/
46 	Dt_t*		messages;	/* message dictionary handle	*/
47 	nl_catd		cat;		/* message catalog handle	*/
48 	int		debug;		/* special debug locale		*/
49 	const char*	locale;		/* message catalog locale	*/
50 	const char*	nlspath;	/* message catalog NLSPATH	*/
51 	char		name[1];	/* catalog name			*/
52 } Catalog_t;
53 
54 typedef struct
55 {
56 	Dtlink_t	link;		/* dictionary link		*/
57 	Catalog_t*	cat;		/* current catalog pointer	*/
58 	int		set;		/* set number			*/
59 	int		seq;		/* sequence number		*/
60 	char		text[1];	/* message text			*/
61 } Message_t;
62 
63 typedef struct
64 {
65 	Sfio_t*		sp;		/* temp string stream		*/
66 	int		off;		/* string base offset		*/
67 } Temp_t;
68 
69 typedef struct
70 {
71 	Dtdisc_t	message_disc;	/* message dict discipline	*/
72 	Dtdisc_t	catalog_disc;	/* catalog dict discipline	*/
73 	Dt_t*		catalogs;	/* catalog dictionary handle	*/
74 	Sfio_t*		tmp;		/* temporary string stream	*/
75 	int		error;		/* no dictionaries!		*/
76 	char		null[1];	/* null string			*/
77 } State_t;
78 
79 static State_t	state =
80 {
81 	{	offsetof(Message_t, text),	0,	0	},
82 	{	offsetof(Catalog_t, name),	0,	0	},
83 };
84 
85 static int
tempget(Sfio_t * sp)86 tempget(Sfio_t* sp)
87 {
88 	if (sfstrtell(sp) > sfstrsize(sp) / 2)
89 		sfstrseek(sp, 0, SEEK_SET);
90 	return sfstrtell(sp);
91 }
92 
93 static char*
tempuse(Sfio_t * sp,int off)94 tempuse(Sfio_t* sp, int off)
95 {
96 	sfputc(sp, 0);
97 	return sfstrbase(sp) + off;
98 }
99 
100 /*
101  * add msg to dict
102  */
103 
104 static int
entry(Dt_t * dict,int set,int seq,const char * msg)105 entry(Dt_t* dict, int set, int seq, const char* msg)
106 {
107 	Message_t*	mp;
108 
109 	if (!(mp = newof(0, Message_t, 1, strlen(msg))))
110 		return 0;
111 	strcpy(mp->text, msg);
112 	mp->set = set;
113 	mp->seq = seq;
114 	if (!dtinsert(dict, mp))
115 	{
116 		free(mp);
117 		return 0;
118 	}
119 #if DEBUG_trace > 1
120 sfprintf(sfstderr, "AHA#%d:%s set %d seq %d msg `%s'\n", __LINE__, __FILE__, set, seq, msg);
121 #endif
122 	return 1;
123 }
124 
125 /*
126  * find catalog in locale and return catopen() descriptor
127  */
128 
129 static nl_catd
find(const char * locale,const char * catalog)130 find(const char* locale, const char* catalog)
131 {
132 	char*		o;
133 	nl_catd		d;
134 	char		path[PATH_MAX];
135 
136 	if (!mcfind(locale, catalog, LC_MESSAGES, 0, path, sizeof(path)) || (d = catopen(path, NL_CAT_LOCALE)) == NOCAT)
137 	{
138 		if (locale == (const char*)lc_categories[AST_LC_MESSAGES].prev)
139 			o = 0;
140 		else if (o = setlocale(LC_MESSAGES, NiL))
141 		{
142 			ast.locale.set |= AST_LC_internal;
143 			setlocale(LC_MESSAGES, locale);
144 		}
145 		d = catopen(catalog, NL_CAT_LOCALE);
146 		if (o)
147 		{
148 			setlocale(LC_MESSAGES, o);
149 			ast.locale.set &= ~AST_LC_internal;
150 		}
151 	}
152 	return d;
153 }
154 
155 /*
156  * initialize the catalog s by loading in the default locale messages
157  */
158 
159 static Catalog_t*
init(register char * s)160 init(register char* s)
161 {
162 	register Catalog_t*	cp;
163 	register int		n;
164 	register int		m;
165 	register int		set;
166 	nl_catd			d;
167 
168 	/*
169 	 * insert into the catalog dictionary
170 	 */
171 
172 	if (!(cp = newof(0, Catalog_t, 1, strlen(s))))
173 		return 0;
174 	strcpy(cp->name, s);
175 	if (!dtinsert(state.catalogs, cp))
176 	{
177 		free(cp);
178 		return 0;
179 	}
180 	cp->cat = NOCAT;
181 
182 	/*
183 	 * locate the default locale catalog
184 	 */
185 
186 	if ((d = find("C", s)) != NOCAT)
187 	{
188 		/*
189 		 * load the default locale messages
190 		 * this assumes one mesage set for ast (AST_MESSAGE_SET or fallback to 1)
191 		 * different packages can share the same message catalog
192 		 * name by using different message set numbers
193 		 * see <mc.h> mcindex()
194 		 *
195 		 * this method requires a scan of each catalog, and the
196 		 * catalogs do not advertise the max message number, so
197 		 * we assume there are no messages after a gap of GAP
198 		 * missing messages
199 		 */
200 
201 		if (cp->messages = dtopen(&state.message_disc, Dtset))
202 		{
203 			n = m = 0;
204 			for (;;)
205 			{
206 				n++;
207 				if (((s = catgets(d, set = AST_MESSAGE_SET, n, state.null)) && *s || (s = catgets(d, set = 1, n, state.null)) && *s) && entry(cp->messages, set, n, s))
208 					m = n;
209 				else if ((n - m) > GAP)
210 					break;
211 			}
212 			if (!m)
213 			{
214 				dtclose(cp->messages);
215 				cp->messages = 0;
216 			}
217 		}
218 		catclose(d);
219 	}
220 	return cp;
221 }
222 
223 /*
224  * return the C locale message pointer for msg in cat
225  * cat may be a : separated list of candidate names
226  */
227 
228 static Message_t*
match(const char * cat,const char * msg)229 match(const char* cat, const char* msg)
230 {
231 	register char*	s;
232 	register char*	t;
233 	Catalog_t*	cp;
234 	Message_t*	mp;
235 	size_t		n;
236 
237 	char		buf[1024];
238 
239 	s = (char*)cat;
240 	for (;;)
241 	{
242 		if (t = strchr(s, ':'))
243 		{
244 			if (s == (char*)cat)
245 			{
246 				if ((n = strlen(s)) >= sizeof(buf))
247 					n = sizeof(buf) - 1;
248 				s = (char*)memcpy(buf, s, n);
249 				s[n] = 0;
250 				t = strchr(s, ':');
251 			}
252 			*t = 0;
253 		}
254 		if (*s && ((cp = (Catalog_t*)dtmatch(state.catalogs, s)) || (cp = init(s))) && cp->messages && (mp = (Message_t*)dtmatch(cp->messages, msg)))
255 		{
256 			mp->cat = cp;
257 			return mp;
258 		}
259 		if (!t)
260 			break;
261 		s = t + 1;
262 	}
263 	return 0;
264 }
265 
266 /*
267  * translate() is called with four arguments:
268  *
269  *	loc	the LC_MESSAGES locale name
270  *	cmd	the calling command name
271  *	cat	the catalog name, possibly a : separated list
272  *		"libFOO"	FOO library messages
273  *		"libshell"	ksh command messages
274  *		"SCRIPT"	script SCRIPT application messages
275  *	msg	message text to be translated
276  *
277  * the translated message text is returned on success
278  * otherwise the original msg is returned
279  *
280  * The first time translate() is called (for a non-C locale)
281  * it creates the state.catalogs dictionary. A dictionary entry
282  * (Catalog_t) is made each time translate() is called with a new
283  * cmd:cat argument.
284  *
285  * The X/Open interface catgets() is used to obtain a translated
286  * message. Its arguments include the message catalog name
287  * and the set/sequence numbers within the catalog. An additional
288  * dictionary, with entries of type Message_t, is needed for
289  * mapping untranslated message strings to the set/sequence numbers
290  * needed by catgets().  A separate Message_t dictionary is maintained
291  * for each Catalog_t.
292  */
293 
294 char*
translate(const char * loc,const char * cmd,const char * cat,const char * msg)295 translate(const char* loc, const char* cmd, const char* cat, const char* msg)
296 {
297 	register char*	r;
298 	char*		t;
299 	int		p;
300 	int		oerrno;
301 	Catalog_t*	cp;
302 	Message_t*	mp;
303 
304 	static uint32_t	serial;
305 	static char*	nlspath;
306 
307 	oerrno = errno;
308 	r = (char*)msg;
309 
310 	/*
311 	 * quick out
312 	 */
313 
314 	if (!cmd && !cat)
315 		goto done;
316 	if (cmd && (t = strrchr(cmd, '/')))
317 		cmd = (const char*)(t + 1);
318 
319 	/*
320 	 * initialize the catalogs dictionary
321 	 */
322 
323 	if (!state.catalogs)
324 	{
325 		if (state.error)
326 			goto done;
327 		if (!(state.tmp = sfstropen()))
328 		{
329 			state.error = 1;
330 			goto done;
331 		}
332 		if (!(state.catalogs = dtopen(&state.catalog_disc, Dtset)))
333 		{
334 			sfclose(state.tmp);
335 			state.error = 1;
336 			goto done;
337 		}
338 	}
339 
340 	/*
341 	 * get the message
342 	 * or do we have to spell it out for you
343 	 */
344 
345 	if ((!cmd || !(mp = match(cmd, msg))) &&
346 	    (!cat || !(mp = match(cat, msg))) &&
347 	    (!error_info.catalog || !(mp = match(error_info.catalog, msg))) &&
348 	    (!ast.id || !(mp = match(ast.id, msg))) ||
349 	     !(cp = mp->cat))
350 	{
351 #if DEBUG_trace > 1
352 sfprintf(sfstderr, "AHA#%d:%s cmd %s cat %s:%s id %s msg `%s'\n", __LINE__, __FILE__, cmd, cat, error_info.catalog, ast.id, msg);
353 #endif
354 		cp = 0;
355 		goto done;
356 	}
357 
358 	/*
359 	 * adjust for the current locale
360 	 */
361 
362 #if DEBUG_trace
363 sfprintf(sfstderr, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__, __FILE__, cp->locale, cp->locale, loc, loc);
364 #endif
365 	if (serial != ast.env_serial)
366 	{
367 		serial = ast.env_serial;
368 		nlspath = getenv("NLSPATH");
369 	}
370 	if (cp->locale != loc || cp->nlspath != nlspath)
371 	{
372 		cp->locale = loc;
373 		cp->nlspath = nlspath;
374 		if (cp->cat != NOCAT)
375 			catclose(cp->cat);
376 		if ((cp->cat = find(cp->locale, cp->name)) == NOCAT)
377 			cp->debug = streq(cp->locale, "debug");
378 		else
379 			cp->debug = 0;
380 #if DEBUG_trace
381 sfprintf(sfstderr, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__, __FILE__, cp->cat, cp->debug, NOCAT);
382 #endif
383 	}
384 	if (cp->cat == NOCAT)
385 	{
386 		if (cp->debug)
387 		{
388 			p = tempget(state.tmp);
389 			sfprintf(state.tmp, "(%s,%d,%d)", cp->name, mp->set, mp->seq);
390 			r = tempuse(state.tmp, p);
391 		}
392 		else if (ast.locale.set & AST_LC_debug)
393 		{
394 			p = tempget(state.tmp);
395 			sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r);
396 			r = tempuse(state.tmp, p);
397 		}
398 	}
399 	else
400 	{
401 		/*
402 		 * get the translated message
403 		 */
404 
405 		r = catgets(cp->cat, mp->set, mp->seq, msg);
406 		if (r != (char*)msg)
407 		{
408 			if (streq(r, (char*)msg))
409 				r = (char*)msg;
410 			else if (strcmp(fmtfmt(r), fmtfmt(msg)))
411 			{
412 				sfprintf(sfstderr, "locale %s catalog %s message %d.%d \"%s\" does not match \"%s\"\n", cp->locale, cp->name, mp->set, mp->seq, r, msg);
413 				r = (char*)msg;
414 			}
415 		}
416 		if (ast.locale.set & AST_LC_debug)
417 		{
418 			p = tempget(state.tmp);
419 			sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r);
420 			r = tempuse(state.tmp, p);
421 		}
422 	}
423 	if (ast.locale.set & AST_LC_translate)
424 		sfprintf(sfstderr, "translate locale=%s catalog=%s set=%d seq=%d \"%s\" => \"%s\"\n", cp->locale, cp->name, mp->set, mp->seq, msg, r == (char*)msg ? "NOPE" : r);
425  done:
426 	if (r == (char*)msg && (!cp && streq(loc, "debug") || cp && cp->debug))
427 	{
428 		p = tempget(state.tmp);
429 		sfprintf(state.tmp, "(%s,%s,%s,%s)", loc, cmd, cat, r);
430 		r = tempuse(state.tmp, p);
431 	}
432 	errno = oerrno;
433 	return r;
434 }
435