xref: /netbsd/usr.bin/locale/locale.c (revision f87e6eb1)
1 /*	$NetBSD: locale.c,v 1.5 2006/02/16 19:19:49 tnozaki Exp $	*/
2 
3 /*-
4  * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  * FreeBSD: src/usr.bin/locale/locale.c,v 1.10 2003/06/26 11:05:56 phantom Exp
29  */
30 
31 #include <sys/cdefs.h>
32 #if defined(LIBC_SCCS) && !defined(lint)
33 __RCSID("$NetBSD: locale.c,v 1.5 2006/02/16 19:19:49 tnozaki Exp $");
34 #endif /* LIBC_SCCS and not lint */
35 
36 /*
37  * XXX: implement missing era_* (LC_TIME) keywords (require libc &
38  *	nl_langinfo(3) extensions)
39  *
40  * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
41  *      localedef(1) implementation).  Currently it's handled via
42  *	nl_langinfo(CODESET).
43  */
44 
45 #include <sys/types.h>
46 #include <assert.h>
47 #include <dirent.h>
48 #include <err.h>
49 #include <locale.h>
50 #include <langinfo.h>
51 #include <limits.h>
52 #include <paths.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <stringlist.h>
57 #include <unistd.h>
58 
59 #ifdef CITRUS
60 #include "citrus_namespace.h"
61 #include "citrus_region.h"
62 #include "citrus_lookup.h"
63 #endif
64 #include "rune.h"
65 
66 /* Local prototypes */
67 void	init_locales_list(void);
68 void	init_locales_list_alias(void);
69 void	list_charmaps(void);
70 void	list_locales(void);
71 const char *lookup_localecat(int);
72 char	*kwval_lconv(int);
73 int	kwval_lookup(char *, char **, int *, int *);
74 void	showdetails(char *);
75 void	showkeywordslist(void);
76 void	showlocale(void);
77 void	usage(void);
78 
79 /* Global variables */
80 static StringList *locales = NULL;
81 
82 int	all_locales = 0;
83 int	all_charmaps = 0;
84 int	prt_categories = 0;
85 int	prt_keywords = 0;
86 int	more_params = 0;
87 
88 struct _lcinfo {
89 	const char	*name;
90 	int		id;
91 } lcinfo [] = {
92 	{ "LC_CTYPE",		LC_CTYPE },
93 	{ "LC_COLLATE",		LC_COLLATE },
94 	{ "LC_TIME",		LC_TIME },
95 	{ "LC_NUMERIC",		LC_NUMERIC },
96 	{ "LC_MONETARY",	LC_MONETARY },
97 	{ "LC_MESSAGES",	LC_MESSAGES }
98 };
99 #define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
100 
101 /* ids for values not referenced by nl_langinfo() */
102 #define	KW_ZERO			10000
103 #define	KW_GROUPING		(KW_ZERO+1)
104 #define KW_INT_CURR_SYMBOL 	(KW_ZERO+2)
105 #define KW_CURRENCY_SYMBOL 	(KW_ZERO+3)
106 #define KW_MON_DECIMAL_POINT 	(KW_ZERO+4)
107 #define KW_MON_THOUSANDS_SEP 	(KW_ZERO+5)
108 #define KW_MON_GROUPING 	(KW_ZERO+6)
109 #define KW_POSITIVE_SIGN 	(KW_ZERO+7)
110 #define KW_NEGATIVE_SIGN 	(KW_ZERO+8)
111 #define KW_INT_FRAC_DIGITS 	(KW_ZERO+9)
112 #define KW_FRAC_DIGITS 		(KW_ZERO+10)
113 #define KW_P_CS_PRECEDES 	(KW_ZERO+11)
114 #define KW_P_SEP_BY_SPACE 	(KW_ZERO+12)
115 #define KW_N_CS_PRECEDES 	(KW_ZERO+13)
116 #define KW_N_SEP_BY_SPACE 	(KW_ZERO+14)
117 #define KW_P_SIGN_POSN 		(KW_ZERO+15)
118 #define KW_N_SIGN_POSN 		(KW_ZERO+16)
119 #define KW_INT_P_CS_PRECEDES 	(KW_ZERO+17)
120 #define KW_INT_P_SEP_BY_SPACE 	(KW_ZERO+18)
121 #define KW_INT_N_CS_PRECEDES 	(KW_ZERO+19)
122 #define KW_INT_N_SEP_BY_SPACE 	(KW_ZERO+20)
123 #define KW_INT_P_SIGN_POSN 	(KW_ZERO+21)
124 #define KW_INT_N_SIGN_POSN 	(KW_ZERO+22)
125 
126 struct _kwinfo {
127 	const char	*name;
128 	int		isstr;		/* true - string, false - number */
129 	int		catid;		/* LC_* */
130 	int		value_ref;
131 	const char	*comment;
132 } kwinfo [] = {
133 	{ "charmap",		1, LC_CTYPE,	CODESET, "" },	/* hack */
134 
135 	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR, "" },
136 	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP, "" },
137 	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING, "" },
138 	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR,
139 	  "Same as decimal_point (BSD only)" },			/* compat */
140 	{ "thousep",		1, LC_NUMERIC,	THOUSEP,
141 	  "Same as thousands_sep (BSD only)" },			/* compat */
142 
143 	{ "int_curr_symbol",	1, LC_MONETARY,	KW_INT_CURR_SYMBOL, "" },
144 	{ "currency_symbol",	1, LC_MONETARY,	KW_CURRENCY_SYMBOL, "" },
145 	{ "mon_decimal_point",	1, LC_MONETARY,	KW_MON_DECIMAL_POINT, "" },
146 	{ "mon_thousands_sep",	1, LC_MONETARY,	KW_MON_THOUSANDS_SEP, "" },
147 	{ "mon_grouping",	1, LC_MONETARY,	KW_MON_GROUPING, "" },
148 	{ "positive_sign",	1, LC_MONETARY,	KW_POSITIVE_SIGN, "" },
149 	{ "negative_sign",	1, LC_MONETARY,	KW_NEGATIVE_SIGN, "" },
150 
151 	{ "int_frac_digits",	0, LC_MONETARY,	KW_INT_FRAC_DIGITS, "" },
152 	{ "frac_digits",	0, LC_MONETARY,	KW_FRAC_DIGITS, "" },
153 	{ "p_cs_precedes",	0, LC_MONETARY,	KW_P_CS_PRECEDES, "" },
154 	{ "p_sep_by_space",	0, LC_MONETARY,	KW_P_SEP_BY_SPACE, "" },
155 	{ "n_cs_precedes",	0, LC_MONETARY,	KW_N_CS_PRECEDES, "" },
156 	{ "n_sep_by_space",	0, LC_MONETARY,	KW_N_SEP_BY_SPACE, "" },
157 	{ "p_sign_posn",	0, LC_MONETARY,	KW_P_SIGN_POSN, "" },
158 	{ "n_sign_posn",	0, LC_MONETARY,	KW_N_SIGN_POSN, "" },
159 	{ "int_p_cs_precedes",	0, LC_MONETARY,	KW_INT_P_CS_PRECEDES, "" },
160 	{ "int_p_sep_by_space",	0, LC_MONETARY,	KW_INT_P_SEP_BY_SPACE, "" },
161 	{ "int_n_cs_precedes",	0, LC_MONETARY,	KW_INT_N_CS_PRECEDES, "" },
162 	{ "int_n_sep_by_space",	0, LC_MONETARY,	KW_INT_N_SEP_BY_SPACE, "" },
163 	{ "int_p_sign_posn",	0, LC_MONETARY,	KW_INT_P_SIGN_POSN, "" },
164 	{ "int_n_sign_posn",	0, LC_MONETARY,	KW_INT_N_SIGN_POSN, "" },
165 
166 	{ "d_t_fmt",		1, LC_TIME,	D_T_FMT, "" },
167 	{ "d_fmt",		1, LC_TIME,	D_FMT, "" },
168 	{ "t_fmt",		1, LC_TIME,	T_FMT, "" },
169 	{ "am_str",		1, LC_TIME,	AM_STR, "" },
170 	{ "pm_str",		1, LC_TIME,	PM_STR, "" },
171 	{ "t_fmt_ampm",		1, LC_TIME,	T_FMT_AMPM, "" },
172 	{ "day_1",		1, LC_TIME,	DAY_1, "" },
173 	{ "day_2",		1, LC_TIME,	DAY_2, "" },
174 	{ "day_3",		1, LC_TIME,	DAY_3, "" },
175 	{ "day_4",		1, LC_TIME,	DAY_4, "" },
176 	{ "day_5",		1, LC_TIME,	DAY_5, "" },
177 	{ "day_6",		1, LC_TIME,	DAY_6, "" },
178 	{ "day_7",		1, LC_TIME,	DAY_7, "" },
179 	{ "abday_1",		1, LC_TIME,	ABDAY_1, "" },
180 	{ "abday_2",		1, LC_TIME,	ABDAY_2, "" },
181 	{ "abday_3",		1, LC_TIME,	ABDAY_3, "" },
182 	{ "abday_4",		1, LC_TIME,	ABDAY_4, "" },
183 	{ "abday_5",		1, LC_TIME,	ABDAY_5, "" },
184 	{ "abday_6",		1, LC_TIME,	ABDAY_6, "" },
185 	{ "abday_7",		1, LC_TIME,	ABDAY_7, "" },
186 	{ "mon_1",		1, LC_TIME,	MON_1, "" },
187 	{ "mon_2",		1, LC_TIME,	MON_2, "" },
188 	{ "mon_3",		1, LC_TIME,	MON_3, "" },
189 	{ "mon_4",		1, LC_TIME,	MON_4, "" },
190 	{ "mon_5",		1, LC_TIME,	MON_5, "" },
191 	{ "mon_6",		1, LC_TIME,	MON_6, "" },
192 	{ "mon_7",		1, LC_TIME,	MON_7, "" },
193 	{ "mon_8",		1, LC_TIME,	MON_8, "" },
194 	{ "mon_9",		1, LC_TIME,	MON_9, "" },
195 	{ "mon_10",		1, LC_TIME,	MON_10, "" },
196 	{ "mon_11",		1, LC_TIME,	MON_11, "" },
197 	{ "mon_12",		1, LC_TIME,	MON_12, "" },
198 	{ "abmon_1",		1, LC_TIME,	ABMON_1, "" },
199 	{ "abmon_2",		1, LC_TIME,	ABMON_2, "" },
200 	{ "abmon_3",		1, LC_TIME,	ABMON_3, "" },
201 	{ "abmon_4",		1, LC_TIME,	ABMON_4, "" },
202 	{ "abmon_5",		1, LC_TIME,	ABMON_5, "" },
203 	{ "abmon_6",		1, LC_TIME,	ABMON_6, "" },
204 	{ "abmon_7",		1, LC_TIME,	ABMON_7, "" },
205 	{ "abmon_8",		1, LC_TIME,	ABMON_8, "" },
206 	{ "abmon_9",		1, LC_TIME,	ABMON_9, "" },
207 	{ "abmon_10",		1, LC_TIME,	ABMON_10, "" },
208 	{ "abmon_11",		1, LC_TIME,	ABMON_11, "" },
209 	{ "abmon_12",		1, LC_TIME,	ABMON_12, "" },
210 	{ "era",		1, LC_TIME,	ERA, "(unavailable)" },
211 	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT, "(unavailable)" },
212 	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT, "(unavailable)" },
213 	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT, "(unavailable)" },
214 	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS, "" },
215 
216 	{ "yesexpr",		1, LC_MESSAGES, YESEXPR, "" },
217 	{ "noexpr",		1, LC_MESSAGES, NOEXPR, "" },
218 	{ "yesstr",		1, LC_MESSAGES, YESSTR,
219 	  "(POSIX legacy)" },					/* compat */
220 	{ "nostr",		1, LC_MESSAGES, NOSTR,
221 	  "(POSIX legacy)" }					/* compat */
222 
223 };
224 #define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
225 
226 int
227 main(int argc, char *argv[])
228 {
229 	int	ch;
230 	int	tmp;
231 
232 	while ((ch = getopt(argc, argv, "ackm")) != -1) {
233 		switch (ch) {
234 		case 'a':
235 			all_locales = 1;
236 			break;
237 		case 'c':
238 			prt_categories = 1;
239 			break;
240 		case 'k':
241 			prt_keywords = 1;
242 			break;
243 		case 'm':
244 			all_charmaps = 1;
245 			break;
246 		default:
247 			usage();
248 		}
249 	}
250 	argc -= optind;
251 	argv += optind;
252 
253 	/* validate arguments */
254 	if (all_locales && all_charmaps)
255 		usage();
256 	if ((all_locales || all_charmaps) && argc > 0)
257 		usage();
258 	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
259 		usage();
260 	if ((prt_categories || prt_keywords) && argc <= 0)
261 		usage();
262 
263 	/* process '-a' */
264 	if (all_locales) {
265 		list_locales();
266 		exit(0);
267 	}
268 
269 	/* process '-m' */
270 	if (all_charmaps) {
271 		list_charmaps();
272 		exit(0);
273 	}
274 
275 	/* check for special case '-k list' */
276 	tmp = 0;
277 	if (prt_keywords && argc > 0)
278 		while (tmp < argc)
279 			if (strcasecmp(argv[tmp++], "list") == 0) {
280 				showkeywordslist();
281 				exit(0);
282 			}
283 
284 	/* process '-c' and/or '-k' */
285 	if (prt_categories || prt_keywords || argc > 0) {
286 		setlocale(LC_ALL, "");
287 		while (argc > 0) {
288 			showdetails(*argv);
289 			argv++;
290 			argc--;
291 		}
292 		exit(0);
293 	}
294 
295 	/* no arguments, show current locale state */
296 	showlocale();
297 
298 	return (0);
299 }
300 
301 void
302 usage(void)
303 {
304 	printf("usage: locale [ -a | -m ]\n"
305                "       locale [ -ck ] name ...\n");
306 	exit(1);
307 }
308 
309 /*
310  * Output information about all available locales
311  *
312  * XXX actually output of this function does not guarantee that locale
313  *     is really available to application, since it can be broken or
314  *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
315  *     also validate these locales?
316  */
317 void
318 list_locales(void)
319 {
320 	size_t i;
321 
322 	init_locales_list();
323 	for (i = 0; i < locales->sl_cur; i++) {
324 		printf("%s\n", locales->sl_str[i]);
325 	}
326 }
327 
328 /*
329  * qsort() helper function
330  */
331 static int
332 scmp(const void *s1, const void *s2)
333 {
334 	return strcmp(*(const char **)s1, *(const char **)s2);
335 }
336 
337 /*
338  * Output information about all available charmaps
339  *
340  * XXX this function is doing a task in hackish way, i.e. by scaning
341  *     list of locales, spliting their codeset part and building list of
342  *     them.
343  */
344 void
345 list_charmaps(void)
346 {
347 	size_t i;
348 	char *s, *cs;
349 	StringList *charmaps;
350 
351 	/* initialize StringList */
352 	charmaps = sl_init();
353 	if (charmaps == NULL)
354 		err(1, "could not allocate memory");
355 
356 	/* fetch locales list */
357 	init_locales_list();
358 
359 	/* split codesets and build their list */
360 	for (i = 0; i < locales->sl_cur; i++) {
361 		s = locales->sl_str[i];
362 		if ((cs = strchr(s, '.')) != NULL) {
363 			cs++;
364 			if (sl_find(charmaps, cs) == NULL)
365 				sl_add(charmaps, cs);
366 		}
367 	}
368 
369 	/* add US-ASCII, if not yet added */
370 	if (sl_find(charmaps, "US-ASCII") == NULL)
371 		sl_add(charmaps, "US-ASCII");
372 
373 	/* sort the list */
374 	qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
375 
376 	/* print results */
377 	for (i = 0; i < charmaps->sl_cur; i++) {
378 		printf("%s\n", charmaps->sl_str[i]);
379 	}
380 }
381 
382 /*
383  * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
384  * environment variable is set)
385  */
386 void
387 init_locales_list(void)
388 {
389 	DIR *dirp;
390 	struct dirent *dp;
391 	char *s;
392 
393 	/* why call this function twice ? */
394 	if (locales != NULL)
395 		return;
396 
397 	/* initialize StringList */
398 	locales = sl_init();
399 	if (locales == NULL)
400 		err(1, "could not allocate memory");
401 
402 	/* get actual locales directory name */
403 	setlocale(LC_CTYPE, "C");
404 	if (_PathLocale == NULL)
405 		errx(1, "unable to find locales storage");
406 
407 	/* open locales directory */
408 	dirp = opendir(_PathLocale);
409 	if (dirp == NULL)
410 		err(1, "could not open directory '%s'", _PathLocale);
411 
412 	/* scan directory and store its contents except "." and ".." */
413 	while ((dp = readdir(dirp)) != NULL) {
414 		/* exclude "." and "..", _LOCALE_ALIAS_NAME */
415 		if ((dp->d_name[0] != '.' || (dp->d_name[1] != '\0' &&
416 		    (dp->d_name[1] != '.' ||  dp->d_name[2] != '\0'))) &&
417 		    strcmp(_LOCALE_ALIAS_NAME, dp->d_name) != 0) {
418 			s = strdup(dp->d_name);
419 			if (s == NULL)
420 				err(1, "could not allocate memory");
421 			sl_add(locales, s);
422 		}
423 	}
424 	closedir(dirp);
425 
426         /* make sure that 'POSIX' and 'C' locales are present in the list.
427 	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
428          * we also list 'C' for constistency
429          */
430 	if (sl_find(locales, "POSIX") == NULL)
431 		sl_add(locales, "POSIX");
432 
433 	if (sl_find(locales, "C") == NULL)
434 		sl_add(locales, "C");
435 
436 	init_locales_list_alias();
437 
438 	/* make output nicer, sort the list */
439 	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
440 }
441 
442 void
443 init_locales_list_alias(void)
444 {
445 	char aliaspath[PATH_MAX];
446 #ifdef CITRUS
447 	struct _lookup *hlookup;
448 	struct _region key, dat;
449 #else
450 	FILE *fp;
451 #endif
452 	size_t n;
453 	char *s, *t;
454 
455 	_DIAGASSERT(locales != NULL);
456 	_DIAGASSERT(_PathLocale != NULL);
457 
458 	(void)snprintf(aliaspath, sizeof(aliaspath),
459 		"%s/" _LOCALE_ALIAS_NAME, _PathLocale);
460 
461 #ifdef CITRUS
462 	if (_lookup_seq_open(&hlookup, aliaspath,
463 	    _LOOKUP_CASE_SENSITIVE) == 0) {
464 		while (_lookup_seq_next(hlookup, &key, &dat) == 0) {
465 			n = _region_size((const struct _region *)&key);
466 			s = _region_head((const struct _region *)&key);
467 			for (t = s; n > 0 && *s!= '/'; --n, ++s);
468 #else
469 	fp = fopen(aliaspath, "r");
470 	if (fp != NULL) {
471 		while ((s = fgetln(fp, &n)) != NULL) {
472 			_DIAGASSERT(n > 0);
473 			if (*s == '#' || *s == '\n')
474 				continue;
475 			for (t = s; n > 0 && strchr("/ \t\n", *s) == NULL;
476 			    --n, ++s);
477 #endif
478 			n = (size_t)(s - t);
479 			s = malloc(n);
480 			if (s == NULL)
481 				err(1, "could not allocate memory");
482 			memcpy(s, t, n);
483 			s[n] = '\0';
484 			if (sl_find(locales, s) == NULL)
485 				sl_add(locales, s);
486 			else
487 				free(s);
488 		}
489 #ifdef CITRUS
490 		_lookup_seq_close(hlookup);
491 #else
492 		fclose(fp);
493 #endif
494 	}
495 }
496 
497 /*
498  * Show current locale status, depending on environment variables
499  */
500 void
501 showlocale(void)
502 {
503 	size_t	i;
504 	const char *lang, *vval, *eval;
505 
506 	setlocale(LC_ALL, "");
507 
508 	lang = getenv("LANG");
509 	if (lang == NULL) {
510 		lang = "";
511 	}
512 	printf("LANG=\"%s\"\n", lang);
513 	/* XXX: if LANG is null, then set it to "C" to get implied values? */
514 
515 	for (i = 0; i < NLCINFO; i++) {
516 		vval = setlocale(lcinfo[i].id, NULL);
517 		eval = getenv(lcinfo[i].name);
518 		if (eval != NULL && !strcmp(eval, vval)
519 				&& strcmp(lang, vval)) {
520 			/*
521 			 * Appropriate environment variable set, its value
522 			 * is valid and not overriden by LC_ALL
523 			 *
524 			 * XXX: possible side effect: if both LANG and
525 			 * overriden environment variable are set into same
526 			 * value, then it'll be assumed as 'implied'
527 			 */
528 			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
529 		} else {
530 			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
531 		}
532 	}
533 
534 	vval = getenv("LC_ALL");
535 	if (vval == NULL) {
536 		vval = "";
537 	}
538 	printf("LC_ALL=\"%s\"\n", vval);
539 }
540 
541 /*
542  * keyword value lookup helper (via localeconv())
543  */
544 char *
545 kwval_lconv(int id)
546 {
547 	struct lconv *lc;
548 	char *rval;
549 
550 	rval = NULL;
551 	lc = localeconv();
552 	switch (id) {
553 		case KW_GROUPING:
554 			rval = lc->grouping;
555 			break;
556 		case KW_INT_CURR_SYMBOL:
557 			rval = lc->int_curr_symbol;
558 			break;
559 		case KW_CURRENCY_SYMBOL:
560 			rval = lc->currency_symbol;
561 			break;
562 		case KW_MON_DECIMAL_POINT:
563 			rval = lc->mon_decimal_point;
564 			break;
565 		case KW_MON_THOUSANDS_SEP:
566 			rval = lc->mon_thousands_sep;
567 			break;
568 		case KW_MON_GROUPING:
569 			rval = lc->mon_grouping;
570 			break;
571 		case KW_POSITIVE_SIGN:
572 			rval = lc->positive_sign;
573 			break;
574 		case KW_NEGATIVE_SIGN:
575 			rval = lc->negative_sign;
576 			break;
577 		case KW_INT_FRAC_DIGITS:
578 			rval = &(lc->int_frac_digits);
579 			break;
580 		case KW_FRAC_DIGITS:
581 			rval = &(lc->frac_digits);
582 			break;
583 		case KW_P_CS_PRECEDES:
584 			rval = &(lc->p_cs_precedes);
585 			break;
586 		case KW_P_SEP_BY_SPACE:
587 			rval = &(lc->p_sep_by_space);
588 			break;
589 		case KW_N_CS_PRECEDES:
590 			rval = &(lc->n_cs_precedes);
591 			break;
592 		case KW_N_SEP_BY_SPACE:
593 			rval = &(lc->n_sep_by_space);
594 			break;
595 		case KW_P_SIGN_POSN:
596 			rval = &(lc->p_sign_posn);
597 			break;
598 		case KW_N_SIGN_POSN:
599 			rval = &(lc->n_sign_posn);
600 			break;
601 		case KW_INT_P_CS_PRECEDES:
602 			rval = &(lc->int_p_cs_precedes);
603 			break;
604 		case KW_INT_P_SEP_BY_SPACE:
605 			rval = &(lc->int_p_sep_by_space);
606 			break;
607 		case KW_INT_N_CS_PRECEDES:
608 			rval = &(lc->int_n_cs_precedes);
609 			break;
610 		case KW_INT_N_SEP_BY_SPACE:
611 			rval = &(lc->int_n_sep_by_space);
612 			break;
613 		case KW_INT_P_SIGN_POSN:
614 			rval = &(lc->int_p_sign_posn);
615 			break;
616 		case KW_INT_N_SIGN_POSN:
617 			rval = &(lc->int_n_sign_posn);
618 			break;
619 		default:
620 			break;
621 	}
622 	return (rval);
623 }
624 
625 /*
626  * keyword value and properties lookup
627  */
628 int
629 kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
630 {
631 	int	rval;
632 	size_t	i;
633 
634 	rval = 0;
635 	for (i = 0; i < NKWINFO; i++) {
636 		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
637 			rval = 1;
638 			*cat = kwinfo[i].catid;
639 			*isstr = kwinfo[i].isstr;
640 			if (kwinfo[i].value_ref < KW_ZERO) {
641 				*kwval = nl_langinfo(kwinfo[i].value_ref);
642 			} else {
643 				*kwval = kwval_lconv(kwinfo[i].value_ref);
644 			}
645 			break;
646 		}
647 	}
648 
649 	return (rval);
650 }
651 
652 /*
653  * Show details about requested keyword according to '-k' and/or '-c'
654  * command line options specified.
655  */
656 void
657 showdetails(char *kw)
658 {
659 	int	isstr, cat, tmpval;
660 	char	*kwval;
661 
662 	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
663 		/*
664 		 * invalid keyword specified.
665 		 * XXX: any actions?
666 		 */
667 		return;
668 	}
669 
670 	if (prt_categories) {
671 		printf("%s\n", lookup_localecat(cat));
672 	}
673 
674 	if (prt_keywords) {
675 		if (isstr) {
676 			printf("%s=\"%s\"\n", kw, kwval);
677 		} else {
678 			tmpval = (char) *kwval;
679 			printf("%s=%d\n", kw, tmpval);
680 		}
681 	}
682 
683 	if (!prt_categories && !prt_keywords) {
684 		if (isstr) {
685 			printf("%s\n", kwval);
686 		} else {
687 			tmpval = (char) *kwval;
688 			printf("%d\n", tmpval);
689 		}
690 	}
691 }
692 
693 /*
694  * Convert locale category id into string
695  */
696 const char *
697 lookup_localecat(int cat)
698 {
699 	size_t	i;
700 
701 	for (i = 0; i < NLCINFO; i++)
702 		if (lcinfo[i].id == cat) {
703 			return (lcinfo[i].name);
704 		}
705 	return ("UNKNOWN");
706 }
707 
708 /*
709  * Show list of keywords
710  */
711 void
712 showkeywordslist(void)
713 {
714 	size_t	i;
715 
716 #define FMT "%-20s %-12s %-7s %-20s\n"
717 
718 	printf("List of available keywords\n\n");
719 	printf(FMT, "Keyword", "Category", "Type", "Comment");
720 	printf("-------------------- ------------ ------- --------------------\n");
721 	for (i = 0; i < NKWINFO; i++) {
722 		printf(FMT,
723 			kwinfo[i].name,
724 			lookup_localecat(kwinfo[i].catid),
725 			(kwinfo[i].isstr == 0) ? "number" : "string",
726 			kwinfo[i].comment);
727 	}
728 }
729