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: stable/10/usr.bin/locale/locale.c 310041 2016-12-13 23:10:35Z vangyzen $
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>
41309329Svangyzen#include <limits.h>
42105239Sphantom#include <locale.h>
43116612Sphantom#include <langinfo.h>
44105239Sphantom#include <stdio.h>
45105239Sphantom#include <stdlib.h>
46105239Sphantom#include <string.h>
47105239Sphantom#include <stringlist.h>
48105239Sphantom#include <unistd.h>
49116851Sphantom#include "setlocale.h"
50105239Sphantom
51105239Sphantom/* Local prototypes */
52309329Svangyzenchar	*format_grouping(const char *);
53116612Sphantomvoid	init_locales_list(void);
54116876Sphantomvoid	list_charmaps(void);
55105239Sphantomvoid	list_locales(void);
56116675Sphantomconst char *lookup_localecat(int);
57116616Sphantomchar	*kwval_lconv(int);
58310041Svangyzenint	kwval_lookup(const char *, char **, int *, int *);
59310041Svangyzenvoid	showdetails(const char *);
60197764Sedwinvoid	showkeywordslist(char *substring);
61116612Sphantomvoid	showlocale(void);
62116616Sphantomvoid	usage(void);
63105239Sphantom
64105239Sphantom/* Global variables */
65105239Sphantomstatic StringList *locales = NULL;
66105239Sphantom
67310041Svangyzenstatic int	all_locales = 0;
68310041Svangyzenstatic int	all_charmaps = 0;
69310041Svangyzenstatic int	prt_categories = 0;
70310041Svangyzenstatic int	prt_keywords = 0;
71105239Sphantom
72310041Svangyzenstatic const struct _lcinfo {
73116616Sphantom	const char	*name;
74116616Sphantom	int		id;
75105239Sphantom} lcinfo [] = {
76116616Sphantom	{ "LC_CTYPE",		LC_CTYPE },
77116616Sphantom	{ "LC_COLLATE",		LC_COLLATE },
78116612Sphantom	{ "LC_TIME",		LC_TIME },
79116612Sphantom	{ "LC_NUMERIC",		LC_NUMERIC },
80116612Sphantom	{ "LC_MONETARY",	LC_MONETARY },
81116612Sphantom	{ "LC_MESSAGES",	LC_MESSAGES }
82105239Sphantom};
83242851Sgrog#define	NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
84105239Sphantom
85116612Sphantom/* ids for values not referenced by nl_langinfo() */
86116612Sphantom#define	KW_ZERO			10000
87116612Sphantom#define	KW_GROUPING		(KW_ZERO+1)
88242851Sgrog#define	KW_INT_CURR_SYMBOL	(KW_ZERO+2)
89242851Sgrog#define	KW_CURRENCY_SYMBOL	(KW_ZERO+3)
90242851Sgrog#define	KW_MON_DECIMAL_POINT	(KW_ZERO+4)
91242851Sgrog#define	KW_MON_THOUSANDS_SEP	(KW_ZERO+5)
92242851Sgrog#define	KW_MON_GROUPING		(KW_ZERO+6)
93242851Sgrog#define	KW_POSITIVE_SIGN	(KW_ZERO+7)
94242851Sgrog#define	KW_NEGATIVE_SIGN	(KW_ZERO+8)
95242851Sgrog#define	KW_INT_FRAC_DIGITS	(KW_ZERO+9)
96242851Sgrog#define	KW_FRAC_DIGITS		(KW_ZERO+10)
97242851Sgrog#define	KW_P_CS_PRECEDES	(KW_ZERO+11)
98242851Sgrog#define	KW_P_SEP_BY_SPACE	(KW_ZERO+12)
99242851Sgrog#define	KW_N_CS_PRECEDES	(KW_ZERO+13)
100242851Sgrog#define	KW_N_SEP_BY_SPACE	(KW_ZERO+14)
101242851Sgrog#define	KW_P_SIGN_POSN		(KW_ZERO+15)
102242851Sgrog#define	KW_N_SIGN_POSN		(KW_ZERO+16)
103242851Sgrog#define	KW_INT_P_CS_PRECEDES	(KW_ZERO+17)
104242851Sgrog#define	KW_INT_P_SEP_BY_SPACE	(KW_ZERO+18)
105242851Sgrog#define	KW_INT_N_CS_PRECEDES	(KW_ZERO+19)
106242851Sgrog#define	KW_INT_N_SEP_BY_SPACE	(KW_ZERO+20)
107242851Sgrog#define	KW_INT_P_SIGN_POSN	(KW_ZERO+21)
108242851Sgrog#define	KW_INT_N_SIGN_POSN	(KW_ZERO+22)
109116612Sphantom
110310041Svangyzenstatic const struct _kwinfo {
111116616Sphantom	const char	*name;
112116616Sphantom	int		isstr;		/* true - string, false - number */
113116616Sphantom	int		catid;		/* LC_* */
114116616Sphantom	int		value_ref;
115116675Sphantom	const char	*comment;
116116612Sphantom} kwinfo [] = {
117116675Sphantom	{ "charmap",		1, LC_CTYPE,	CODESET, "" },	/* hack */
118116612Sphantom
119116675Sphantom	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR, "" },
120116675Sphantom	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP, "" },
121116675Sphantom	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING, "" },
122116675Sphantom	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR,
123116675Sphantom	  "Same as decimal_point (FreeBSD only)" },		/* compat */
124116675Sphantom	{ "thousep",		1, LC_NUMERIC,	THOUSEP,
125116675Sphantom	  "Same as thousands_sep (FreeBSD only)" },		/* compat */
126116612Sphantom
127116675Sphantom	{ "int_curr_symbol",	1, LC_MONETARY,	KW_INT_CURR_SYMBOL, "" },
128116675Sphantom	{ "currency_symbol",	1, LC_MONETARY,	KW_CURRENCY_SYMBOL, "" },
129116675Sphantom	{ "mon_decimal_point",	1, LC_MONETARY,	KW_MON_DECIMAL_POINT, "" },
130116675Sphantom	{ "mon_thousands_sep",	1, LC_MONETARY,	KW_MON_THOUSANDS_SEP, "" },
131116675Sphantom	{ "mon_grouping",	1, LC_MONETARY,	KW_MON_GROUPING, "" },
132116675Sphantom	{ "positive_sign",	1, LC_MONETARY,	KW_POSITIVE_SIGN, "" },
133116675Sphantom	{ "negative_sign",	1, LC_MONETARY,	KW_NEGATIVE_SIGN, "" },
134116612Sphantom
135116675Sphantom	{ "int_frac_digits",	0, LC_MONETARY,	KW_INT_FRAC_DIGITS, "" },
136116675Sphantom	{ "frac_digits",	0, LC_MONETARY,	KW_FRAC_DIGITS, "" },
137116675Sphantom	{ "p_cs_precedes",	0, LC_MONETARY,	KW_P_CS_PRECEDES, "" },
138116675Sphantom	{ "p_sep_by_space",	0, LC_MONETARY,	KW_P_SEP_BY_SPACE, "" },
139116675Sphantom	{ "n_cs_precedes",	0, LC_MONETARY,	KW_N_CS_PRECEDES, "" },
140116675Sphantom	{ "n_sep_by_space",	0, LC_MONETARY,	KW_N_SEP_BY_SPACE, "" },
141116675Sphantom	{ "p_sign_posn",	0, LC_MONETARY,	KW_P_SIGN_POSN, "" },
142116675Sphantom	{ "n_sign_posn",	0, LC_MONETARY,	KW_N_SIGN_POSN, "" },
143116873Sphantom	{ "int_p_cs_precedes",	0, LC_MONETARY,	KW_INT_P_CS_PRECEDES, "" },
144116873Sphantom	{ "int_p_sep_by_space",	0, LC_MONETARY,	KW_INT_P_SEP_BY_SPACE, "" },
145116873Sphantom	{ "int_n_cs_precedes",	0, LC_MONETARY,	KW_INT_N_CS_PRECEDES, "" },
146116873Sphantom	{ "int_n_sep_by_space",	0, LC_MONETARY,	KW_INT_N_SEP_BY_SPACE, "" },
147116873Sphantom	{ "int_p_sign_posn",	0, LC_MONETARY,	KW_INT_P_SIGN_POSN, "" },
148116873Sphantom	{ "int_n_sign_posn",	0, LC_MONETARY,	KW_INT_N_SIGN_POSN, "" },
149116612Sphantom
150116675Sphantom	{ "d_t_fmt",		1, LC_TIME,	D_T_FMT, "" },
151116675Sphantom	{ "d_fmt",		1, LC_TIME,	D_FMT, "" },
152116675Sphantom	{ "t_fmt",		1, LC_TIME,	T_FMT, "" },
153116675Sphantom	{ "am_str",		1, LC_TIME,	AM_STR, "" },
154116675Sphantom	{ "pm_str",		1, LC_TIME,	PM_STR, "" },
155116675Sphantom	{ "t_fmt_ampm",		1, LC_TIME,	T_FMT_AMPM, "" },
156116675Sphantom	{ "day_1",		1, LC_TIME,	DAY_1, "" },
157116675Sphantom	{ "day_2",		1, LC_TIME,	DAY_2, "" },
158116675Sphantom	{ "day_3",		1, LC_TIME,	DAY_3, "" },
159116675Sphantom	{ "day_4",		1, LC_TIME,	DAY_4, "" },
160116675Sphantom	{ "day_5",		1, LC_TIME,	DAY_5, "" },
161116675Sphantom	{ "day_6",		1, LC_TIME,	DAY_6, "" },
162116675Sphantom	{ "day_7",		1, LC_TIME,	DAY_7, "" },
163116675Sphantom	{ "abday_1",		1, LC_TIME,	ABDAY_1, "" },
164116675Sphantom	{ "abday_2",		1, LC_TIME,	ABDAY_2, "" },
165116675Sphantom	{ "abday_3",		1, LC_TIME,	ABDAY_3, "" },
166116675Sphantom	{ "abday_4",		1, LC_TIME,	ABDAY_4, "" },
167116675Sphantom	{ "abday_5",		1, LC_TIME,	ABDAY_5, "" },
168116675Sphantom	{ "abday_6",		1, LC_TIME,	ABDAY_6, "" },
169116675Sphantom	{ "abday_7",		1, LC_TIME,	ABDAY_7, "" },
170116675Sphantom	{ "mon_1",		1, LC_TIME,	MON_1, "" },
171116675Sphantom	{ "mon_2",		1, LC_TIME,	MON_2, "" },
172116675Sphantom	{ "mon_3",		1, LC_TIME,	MON_3, "" },
173116675Sphantom	{ "mon_4",		1, LC_TIME,	MON_4, "" },
174116675Sphantom	{ "mon_5",		1, LC_TIME,	MON_5, "" },
175116675Sphantom	{ "mon_6",		1, LC_TIME,	MON_6, "" },
176116675Sphantom	{ "mon_7",		1, LC_TIME,	MON_7, "" },
177116675Sphantom	{ "mon_8",		1, LC_TIME,	MON_8, "" },
178116675Sphantom	{ "mon_9",		1, LC_TIME,	MON_9, "" },
179116675Sphantom	{ "mon_10",		1, LC_TIME,	MON_10, "" },
180116675Sphantom	{ "mon_11",		1, LC_TIME,	MON_11, "" },
181116675Sphantom	{ "mon_12",		1, LC_TIME,	MON_12, "" },
182116675Sphantom	{ "abmon_1",		1, LC_TIME,	ABMON_1, "" },
183116675Sphantom	{ "abmon_2",		1, LC_TIME,	ABMON_2, "" },
184116675Sphantom	{ "abmon_3",		1, LC_TIME,	ABMON_3, "" },
185116675Sphantom	{ "abmon_4",		1, LC_TIME,	ABMON_4, "" },
186116675Sphantom	{ "abmon_5",		1, LC_TIME,	ABMON_5, "" },
187116675Sphantom	{ "abmon_6",		1, LC_TIME,	ABMON_6, "" },
188116675Sphantom	{ "abmon_7",		1, LC_TIME,	ABMON_7, "" },
189116675Sphantom	{ "abmon_8",		1, LC_TIME,	ABMON_8, "" },
190116675Sphantom	{ "abmon_9",		1, LC_TIME,	ABMON_9, "" },
191116675Sphantom	{ "abmon_10",		1, LC_TIME,	ABMON_10, "" },
192116675Sphantom	{ "abmon_11",		1, LC_TIME,	ABMON_11, "" },
193116675Sphantom	{ "abmon_12",		1, LC_TIME,	ABMON_12, "" },
194197847Sedwin	{ "altmon_1",		1, LC_TIME,	ALTMON_1, "(FreeBSD only)" },
195197847Sedwin	{ "altmon_2",		1, LC_TIME,	ALTMON_2, "(FreeBSD only)" },
196197847Sedwin	{ "altmon_3",		1, LC_TIME,	ALTMON_3, "(FreeBSD only)" },
197197847Sedwin	{ "altmon_4",		1, LC_TIME,	ALTMON_4, "(FreeBSD only)" },
198197847Sedwin	{ "altmon_5",		1, LC_TIME,	ALTMON_5, "(FreeBSD only)" },
199197847Sedwin	{ "altmon_6",		1, LC_TIME,	ALTMON_6, "(FreeBSD only)" },
200197847Sedwin	{ "altmon_7",		1, LC_TIME,	ALTMON_7, "(FreeBSD only)" },
201197847Sedwin	{ "altmon_8",		1, LC_TIME,	ALTMON_8, "(FreeBSD only)" },
202197847Sedwin	{ "altmon_9",		1, LC_TIME,	ALTMON_9, "(FreeBSD only)" },
203197847Sedwin	{ "altmon_10",		1, LC_TIME,	ALTMON_10, "(FreeBSD only)" },
204197847Sedwin	{ "altmon_11",		1, LC_TIME,	ALTMON_11, "(FreeBSD only)" },
205197847Sedwin	{ "altmon_12",		1, LC_TIME,	ALTMON_12, "(FreeBSD only)" },
206116675Sphantom	{ "era",		1, LC_TIME,	ERA, "(unavailable)" },
207116675Sphantom	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT, "(unavailable)" },
208116675Sphantom	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT, "(unavailable)" },
209116675Sphantom	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT, "(unavailable)" },
210116675Sphantom	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS, "" },
211116675Sphantom	{ "d_md_order",		1, LC_TIME,	D_MD_ORDER,
212116675Sphantom	  "(FreeBSD only)"				},	/* local */
213116612Sphantom
214116675Sphantom	{ "yesexpr",		1, LC_MESSAGES, YESEXPR, "" },
215116675Sphantom	{ "noexpr",		1, LC_MESSAGES, NOEXPR, "" },
216116675Sphantom	{ "yesstr",		1, LC_MESSAGES, YESSTR,
217116675Sphantom	  "(POSIX legacy)" },					/* compat */
218116675Sphantom	{ "nostr",		1, LC_MESSAGES, NOSTR,
219116675Sphantom	  "(POSIX legacy)" }					/* compat */
220116612Sphantom
221116612Sphantom};
222242851Sgrog#define	NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
223116612Sphantom
224310041Svangyzenstatic const char *boguslocales[] = { "UTF-8" };
225133013Stjr#define	NBOGUS	(sizeof(boguslocales)/sizeof(boguslocales[0]))
226133013Stjr
227105239Sphantomint
228105239Sphantommain(int argc, char *argv[])
229105239Sphantom{
230124830Sgrehan	int	ch;
231116675Sphantom	int	tmp;
232105239Sphantom
233197764Sedwin	while ((ch = getopt(argc, argv, "ackms:")) != -1) {
234105239Sphantom		switch (ch) {
235105239Sphantom		case 'a':
236105239Sphantom			all_locales = 1;
237105239Sphantom			break;
238116612Sphantom		case 'c':
239116612Sphantom			prt_categories = 1;
240105239Sphantom			break;
241116612Sphantom		case 'k':
242116612Sphantom			prt_keywords = 1;
243116612Sphantom			break;
244116612Sphantom		case 'm':
245116612Sphantom			all_charmaps = 1;
246116612Sphantom			break;
247105239Sphantom		default:
248105239Sphantom			usage();
249105239Sphantom		}
250116612Sphantom	}
251105239Sphantom	argc -= optind;
252105239Sphantom	argv += optind;
253105239Sphantom
254116612Sphantom	/* validate arguments */
255116612Sphantom	if (all_locales && all_charmaps)
256116612Sphantom		usage();
257242743Sgrog	if ((all_locales || all_charmaps) && argc > 0)
258116612Sphantom		usage();
259116612Sphantom	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
260116612Sphantom		usage();
261116612Sphantom
262116612Sphantom	/* process '-a' */
263105239Sphantom	if (all_locales) {
264105239Sphantom		list_locales();
265105239Sphantom		exit(0);
266105239Sphantom	}
267105239Sphantom
268116612Sphantom	/* process '-m' */
269116612Sphantom	if (all_charmaps) {
270116876Sphantom		list_charmaps();
271116876Sphantom		exit(0);
272116612Sphantom	}
273116612Sphantom
274116675Sphantom	/* check for special case '-k list' */
275116675Sphantom	tmp = 0;
276116675Sphantom	if (prt_keywords && argc > 0)
277116675Sphantom		while (tmp < argc)
278116675Sphantom			if (strcasecmp(argv[tmp++], "list") == 0) {
279197764Sedwin				showkeywordslist(argv[tmp]);
280116675Sphantom				exit(0);
281116675Sphantom			}
282116675Sphantom
283243201Sgrog	/* process '-c', '-k', or command line arguments. */
284243201Sgrog	if (prt_categories || prt_keywords || argc > 0) {
285309181Sume		if (prt_keywords || argc > 0)
286309181Sume			setlocale(LC_ALL, "");
287242743Sgrog		if (argc > 0) {
288242808Sgrog			while (argc > 0) {
289242743Sgrog				showdetails(*argv);
290242808Sgrog				argv++;
291242808Sgrog				argc--;
292242808Sgrog			}
293242808Sgrog		} else {
294242743Sgrog			uint i;
295242808Sgrog			for (i = 0; i < sizeof (kwinfo) / sizeof (struct _kwinfo); i++)
296310041Svangyzen				showdetails(kwinfo[i].name);
297242808Sgrog		}
298116612Sphantom		exit(0);
299116612Sphantom	}
300116612Sphantom
301116612Sphantom	/* no arguments, show current locale state */
302116612Sphantom	showlocale();
303116612Sphantom
304105239Sphantom	return (0);
305105239Sphantom}
306105239Sphantom
307105239Sphantomvoid
308105239Sphantomusage(void)
309105239Sphantom{
310116612Sphantom	printf("Usage: locale [ -a | -m ]\n"
311242808Sgrog	       "       locale -k list [prefix]\n"
312242808Sgrog	       "       locale [ -ck ] [keyword ...]\n");
313105239Sphantom	exit(1);
314105239Sphantom}
315105239Sphantom
316116612Sphantom/*
317116612Sphantom * Output information about all available locales
318116612Sphantom *
319116612Sphantom * XXX actually output of this function does not guarantee that locale
320116612Sphantom *     is really available to application, since it can be broken or
321242851Sgrog *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
322116612Sphantom *     also validate these locales?
323116612Sphantom */
324105239Sphantomvoid
325105239Sphantomlist_locales(void)
326105239Sphantom{
327116616Sphantom	size_t i;
328105239Sphantom
329116612Sphantom	init_locales_list();
330105239Sphantom	for (i = 0; i < locales->sl_cur; i++) {
331105239Sphantom		printf("%s\n", locales->sl_str[i]);
332105239Sphantom	}
333105239Sphantom}
334105239Sphantom
335116876Sphantom/*
336116877Sphantom * qsort() helper function
337116877Sphantom */
338116877Sphantomstatic int
339116877Sphantomscmp(const void *s1, const void *s2)
340116877Sphantom{
341310041Svangyzen	return strcmp(*(const char * const *)s1, *(const char * const *)s2);
342116877Sphantom}
343116877Sphantom
344116877Sphantom/*
345116876Sphantom * Output information about all available charmaps
346116876Sphantom *
347116876Sphantom * XXX this function is doing a task in hackish way, i.e. by scaning
348116876Sphantom *     list of locales, spliting their codeset part and building list of
349116876Sphantom *     them.
350116876Sphantom */
351116876Sphantomvoid
352116876Sphantomlist_charmaps(void)
353116876Sphantom{
354116876Sphantom	size_t i;
355116876Sphantom	char *s, *cs;
356116876Sphantom	StringList *charmaps;
357116612Sphantom
358116876Sphantom	/* initialize StringList */
359116876Sphantom	charmaps = sl_init();
360116876Sphantom	if (charmaps == NULL)
361116876Sphantom		err(1, "could not allocate memory");
362116876Sphantom
363116876Sphantom	/* fetch locales list */
364116876Sphantom	init_locales_list();
365116876Sphantom
366116876Sphantom	/* split codesets and build their list */
367116876Sphantom	for (i = 0; i < locales->sl_cur; i++) {
368116876Sphantom		s = locales->sl_str[i];
369116876Sphantom		if ((cs = strchr(s, '.')) != NULL) {
370116876Sphantom			cs++;
371116876Sphantom			if (sl_find(charmaps, cs) == NULL)
372116876Sphantom				sl_add(charmaps, cs);
373116876Sphantom		}
374116876Sphantom	}
375116876Sphantom
376116876Sphantom	/* add US-ASCII, if not yet added */
377116876Sphantom	if (sl_find(charmaps, "US-ASCII") == NULL)
378310041Svangyzen		sl_add(charmaps, strdup("US-ASCII"));
379116876Sphantom
380116876Sphantom	/* sort the list */
381116876Sphantom	qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
382116876Sphantom
383116876Sphantom	/* print results */
384116876Sphantom	for (i = 0; i < charmaps->sl_cur; i++) {
385116876Sphantom		printf("%s\n", charmaps->sl_str[i]);
386116876Sphantom	}
387116876Sphantom}
388116876Sphantom
389116612Sphantom/*
390116612Sphantom * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
391116612Sphantom * environment variable is set)
392116612Sphantom */
393105239Sphantomvoid
394116612Sphantominit_locales_list(void)
395105239Sphantom{
396116612Sphantom	DIR *dirp;
397116612Sphantom	struct dirent *dp;
398133013Stjr	size_t i;
399133013Stjr	int bogus;
400105239Sphantom
401116612Sphantom	/* why call this function twice ? */
402116612Sphantom	if (locales != NULL)
403105239Sphantom		return;
404116612Sphantom
405116612Sphantom	/* initialize StringList */
406116612Sphantom	locales = sl_init();
407116612Sphantom	if (locales == NULL)
408116612Sphantom		err(1, "could not allocate memory");
409116612Sphantom
410116612Sphantom	/* get actual locales directory name */
411116851Sphantom	if (__detect_path_locale() != 0)
412116851Sphantom		err(1, "unable to find locales storage");
413116612Sphantom
414116612Sphantom	/* open locales directory */
415116851Sphantom	dirp = opendir(_PathLocale);
416116612Sphantom	if (dirp == NULL)
417116851Sphantom		err(1, "could not open directory '%s'", _PathLocale);
418116612Sphantom
419116612Sphantom	/* scan directory and store its contents except "." and ".." */
420116612Sphantom	while ((dp = readdir(dirp)) != NULL) {
421116612Sphantom		if (*(dp->d_name) == '.')
422116612Sphantom			continue;		/* exclude "." and ".." */
423133013Stjr		for (bogus = i = 0; i < NBOGUS; i++)
424133013Stjr			if (strncmp(dp->d_name, boguslocales[i],
425133013Stjr			    strlen(boguslocales[i])) == 0)
426133013Stjr				bogus = 1;
427133013Stjr		if (!bogus)
428133013Stjr			sl_add(locales, strdup(dp->d_name));
429105239Sphantom	}
430116612Sphantom	closedir(dirp);
431105239Sphantom
432242808Sgrog	/* make sure that 'POSIX' and 'C' locales are present in the list.
433116612Sphantom	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
434242808Sgrog	 * we also list 'C' for constistency
435242808Sgrog	 */
436116612Sphantom	if (sl_find(locales, "POSIX") == NULL)
437310041Svangyzen		sl_add(locales, strdup("POSIX"));
438105239Sphantom
439116612Sphantom	if (sl_find(locales, "C") == NULL)
440310041Svangyzen		sl_add(locales, strdup("C"));
441105239Sphantom
442116612Sphantom	/* make output nicer, sort the list */
443116612Sphantom	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
444105239Sphantom}
445105239Sphantom
446116612Sphantom/*
447116612Sphantom * Show current locale status, depending on environment variables
448116612Sphantom */
449105239Sphantomvoid
450116612Sphantomshowlocale(void)
451105239Sphantom{
452116616Sphantom	size_t	i;
453125329Sache	const char *lang, *vval, *eval;
454105239Sphantom
455125329Sache	setlocale(LC_ALL, "");
456105239Sphantom
457125329Sache	lang = getenv("LANG");
458125329Sache	if (lang == NULL) {
459116612Sphantom		lang = "";
460125329Sache	}
461125329Sache	printf("LANG=%s\n", lang);
462116616Sphantom	/* XXX: if LANG is null, then set it to "C" to get implied values? */
463105239Sphantom
464116612Sphantom	for (i = 0; i < NLCINFO; i++) {
465116612Sphantom		vval = setlocale(lcinfo[i].id, NULL);
466116612Sphantom		eval = getenv(lcinfo[i].name);
467116612Sphantom		if (eval != NULL && !strcmp(eval, vval)
468116612Sphantom				&& strcmp(lang, vval)) {
469116612Sphantom			/*
470116612Sphantom			 * Appropriate environment variable set, its value
471293290Sbdrewery			 * is valid and not overridden by LC_ALL
472116612Sphantom			 *
473116612Sphantom			 * XXX: possible side effect: if both LANG and
474293290Sbdrewery			 * overridden environment variable are set into same
475116612Sphantom			 * value, then it'll be assumed as 'implied'
476116612Sphantom			 */
477116612Sphantom			printf("%s=%s\n", lcinfo[i].name, vval);
478116612Sphantom		} else {
479116612Sphantom			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
480116612Sphantom		}
481105239Sphantom	}
482105239Sphantom
483125329Sache	vval = getenv("LC_ALL");
484125329Sache	if (vval == NULL) {
485125329Sache		vval = "";
486125329Sache	}
487125329Sache	printf("LC_ALL=%s\n", vval);
488105239Sphantom}
489105239Sphantom
490309329Svangyzenchar *
491309329Svangyzenformat_grouping(const char *binary)
492309329Svangyzen{
493309329Svangyzen	static char rval[64];
494309329Svangyzen	const char *cp;
495310041Svangyzen	size_t roff;
496310041Svangyzen	int len;
497309329Svangyzen
498309329Svangyzen	rval[0] = '\0';
499310041Svangyzen	roff = 0;
500309329Svangyzen	for (cp = binary; *cp != '\0'; ++cp) {
501310041Svangyzen#if CHAR_MIN != 0
502310041Svangyzen		if (*cp < 0)
503310041Svangyzen			break;		/* garbage input */
504310041Svangyzen#endif
505310041Svangyzen		len = snprintf(&rval[roff], sizeof(rval) - roff, "%u;", *cp);
506310041Svangyzen		if (len < 0 || (unsigned)len >= sizeof(rval) - roff)
507310041Svangyzen			break;		/* insufficient space for output */
508310041Svangyzen		roff += len;
509310041Svangyzen		if (*cp == CHAR_MAX)
510310041Svangyzen			break;		/* special termination */
511309329Svangyzen	}
512309329Svangyzen
513310041Svangyzen	/* Truncate at the last successfully snprintf()ed semicolon. */
514310041Svangyzen	if (roff != 0)
515310041Svangyzen		rval[roff - 1] = '\0';
516309329Svangyzen
517310041Svangyzen	return (&rval[0]);
518309329Svangyzen}
519309329Svangyzen
520116612Sphantom/*
521116612Sphantom * keyword value lookup helper (via localeconv())
522116612Sphantom */
523116612Sphantomchar *
524116612Sphantomkwval_lconv(int id)
525105239Sphantom{
526116616Sphantom	struct lconv *lc;
527116616Sphantom	char *rval;
528105239Sphantom
529116616Sphantom	rval = NULL;
530116616Sphantom	lc = localeconv();
531116612Sphantom	switch (id) {
532116612Sphantom		case KW_GROUPING:
533309329Svangyzen			rval = format_grouping(lc->grouping);
534116612Sphantom			break;
535116612Sphantom		case KW_INT_CURR_SYMBOL:
536116612Sphantom			rval = lc->int_curr_symbol;
537116612Sphantom			break;
538116612Sphantom		case KW_CURRENCY_SYMBOL:
539116612Sphantom			rval = lc->currency_symbol;
540116612Sphantom			break;
541116612Sphantom		case KW_MON_DECIMAL_POINT:
542116612Sphantom			rval = lc->mon_decimal_point;
543116612Sphantom			break;
544116612Sphantom		case KW_MON_THOUSANDS_SEP:
545116612Sphantom			rval = lc->mon_thousands_sep;
546116612Sphantom			break;
547116612Sphantom		case KW_MON_GROUPING:
548309329Svangyzen			rval = format_grouping(lc->mon_grouping);
549116612Sphantom			break;
550116612Sphantom		case KW_POSITIVE_SIGN:
551116612Sphantom			rval = lc->positive_sign;
552116612Sphantom			break;
553116612Sphantom		case KW_NEGATIVE_SIGN:
554116612Sphantom			rval = lc->negative_sign;
555116612Sphantom			break;
556116612Sphantom		case KW_INT_FRAC_DIGITS:
557116612Sphantom			rval = &(lc->int_frac_digits);
558116612Sphantom			break;
559116612Sphantom		case KW_FRAC_DIGITS:
560116612Sphantom			rval = &(lc->frac_digits);
561116612Sphantom			break;
562116612Sphantom		case KW_P_CS_PRECEDES:
563116612Sphantom			rval = &(lc->p_cs_precedes);
564116612Sphantom			break;
565116612Sphantom		case KW_P_SEP_BY_SPACE:
566116612Sphantom			rval = &(lc->p_sep_by_space);
567116612Sphantom			break;
568116612Sphantom		case KW_N_CS_PRECEDES:
569116612Sphantom			rval = &(lc->n_cs_precedes);
570116612Sphantom			break;
571116612Sphantom		case KW_N_SEP_BY_SPACE:
572116612Sphantom			rval = &(lc->n_sep_by_space);
573116612Sphantom			break;
574116612Sphantom		case KW_P_SIGN_POSN:
575116612Sphantom			rval = &(lc->p_sign_posn);
576116612Sphantom			break;
577116612Sphantom		case KW_N_SIGN_POSN:
578116612Sphantom			rval = &(lc->n_sign_posn);
579116612Sphantom			break;
580116873Sphantom		case KW_INT_P_CS_PRECEDES:
581116873Sphantom			rval = &(lc->int_p_cs_precedes);
582116873Sphantom			break;
583116873Sphantom		case KW_INT_P_SEP_BY_SPACE:
584116873Sphantom			rval = &(lc->int_p_sep_by_space);
585116873Sphantom			break;
586116873Sphantom		case KW_INT_N_CS_PRECEDES:
587116873Sphantom			rval = &(lc->int_n_cs_precedes);
588116873Sphantom			break;
589116873Sphantom		case KW_INT_N_SEP_BY_SPACE:
590116873Sphantom			rval = &(lc->int_n_sep_by_space);
591116873Sphantom			break;
592116873Sphantom		case KW_INT_P_SIGN_POSN:
593116873Sphantom			rval = &(lc->int_p_sign_posn);
594116873Sphantom			break;
595116873Sphantom		case KW_INT_N_SIGN_POSN:
596116873Sphantom			rval = &(lc->int_n_sign_posn);
597116873Sphantom			break;
598116612Sphantom		default:
599116612Sphantom			break;
600116612Sphantom	}
601116612Sphantom	return (rval);
602105239Sphantom}
603105239Sphantom
604116612Sphantom/*
605116612Sphantom * keyword value and properties lookup
606116612Sphantom */
607116612Sphantomint
608310041Svangyzenkwval_lookup(const char *kwname, char **kwval, int *cat, int *isstr)
609105239Sphantom{
610116616Sphantom	int	rval;
611116616Sphantom	size_t	i;
612116612Sphantom
613116616Sphantom	rval = 0;
614116612Sphantom	for (i = 0; i < NKWINFO; i++) {
615116612Sphantom		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
616116612Sphantom			rval = 1;
617116612Sphantom			*cat = kwinfo[i].catid;
618116612Sphantom			*isstr = kwinfo[i].isstr;
619116612Sphantom			if (kwinfo[i].value_ref < KW_ZERO) {
620116612Sphantom				*kwval = nl_langinfo(kwinfo[i].value_ref);
621116612Sphantom			} else {
622116612Sphantom				*kwval = kwval_lconv(kwinfo[i].value_ref);
623116612Sphantom			}
624116612Sphantom			break;
625116612Sphantom		}
626116612Sphantom	}
627116612Sphantom
628116612Sphantom	return (rval);
629105239Sphantom}
630105239Sphantom
631116612Sphantom/*
632116612Sphantom * Show details about requested keyword according to '-k' and/or '-c'
633116612Sphantom * command line options specified.
634116612Sphantom */
635105239Sphantomvoid
636310041Svangyzenshowdetails(const char *kw)
637105239Sphantom{
638116616Sphantom	int	isstr, cat, tmpval;
639116616Sphantom	char	*kwval;
640105239Sphantom
641116612Sphantom	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
642116612Sphantom		/*
643116612Sphantom		 * invalid keyword specified.
644116612Sphantom		 * XXX: any actions?
645116612Sphantom		 */
646197764Sedwin		fprintf(stderr, "Unknown keyword: `%s'\n", kw);
647105239Sphantom		return;
648116612Sphantom	}
649105239Sphantom
650116612Sphantom	if (prt_categories) {
651310041Svangyzen		if (prt_keywords)
652242743Sgrog			printf("%-20s ", lookup_localecat(cat));
653310041Svangyzen		else
654242808Sgrog			printf("%-20s\t%s\n", kw, lookup_localecat(cat));
655116612Sphantom	}
656105239Sphantom
657116612Sphantom	if (prt_keywords) {
658116612Sphantom		if (isstr) {
659116612Sphantom			printf("%s=\"%s\"\n", kw, kwval);
660116612Sphantom		} else {
661116616Sphantom			tmpval = (char) *kwval;
662116616Sphantom			printf("%s=%d\n", kw, tmpval);
663116612Sphantom		}
664116612Sphantom	}
665105239Sphantom
666116612Sphantom	if (!prt_categories && !prt_keywords) {
667116612Sphantom		if (isstr) {
668116612Sphantom			printf("%s\n", kwval);
669116612Sphantom		} else {
670116616Sphantom			tmpval = (char) *kwval;
671116616Sphantom			printf("%d\n", tmpval);
672116612Sphantom		}
673105239Sphantom	}
674105239Sphantom}
675116675Sphantom
676116675Sphantom/*
677116675Sphantom * Convert locale category id into string
678116675Sphantom */
679116675Sphantomconst char *
680116675Sphantomlookup_localecat(int cat)
681116675Sphantom{
682116675Sphantom	size_t	i;
683116675Sphantom
684116675Sphantom	for (i = 0; i < NLCINFO; i++)
685116675Sphantom		if (lcinfo[i].id == cat) {
686116675Sphantom			return (lcinfo[i].name);
687116675Sphantom		}
688116675Sphantom	return ("UNKNOWN");
689116675Sphantom}
690116675Sphantom
691116675Sphantom/*
692116675Sphantom * Show list of keywords
693116675Sphantom */
694116675Sphantomvoid
695197764Sedwinshowkeywordslist(char *substring)
696116675Sphantom{
697116675Sphantom	size_t	i;
698116675Sphantom
699242851Sgrog#define	FMT "%-20s %-12s %-7s %-20s\n"
700116675Sphantom
701197764Sedwin	if (substring == NULL)
702197764Sedwin		printf("List of available keywords\n\n");
703197764Sedwin	else
704197764Sedwin		printf("List of available keywords starting with '%s'\n\n",
705197764Sedwin		    substring);
706116675Sphantom	printf(FMT, "Keyword", "Category", "Type", "Comment");
707116675Sphantom	printf("-------------------- ------------ ------- --------------------\n");
708116675Sphantom	for (i = 0; i < NKWINFO; i++) {
709197764Sedwin		if (substring != NULL) {
710197764Sedwin			if (strncmp(kwinfo[i].name, substring,
711197764Sedwin			    strlen(substring)) != 0)
712197764Sedwin				continue;
713197764Sedwin		}
714116675Sphantom		printf(FMT,
715116675Sphantom			kwinfo[i].name,
716116675Sphantom			lookup_localecat(kwinfo[i].catid),
717116675Sphantom			(kwinfo[i].isstr == 0) ? "number" : "string",
718116675Sphantom			kwinfo[i].comment);
719116675Sphantom	}
720116675Sphantom}
721