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