1105239Sphantom/*-
2116612Sphantom * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
3105239Sphantom * All rights reserved.
4105239Sphantom *
5105239Sphantom * Redistribution and use in source and binary forms, with or without
6105239Sphantom * modification, are permitted provided that the following conditions
7105239Sphantom * are met:
8105239Sphantom * 1. Redistributions of source code must retain the above copyright
9105239Sphantom *    notice, this list of conditions and the following disclaimer.
10105239Sphantom * 2. Redistributions in binary form must reproduce the above copyright
11105239Sphantom *    notice, this list of conditions and the following disclaimer in the
12105239Sphantom *    documentation and/or other materials provided with the distribution.
13105239Sphantom *
14105239Sphantom * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15105239Sphantom * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16105239Sphantom * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17105239Sphantom * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18105239Sphantom * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19105239Sphantom * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20105239Sphantom * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21105239Sphantom * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22105239Sphantom * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23105239Sphantom * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24105239Sphantom * SUCH DAMAGE.
25105239Sphantom *
26105239Sphantom * $FreeBSD$
27105239Sphantom */
28105239Sphantom
29116612Sphantom/*
30116873Sphantom * XXX: implement missing era_* (LC_TIME) keywords (require libc &
31116873Sphantom *	nl_langinfo(3) extensions)
32116612Sphantom *
33116612Sphantom * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
34242808Sgrog *	localedef(1) implementation).  Currently it's handled via
35116612Sphantom *	nl_langinfo(CODESET).
36116612Sphantom */
37116612Sphantom
38105239Sphantom#include <sys/types.h>
39105239Sphantom#include <dirent.h>
40116612Sphantom#include <err.h>
41105239Sphantom#include <locale.h>
42116612Sphantom#include <langinfo.h>
43105239Sphantom#include <stdio.h>
44105239Sphantom#include <stdlib.h>
45105239Sphantom#include <string.h>
46105239Sphantom#include <stringlist.h>
47105239Sphantom#include <unistd.h>
48116851Sphantom#include "setlocale.h"
49105239Sphantom
50105239Sphantom/* Local prototypes */
51116612Sphantomvoid	init_locales_list(void);
52116876Sphantomvoid	list_charmaps(void);
53105239Sphantomvoid	list_locales(void);
54116675Sphantomconst char *lookup_localecat(int);
55116616Sphantomchar	*kwval_lconv(int);
56116616Sphantomint	kwval_lookup(char *, char **, int *, int *);
57116612Sphantomvoid	showdetails(char *);
58197764Sedwinvoid	showkeywordslist(char *substring);
59116612Sphantomvoid	showlocale(void);
60116616Sphantomvoid	usage(void);
61105239Sphantom
62105239Sphantom/* Global variables */
63105239Sphantomstatic StringList *locales = NULL;
64105239Sphantom
65105239Sphantomint	all_locales = 0;
66116612Sphantomint	all_charmaps = 0;
67116612Sphantomint	prt_categories = 0;
68116612Sphantomint	prt_keywords = 0;
69116612Sphantomint	more_params = 0;
70105239Sphantom
71105239Sphantomstruct _lcinfo {
72116616Sphantom	const char	*name;
73116616Sphantom	int		id;
74105239Sphantom} lcinfo [] = {
75116616Sphantom	{ "LC_CTYPE",		LC_CTYPE },
76116616Sphantom	{ "LC_COLLATE",		LC_COLLATE },
77116612Sphantom	{ "LC_TIME",		LC_TIME },
78116612Sphantom	{ "LC_NUMERIC",		LC_NUMERIC },
79116612Sphantom	{ "LC_MONETARY",	LC_MONETARY },
80116612Sphantom	{ "LC_MESSAGES",	LC_MESSAGES }
81105239Sphantom};
82242851Sgrog#define	NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
83105239Sphantom
84116612Sphantom/* ids for values not referenced by nl_langinfo() */
85116612Sphantom#define	KW_ZERO			10000
86116612Sphantom#define	KW_GROUPING		(KW_ZERO+1)
87242851Sgrog#define	KW_INT_CURR_SYMBOL	(KW_ZERO+2)
88242851Sgrog#define	KW_CURRENCY_SYMBOL	(KW_ZERO+3)
89242851Sgrog#define	KW_MON_DECIMAL_POINT	(KW_ZERO+4)
90242851Sgrog#define	KW_MON_THOUSANDS_SEP	(KW_ZERO+5)
91242851Sgrog#define	KW_MON_GROUPING		(KW_ZERO+6)
92242851Sgrog#define	KW_POSITIVE_SIGN	(KW_ZERO+7)
93242851Sgrog#define	KW_NEGATIVE_SIGN	(KW_ZERO+8)
94242851Sgrog#define	KW_INT_FRAC_DIGITS	(KW_ZERO+9)
95242851Sgrog#define	KW_FRAC_DIGITS		(KW_ZERO+10)
96242851Sgrog#define	KW_P_CS_PRECEDES	(KW_ZERO+11)
97242851Sgrog#define	KW_P_SEP_BY_SPACE	(KW_ZERO+12)
98242851Sgrog#define	KW_N_CS_PRECEDES	(KW_ZERO+13)
99242851Sgrog#define	KW_N_SEP_BY_SPACE	(KW_ZERO+14)
100242851Sgrog#define	KW_P_SIGN_POSN		(KW_ZERO+15)
101242851Sgrog#define	KW_N_SIGN_POSN		(KW_ZERO+16)
102242851Sgrog#define	KW_INT_P_CS_PRECEDES	(KW_ZERO+17)
103242851Sgrog#define	KW_INT_P_SEP_BY_SPACE	(KW_ZERO+18)
104242851Sgrog#define	KW_INT_N_CS_PRECEDES	(KW_ZERO+19)
105242851Sgrog#define	KW_INT_N_SEP_BY_SPACE	(KW_ZERO+20)
106242851Sgrog#define	KW_INT_P_SIGN_POSN	(KW_ZERO+21)
107242851Sgrog#define	KW_INT_N_SIGN_POSN	(KW_ZERO+22)
108116612Sphantom
109116612Sphantomstruct _kwinfo {
110116616Sphantom	const char	*name;
111116616Sphantom	int		isstr;		/* true - string, false - number */
112116616Sphantom	int		catid;		/* LC_* */
113116616Sphantom	int		value_ref;
114116675Sphantom	const char	*comment;
115116612Sphantom} kwinfo [] = {
116116675Sphantom	{ "charmap",		1, LC_CTYPE,	CODESET, "" },	/* hack */
117116612Sphantom
118116675Sphantom	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR, "" },
119116675Sphantom	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP, "" },
120116675Sphantom	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING, "" },
121116675Sphantom	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR,
122116675Sphantom	  "Same as decimal_point (FreeBSD only)" },		/* compat */
123116675Sphantom	{ "thousep",		1, LC_NUMERIC,	THOUSEP,
124116675Sphantom	  "Same as thousands_sep (FreeBSD only)" },		/* compat */
125116612Sphantom
126116675Sphantom	{ "int_curr_symbol",	1, LC_MONETARY,	KW_INT_CURR_SYMBOL, "" },
127116675Sphantom	{ "currency_symbol",	1, LC_MONETARY,	KW_CURRENCY_SYMBOL, "" },
128116675Sphantom	{ "mon_decimal_point",	1, LC_MONETARY,	KW_MON_DECIMAL_POINT, "" },
129116675Sphantom	{ "mon_thousands_sep",	1, LC_MONETARY,	KW_MON_THOUSANDS_SEP, "" },
130116675Sphantom	{ "mon_grouping",	1, LC_MONETARY,	KW_MON_GROUPING, "" },
131116675Sphantom	{ "positive_sign",	1, LC_MONETARY,	KW_POSITIVE_SIGN, "" },
132116675Sphantom	{ "negative_sign",	1, LC_MONETARY,	KW_NEGATIVE_SIGN, "" },
133116612Sphantom
134116675Sphantom	{ "int_frac_digits",	0, LC_MONETARY,	KW_INT_FRAC_DIGITS, "" },
135116675Sphantom	{ "frac_digits",	0, LC_MONETARY,	KW_FRAC_DIGITS, "" },
136116675Sphantom	{ "p_cs_precedes",	0, LC_MONETARY,	KW_P_CS_PRECEDES, "" },
137116675Sphantom	{ "p_sep_by_space",	0, LC_MONETARY,	KW_P_SEP_BY_SPACE, "" },
138116675Sphantom	{ "n_cs_precedes",	0, LC_MONETARY,	KW_N_CS_PRECEDES, "" },
139116675Sphantom	{ "n_sep_by_space",	0, LC_MONETARY,	KW_N_SEP_BY_SPACE, "" },
140116675Sphantom	{ "p_sign_posn",	0, LC_MONETARY,	KW_P_SIGN_POSN, "" },
141116675Sphantom	{ "n_sign_posn",	0, LC_MONETARY,	KW_N_SIGN_POSN, "" },
142116873Sphantom	{ "int_p_cs_precedes",	0, LC_MONETARY,	KW_INT_P_CS_PRECEDES, "" },
143116873Sphantom	{ "int_p_sep_by_space",	0, LC_MONETARY,	KW_INT_P_SEP_BY_SPACE, "" },
144116873Sphantom	{ "int_n_cs_precedes",	0, LC_MONETARY,	KW_INT_N_CS_PRECEDES, "" },
145116873Sphantom	{ "int_n_sep_by_space",	0, LC_MONETARY,	KW_INT_N_SEP_BY_SPACE, "" },
146116873Sphantom	{ "int_p_sign_posn",	0, LC_MONETARY,	KW_INT_P_SIGN_POSN, "" },
147116873Sphantom	{ "int_n_sign_posn",	0, LC_MONETARY,	KW_INT_N_SIGN_POSN, "" },
148116612Sphantom
149116675Sphantom	{ "d_t_fmt",		1, LC_TIME,	D_T_FMT, "" },
150116675Sphantom	{ "d_fmt",		1, LC_TIME,	D_FMT, "" },
151116675Sphantom	{ "t_fmt",		1, LC_TIME,	T_FMT, "" },
152116675Sphantom	{ "am_str",		1, LC_TIME,	AM_STR, "" },
153116675Sphantom	{ "pm_str",		1, LC_TIME,	PM_STR, "" },
154116675Sphantom	{ "t_fmt_ampm",		1, LC_TIME,	T_FMT_AMPM, "" },
155116675Sphantom	{ "day_1",		1, LC_TIME,	DAY_1, "" },
156116675Sphantom	{ "day_2",		1, LC_TIME,	DAY_2, "" },
157116675Sphantom	{ "day_3",		1, LC_TIME,	DAY_3, "" },
158116675Sphantom	{ "day_4",		1, LC_TIME,	DAY_4, "" },
159116675Sphantom	{ "day_5",		1, LC_TIME,	DAY_5, "" },
160116675Sphantom	{ "day_6",		1, LC_TIME,	DAY_6, "" },
161116675Sphantom	{ "day_7",		1, LC_TIME,	DAY_7, "" },
162116675Sphantom	{ "abday_1",		1, LC_TIME,	ABDAY_1, "" },
163116675Sphantom	{ "abday_2",		1, LC_TIME,	ABDAY_2, "" },
164116675Sphantom	{ "abday_3",		1, LC_TIME,	ABDAY_3, "" },
165116675Sphantom	{ "abday_4",		1, LC_TIME,	ABDAY_4, "" },
166116675Sphantom	{ "abday_5",		1, LC_TIME,	ABDAY_5, "" },
167116675Sphantom	{ "abday_6",		1, LC_TIME,	ABDAY_6, "" },
168116675Sphantom	{ "abday_7",		1, LC_TIME,	ABDAY_7, "" },
169116675Sphantom	{ "mon_1",		1, LC_TIME,	MON_1, "" },
170116675Sphantom	{ "mon_2",		1, LC_TIME,	MON_2, "" },
171116675Sphantom	{ "mon_3",		1, LC_TIME,	MON_3, "" },
172116675Sphantom	{ "mon_4",		1, LC_TIME,	MON_4, "" },
173116675Sphantom	{ "mon_5",		1, LC_TIME,	MON_5, "" },
174116675Sphantom	{ "mon_6",		1, LC_TIME,	MON_6, "" },
175116675Sphantom	{ "mon_7",		1, LC_TIME,	MON_7, "" },
176116675Sphantom	{ "mon_8",		1, LC_TIME,	MON_8, "" },
177116675Sphantom	{ "mon_9",		1, LC_TIME,	MON_9, "" },
178116675Sphantom	{ "mon_10",		1, LC_TIME,	MON_10, "" },
179116675Sphantom	{ "mon_11",		1, LC_TIME,	MON_11, "" },
180116675Sphantom	{ "mon_12",		1, LC_TIME,	MON_12, "" },
181116675Sphantom	{ "abmon_1",		1, LC_TIME,	ABMON_1, "" },
182116675Sphantom	{ "abmon_2",		1, LC_TIME,	ABMON_2, "" },
183116675Sphantom	{ "abmon_3",		1, LC_TIME,	ABMON_3, "" },
184116675Sphantom	{ "abmon_4",		1, LC_TIME,	ABMON_4, "" },
185116675Sphantom	{ "abmon_5",		1, LC_TIME,	ABMON_5, "" },
186116675Sphantom	{ "abmon_6",		1, LC_TIME,	ABMON_6, "" },
187116675Sphantom	{ "abmon_7",		1, LC_TIME,	ABMON_7, "" },
188116675Sphantom	{ "abmon_8",		1, LC_TIME,	ABMON_8, "" },
189116675Sphantom	{ "abmon_9",		1, LC_TIME,	ABMON_9, "" },
190116675Sphantom	{ "abmon_10",		1, LC_TIME,	ABMON_10, "" },
191116675Sphantom	{ "abmon_11",		1, LC_TIME,	ABMON_11, "" },
192116675Sphantom	{ "abmon_12",		1, LC_TIME,	ABMON_12, "" },
193197847Sedwin	{ "altmon_1",		1, LC_TIME,	ALTMON_1, "(FreeBSD only)" },
194197847Sedwin	{ "altmon_2",		1, LC_TIME,	ALTMON_2, "(FreeBSD only)" },
195197847Sedwin	{ "altmon_3",		1, LC_TIME,	ALTMON_3, "(FreeBSD only)" },
196197847Sedwin	{ "altmon_4",		1, LC_TIME,	ALTMON_4, "(FreeBSD only)" },
197197847Sedwin	{ "altmon_5",		1, LC_TIME,	ALTMON_5, "(FreeBSD only)" },
198197847Sedwin	{ "altmon_6",		1, LC_TIME,	ALTMON_6, "(FreeBSD only)" },
199197847Sedwin	{ "altmon_7",		1, LC_TIME,	ALTMON_7, "(FreeBSD only)" },
200197847Sedwin	{ "altmon_8",		1, LC_TIME,	ALTMON_8, "(FreeBSD only)" },
201197847Sedwin	{ "altmon_9",		1, LC_TIME,	ALTMON_9, "(FreeBSD only)" },
202197847Sedwin	{ "altmon_10",		1, LC_TIME,	ALTMON_10, "(FreeBSD only)" },
203197847Sedwin	{ "altmon_11",		1, LC_TIME,	ALTMON_11, "(FreeBSD only)" },
204197847Sedwin	{ "altmon_12",		1, LC_TIME,	ALTMON_12, "(FreeBSD only)" },
205116675Sphantom	{ "era",		1, LC_TIME,	ERA, "(unavailable)" },
206116675Sphantom	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT, "(unavailable)" },
207116675Sphantom	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT, "(unavailable)" },
208116675Sphantom	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT, "(unavailable)" },
209116675Sphantom	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS, "" },
210116675Sphantom	{ "d_md_order",		1, LC_TIME,	D_MD_ORDER,
211116675Sphantom	  "(FreeBSD only)"				},	/* local */
212116612Sphantom
213116675Sphantom	{ "yesexpr",		1, LC_MESSAGES, YESEXPR, "" },
214116675Sphantom	{ "noexpr",		1, LC_MESSAGES, NOEXPR, "" },
215116675Sphantom	{ "yesstr",		1, LC_MESSAGES, YESSTR,
216116675Sphantom	  "(POSIX legacy)" },					/* compat */
217116675Sphantom	{ "nostr",		1, LC_MESSAGES, NOSTR,
218116675Sphantom	  "(POSIX legacy)" }					/* compat */
219116612Sphantom
220116612Sphantom};
221242851Sgrog#define	NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
222116612Sphantom
223133014Stjrconst char *boguslocales[] = { "UTF-8" };
224133013Stjr#define	NBOGUS	(sizeof(boguslocales)/sizeof(boguslocales[0]))
225133013Stjr
226105239Sphantomint
227105239Sphantommain(int argc, char *argv[])
228105239Sphantom{
229124830Sgrehan	int	ch;
230116675Sphantom	int	tmp;
231105239Sphantom
232197764Sedwin	while ((ch = getopt(argc, argv, "ackms:")) != -1) {
233105239Sphantom		switch (ch) {
234105239Sphantom		case 'a':
235105239Sphantom			all_locales = 1;
236105239Sphantom			break;
237116612Sphantom		case 'c':
238116612Sphantom			prt_categories = 1;
239105239Sphantom			break;
240116612Sphantom		case 'k':
241116612Sphantom			prt_keywords = 1;
242116612Sphantom			break;
243116612Sphantom		case 'm':
244116612Sphantom			all_charmaps = 1;
245116612Sphantom			break;
246105239Sphantom		default:
247105239Sphantom			usage();
248105239Sphantom		}
249116612Sphantom	}
250105239Sphantom	argc -= optind;
251105239Sphantom	argv += optind;
252105239Sphantom
253116612Sphantom	/* validate arguments */
254116612Sphantom	if (all_locales && all_charmaps)
255116612Sphantom		usage();
256242743Sgrog	if ((all_locales || all_charmaps) && argc > 0)
257116612Sphantom		usage();
258116612Sphantom	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
259116612Sphantom		usage();
260116612Sphantom
261116612Sphantom	/* process '-a' */
262105239Sphantom	if (all_locales) {
263105239Sphantom		list_locales();
264105239Sphantom		exit(0);
265105239Sphantom	}
266105239Sphantom
267116612Sphantom	/* process '-m' */
268116612Sphantom	if (all_charmaps) {
269116876Sphantom		list_charmaps();
270116876Sphantom		exit(0);
271116612Sphantom	}
272116612Sphantom
273116675Sphantom	/* check for special case '-k list' */
274116675Sphantom	tmp = 0;
275116675Sphantom	if (prt_keywords && argc > 0)
276116675Sphantom		while (tmp < argc)
277116675Sphantom			if (strcasecmp(argv[tmp++], "list") == 0) {
278197764Sedwin				showkeywordslist(argv[tmp]);
279116675Sphantom				exit(0);
280116675Sphantom			}
281116675Sphantom
282243201Sgrog	/* process '-c', '-k', or command line arguments. */
283243201Sgrog	if (prt_categories || prt_keywords || argc > 0) {
284242743Sgrog		if (argc > 0) {
285242743Sgrog			setlocale(LC_ALL, "");
286242808Sgrog			while (argc > 0) {
287242743Sgrog				showdetails(*argv);
288242808Sgrog				argv++;
289242808Sgrog				argc--;
290242808Sgrog			}
291242808Sgrog		} else {
292242743Sgrog			uint i;
293242808Sgrog			for (i = 0; i < sizeof (kwinfo) / sizeof (struct _kwinfo); i++)
294242743Sgrog				showdetails ((char *)kwinfo [i].name);
295242808Sgrog		}
296116612Sphantom		exit(0);
297116612Sphantom	}
298116612Sphantom
299116612Sphantom	/* no arguments, show current locale state */
300116612Sphantom	showlocale();
301116612Sphantom
302105239Sphantom	return (0);
303105239Sphantom}
304105239Sphantom
305105239Sphantomvoid
306105239Sphantomusage(void)
307105239Sphantom{
308116612Sphantom	printf("Usage: locale [ -a | -m ]\n"
309242808Sgrog	       "       locale -k list [prefix]\n"
310242808Sgrog	       "       locale [ -ck ] [keyword ...]\n");
311105239Sphantom	exit(1);
312105239Sphantom}
313105239Sphantom
314116612Sphantom/*
315116612Sphantom * Output information about all available locales
316116612Sphantom *
317116612Sphantom * XXX actually output of this function does not guarantee that locale
318116612Sphantom *     is really available to application, since it can be broken or
319242851Sgrog *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
320116612Sphantom *     also validate these locales?
321116612Sphantom */
322105239Sphantomvoid
323105239Sphantomlist_locales(void)
324105239Sphantom{
325116616Sphantom	size_t i;
326105239Sphantom
327116612Sphantom	init_locales_list();
328105239Sphantom	for (i = 0; i < locales->sl_cur; i++) {
329105239Sphantom		printf("%s\n", locales->sl_str[i]);
330105239Sphantom	}
331105239Sphantom}
332105239Sphantom
333116876Sphantom/*
334116877Sphantom * qsort() helper function
335116877Sphantom */
336116877Sphantomstatic int
337116877Sphantomscmp(const void *s1, const void *s2)
338116877Sphantom{
339116877Sphantom	return strcmp(*(const char **)s1, *(const char **)s2);
340116877Sphantom}
341116877Sphantom
342116877Sphantom/*
343116876Sphantom * Output information about all available charmaps
344116876Sphantom *
345116876Sphantom * XXX this function is doing a task in hackish way, i.e. by scaning
346116876Sphantom *     list of locales, spliting their codeset part and building list of
347116876Sphantom *     them.
348116876Sphantom */
349116876Sphantomvoid
350116876Sphantomlist_charmaps(void)
351116876Sphantom{
352116876Sphantom	size_t i;
353116876Sphantom	char *s, *cs;
354116876Sphantom	StringList *charmaps;
355116612Sphantom
356116876Sphantom	/* initialize StringList */
357116876Sphantom	charmaps = sl_init();
358116876Sphantom	if (charmaps == NULL)
359116876Sphantom		err(1, "could not allocate memory");
360116876Sphantom
361116876Sphantom	/* fetch locales list */
362116876Sphantom	init_locales_list();
363116876Sphantom
364116876Sphantom	/* split codesets and build their list */
365116876Sphantom	for (i = 0; i < locales->sl_cur; i++) {
366116876Sphantom		s = locales->sl_str[i];
367116876Sphantom		if ((cs = strchr(s, '.')) != NULL) {
368116876Sphantom			cs++;
369116876Sphantom			if (sl_find(charmaps, cs) == NULL)
370116876Sphantom				sl_add(charmaps, cs);
371116876Sphantom		}
372116876Sphantom	}
373116876Sphantom
374116876Sphantom	/* add US-ASCII, if not yet added */
375116876Sphantom	if (sl_find(charmaps, "US-ASCII") == NULL)
376116876Sphantom		sl_add(charmaps, "US-ASCII");
377116876Sphantom
378116876Sphantom	/* sort the list */
379116876Sphantom	qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
380116876Sphantom
381116876Sphantom	/* print results */
382116876Sphantom	for (i = 0; i < charmaps->sl_cur; i++) {
383116876Sphantom		printf("%s\n", charmaps->sl_str[i]);
384116876Sphantom	}
385116876Sphantom}
386116876Sphantom
387116612Sphantom/*
388116612Sphantom * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
389116612Sphantom * environment variable is set)
390116612Sphantom */
391105239Sphantomvoid
392116612Sphantominit_locales_list(void)
393105239Sphantom{
394116612Sphantom	DIR *dirp;
395116612Sphantom	struct dirent *dp;
396133013Stjr	size_t i;
397133013Stjr	int bogus;
398105239Sphantom
399116612Sphantom	/* why call this function twice ? */
400116612Sphantom	if (locales != NULL)
401105239Sphantom		return;
402116612Sphantom
403116612Sphantom	/* initialize StringList */
404116612Sphantom	locales = sl_init();
405116612Sphantom	if (locales == NULL)
406116612Sphantom		err(1, "could not allocate memory");
407116612Sphantom
408116612Sphantom	/* get actual locales directory name */
409116851Sphantom	if (__detect_path_locale() != 0)
410116851Sphantom		err(1, "unable to find locales storage");
411116612Sphantom
412116612Sphantom	/* open locales directory */
413116851Sphantom	dirp = opendir(_PathLocale);
414116612Sphantom	if (dirp == NULL)
415116851Sphantom		err(1, "could not open directory '%s'", _PathLocale);
416116612Sphantom
417116612Sphantom	/* scan directory and store its contents except "." and ".." */
418116612Sphantom	while ((dp = readdir(dirp)) != NULL) {
419116612Sphantom		if (*(dp->d_name) == '.')
420116612Sphantom			continue;		/* exclude "." and ".." */
421133013Stjr		for (bogus = i = 0; i < NBOGUS; i++)
422133013Stjr			if (strncmp(dp->d_name, boguslocales[i],
423133013Stjr			    strlen(boguslocales[i])) == 0)
424133013Stjr				bogus = 1;
425133013Stjr		if (!bogus)
426133013Stjr			sl_add(locales, strdup(dp->d_name));
427105239Sphantom	}
428116612Sphantom	closedir(dirp);
429105239Sphantom
430242808Sgrog	/* make sure that 'POSIX' and 'C' locales are present in the list.
431116612Sphantom	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
432242808Sgrog	 * we also list 'C' for constistency
433242808Sgrog	 */
434116612Sphantom	if (sl_find(locales, "POSIX") == NULL)
435116612Sphantom		sl_add(locales, "POSIX");
436105239Sphantom
437116612Sphantom	if (sl_find(locales, "C") == NULL)
438116612Sphantom		sl_add(locales, "C");
439105239Sphantom
440116612Sphantom	/* make output nicer, sort the list */
441116612Sphantom	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
442105239Sphantom}
443105239Sphantom
444116612Sphantom/*
445116612Sphantom * Show current locale status, depending on environment variables
446116612Sphantom */
447105239Sphantomvoid
448116612Sphantomshowlocale(void)
449105239Sphantom{
450116616Sphantom	size_t	i;
451125329Sache	const char *lang, *vval, *eval;
452105239Sphantom
453125329Sache	setlocale(LC_ALL, "");
454105239Sphantom
455125329Sache	lang = getenv("LANG");
456125329Sache	if (lang == NULL) {
457116612Sphantom		lang = "";
458125329Sache	}
459125329Sache	printf("LANG=%s\n", lang);
460116616Sphantom	/* XXX: if LANG is null, then set it to "C" to get implied values? */
461105239Sphantom
462116612Sphantom	for (i = 0; i < NLCINFO; i++) {
463116612Sphantom		vval = setlocale(lcinfo[i].id, NULL);
464116612Sphantom		eval = getenv(lcinfo[i].name);
465116612Sphantom		if (eval != NULL && !strcmp(eval, vval)
466116612Sphantom				&& strcmp(lang, vval)) {
467116612Sphantom			/*
468116612Sphantom			 * Appropriate environment variable set, its value
469116612Sphantom			 * is valid and not overriden by LC_ALL
470116612Sphantom			 *
471116612Sphantom			 * XXX: possible side effect: if both LANG and
472116612Sphantom			 * overriden environment variable are set into same
473116612Sphantom			 * value, then it'll be assumed as 'implied'
474116612Sphantom			 */
475116612Sphantom			printf("%s=%s\n", lcinfo[i].name, vval);
476116612Sphantom		} else {
477116612Sphantom			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
478116612Sphantom		}
479105239Sphantom	}
480105239Sphantom
481125329Sache	vval = getenv("LC_ALL");
482125329Sache	if (vval == NULL) {
483125329Sache		vval = "";
484125329Sache	}
485125329Sache	printf("LC_ALL=%s\n", vval);
486105239Sphantom}
487105239Sphantom
488116612Sphantom/*
489116612Sphantom * keyword value lookup helper (via localeconv())
490116612Sphantom */
491116612Sphantomchar *
492116612Sphantomkwval_lconv(int id)
493105239Sphantom{
494116616Sphantom	struct lconv *lc;
495116616Sphantom	char *rval;
496105239Sphantom
497116616Sphantom	rval = NULL;
498116616Sphantom	lc = localeconv();
499116612Sphantom	switch (id) {
500116612Sphantom		case KW_GROUPING:
501116612Sphantom			rval = lc->grouping;
502116612Sphantom			break;
503116612Sphantom		case KW_INT_CURR_SYMBOL:
504116612Sphantom			rval = lc->int_curr_symbol;
505116612Sphantom			break;
506116612Sphantom		case KW_CURRENCY_SYMBOL:
507116612Sphantom			rval = lc->currency_symbol;
508116612Sphantom			break;
509116612Sphantom		case KW_MON_DECIMAL_POINT:
510116612Sphantom			rval = lc->mon_decimal_point;
511116612Sphantom			break;
512116612Sphantom		case KW_MON_THOUSANDS_SEP:
513116612Sphantom			rval = lc->mon_thousands_sep;
514116612Sphantom			break;
515116612Sphantom		case KW_MON_GROUPING:
516116612Sphantom			rval = lc->mon_grouping;
517116612Sphantom			break;
518116612Sphantom		case KW_POSITIVE_SIGN:
519116612Sphantom			rval = lc->positive_sign;
520116612Sphantom			break;
521116612Sphantom		case KW_NEGATIVE_SIGN:
522116612Sphantom			rval = lc->negative_sign;
523116612Sphantom			break;
524116612Sphantom		case KW_INT_FRAC_DIGITS:
525116612Sphantom			rval = &(lc->int_frac_digits);
526116612Sphantom			break;
527116612Sphantom		case KW_FRAC_DIGITS:
528116612Sphantom			rval = &(lc->frac_digits);
529116612Sphantom			break;
530116612Sphantom		case KW_P_CS_PRECEDES:
531116612Sphantom			rval = &(lc->p_cs_precedes);
532116612Sphantom			break;
533116612Sphantom		case KW_P_SEP_BY_SPACE:
534116612Sphantom			rval = &(lc->p_sep_by_space);
535116612Sphantom			break;
536116612Sphantom		case KW_N_CS_PRECEDES:
537116612Sphantom			rval = &(lc->n_cs_precedes);
538116612Sphantom			break;
539116612Sphantom		case KW_N_SEP_BY_SPACE:
540116612Sphantom			rval = &(lc->n_sep_by_space);
541116612Sphantom			break;
542116612Sphantom		case KW_P_SIGN_POSN:
543116612Sphantom			rval = &(lc->p_sign_posn);
544116612Sphantom			break;
545116612Sphantom		case KW_N_SIGN_POSN:
546116612Sphantom			rval = &(lc->n_sign_posn);
547116612Sphantom			break;
548116873Sphantom		case KW_INT_P_CS_PRECEDES:
549116873Sphantom			rval = &(lc->int_p_cs_precedes);
550116873Sphantom			break;
551116873Sphantom		case KW_INT_P_SEP_BY_SPACE:
552116873Sphantom			rval = &(lc->int_p_sep_by_space);
553116873Sphantom			break;
554116873Sphantom		case KW_INT_N_CS_PRECEDES:
555116873Sphantom			rval = &(lc->int_n_cs_precedes);
556116873Sphantom			break;
557116873Sphantom		case KW_INT_N_SEP_BY_SPACE:
558116873Sphantom			rval = &(lc->int_n_sep_by_space);
559116873Sphantom			break;
560116873Sphantom		case KW_INT_P_SIGN_POSN:
561116873Sphantom			rval = &(lc->int_p_sign_posn);
562116873Sphantom			break;
563116873Sphantom		case KW_INT_N_SIGN_POSN:
564116873Sphantom			rval = &(lc->int_n_sign_posn);
565116873Sphantom			break;
566116612Sphantom		default:
567116612Sphantom			break;
568116612Sphantom	}
569116612Sphantom	return (rval);
570105239Sphantom}
571105239Sphantom
572116612Sphantom/*
573116612Sphantom * keyword value and properties lookup
574116612Sphantom */
575116612Sphantomint
576116612Sphantomkwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
577105239Sphantom{
578116616Sphantom	int	rval;
579116616Sphantom	size_t	i;
580116612Sphantom
581116616Sphantom	rval = 0;
582116612Sphantom	for (i = 0; i < NKWINFO; i++) {
583116612Sphantom		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
584116612Sphantom			rval = 1;
585116612Sphantom			*cat = kwinfo[i].catid;
586116612Sphantom			*isstr = kwinfo[i].isstr;
587116612Sphantom			if (kwinfo[i].value_ref < KW_ZERO) {
588116612Sphantom				*kwval = nl_langinfo(kwinfo[i].value_ref);
589116612Sphantom			} else {
590116612Sphantom				*kwval = kwval_lconv(kwinfo[i].value_ref);
591116612Sphantom			}
592116612Sphantom			break;
593116612Sphantom		}
594116612Sphantom	}
595116612Sphantom
596116612Sphantom	return (rval);
597105239Sphantom}
598105239Sphantom
599116612Sphantom/*
600116612Sphantom * Show details about requested keyword according to '-k' and/or '-c'
601116612Sphantom * command line options specified.
602116612Sphantom */
603105239Sphantomvoid
604116612Sphantomshowdetails(char *kw)
605105239Sphantom{
606116616Sphantom	int	isstr, cat, tmpval;
607116616Sphantom	char	*kwval;
608105239Sphantom
609116612Sphantom	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
610116612Sphantom		/*
611116612Sphantom		 * invalid keyword specified.
612116612Sphantom		 * XXX: any actions?
613116612Sphantom		 */
614197764Sedwin		fprintf(stderr, "Unknown keyword: `%s'\n", kw);
615105239Sphantom		return;
616116612Sphantom	}
617105239Sphantom
618116612Sphantom	if (prt_categories) {
619242808Sgrog		  if (prt_keywords)
620242743Sgrog			printf("%-20s ", lookup_localecat(cat));
621242808Sgrog		  else
622242808Sgrog			printf("%-20s\t%s\n", kw, lookup_localecat(cat));
623116612Sphantom	}
624105239Sphantom
625116612Sphantom	if (prt_keywords) {
626116612Sphantom		if (isstr) {
627116612Sphantom			printf("%s=\"%s\"\n", kw, kwval);
628116612Sphantom		} else {
629116616Sphantom			tmpval = (char) *kwval;
630116616Sphantom			printf("%s=%d\n", kw, tmpval);
631116612Sphantom		}
632116612Sphantom	}
633105239Sphantom
634116612Sphantom	if (!prt_categories && !prt_keywords) {
635116612Sphantom		if (isstr) {
636116612Sphantom			printf("%s\n", kwval);
637116612Sphantom		} else {
638116616Sphantom			tmpval = (char) *kwval;
639116616Sphantom			printf("%d\n", tmpval);
640116612Sphantom		}
641105239Sphantom	}
642105239Sphantom}
643116675Sphantom
644116675Sphantom/*
645116675Sphantom * Convert locale category id into string
646116675Sphantom */
647116675Sphantomconst char *
648116675Sphantomlookup_localecat(int cat)
649116675Sphantom{
650116675Sphantom	size_t	i;
651116675Sphantom
652116675Sphantom	for (i = 0; i < NLCINFO; i++)
653116675Sphantom		if (lcinfo[i].id == cat) {
654116675Sphantom			return (lcinfo[i].name);
655116675Sphantom		}
656116675Sphantom	return ("UNKNOWN");
657116675Sphantom}
658116675Sphantom
659116675Sphantom/*
660116675Sphantom * Show list of keywords
661116675Sphantom */
662116675Sphantomvoid
663197764Sedwinshowkeywordslist(char *substring)
664116675Sphantom{
665116675Sphantom	size_t	i;
666116675Sphantom
667242851Sgrog#define	FMT "%-20s %-12s %-7s %-20s\n"
668116675Sphantom
669197764Sedwin	if (substring == NULL)
670197764Sedwin		printf("List of available keywords\n\n");
671197764Sedwin	else
672197764Sedwin		printf("List of available keywords starting with '%s'\n\n",
673197764Sedwin		    substring);
674116675Sphantom	printf(FMT, "Keyword", "Category", "Type", "Comment");
675116675Sphantom	printf("-------------------- ------------ ------- --------------------\n");
676116675Sphantom	for (i = 0; i < NKWINFO; i++) {
677197764Sedwin		if (substring != NULL) {
678197764Sedwin			if (strncmp(kwinfo[i].name, substring,
679197764Sedwin			    strlen(substring)) != 0)
680197764Sedwin				continue;
681197764Sedwin		}
682116675Sphantom		printf(FMT,
683116675Sphantom			kwinfo[i].name,
684116675Sphantom			lookup_localecat(kwinfo[i].catid),
685116675Sphantom			(kwinfo[i].isstr == 0) ? "number" : "string",
686116675Sphantom			kwinfo[i].comment);
687116675Sphantom	}
688116675Sphantom}
689