Home | History | Annotate | Line # | Download | only in locale
locale.c revision 1.7
      1 /*	$NetBSD: locale.c,v 1.7 2009/01/16 13:30:07 hira Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2002, 2003 Alexey Zelkin <phantom (at) 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.7 2009/01/16 13:30:07 hira 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 "setlocale_local.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 + 1);
    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