1105239Sphantom/*-
2330449Seadler * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3330449Seadler *
4116612Sphantom * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
5105239Sphantom * All rights reserved.
6105239Sphantom *
7105239Sphantom * Redistribution and use in source and binary forms, with or without
8105239Sphantom * modification, are permitted provided that the following conditions
9105239Sphantom * are met:
10105239Sphantom * 1. Redistributions of source code must retain the above copyright
11105239Sphantom *    notice, this list of conditions and the following disclaimer.
12105239Sphantom * 2. Redistributions in binary form must reproduce the above copyright
13105239Sphantom *    notice, this list of conditions and the following disclaimer in the
14105239Sphantom *    documentation and/or other materials provided with the distribution.
15105239Sphantom *
16105239Sphantom * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17105239Sphantom * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18105239Sphantom * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19105239Sphantom * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20105239Sphantom * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21105239Sphantom * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22105239Sphantom * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23105239Sphantom * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24105239Sphantom * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25105239Sphantom * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26105239Sphantom * SUCH DAMAGE.
27105239Sphantom *
28105239Sphantom * $FreeBSD: stable/11/usr.bin/locale/locale.c 330449 2018-03-05 07:26:05Z eadler $
29105239Sphantom */
30105239Sphantom
31116612Sphantom/*
32116873Sphantom * XXX: implement missing era_* (LC_TIME) keywords (require libc &
33116873Sphantom *	nl_langinfo(3) extensions)
34116612Sphantom *
35116612Sphantom * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
36242808Sgrog *	localedef(1) implementation).  Currently it's handled via
37116612Sphantom *	nl_langinfo(CODESET).
38116612Sphantom */
39116612Sphantom
40298169Sbapt#include <sys/param.h>
41105239Sphantom#include <sys/types.h>
42298169Sbapt
43105239Sphantom#include <dirent.h>
44116612Sphantom#include <err.h>
45309330Svangyzen#include <limits.h>
46105239Sphantom#include <locale.h>
47116612Sphantom#include <langinfo.h>
48105239Sphantom#include <stdio.h>
49105239Sphantom#include <stdlib.h>
50105239Sphantom#include <string.h>
51105239Sphantom#include <stringlist.h>
52105239Sphantom#include <unistd.h>
53116851Sphantom#include "setlocale.h"
54105239Sphantom
55105239Sphantom/* Local prototypes */
56309330Svangyzenchar	*format_grouping(const char *);
57116612Sphantomvoid	init_locales_list(void);
58116876Sphantomvoid	list_charmaps(void);
59105239Sphantomvoid	list_locales(void);
60116675Sphantomconst char *lookup_localecat(int);
61116616Sphantomchar	*kwval_lconv(int);
62310040Svangyzenint	kwval_lookup(const char *, char **, int *, int *);
63310040Svangyzenvoid	showdetails(const char *);
64197764Sedwinvoid	showkeywordslist(char *substring);
65116612Sphantomvoid	showlocale(void);
66116616Sphantomvoid	usage(void);
67105239Sphantom
68105239Sphantom/* Global variables */
69105239Sphantomstatic StringList *locales = NULL;
70105239Sphantom
71310040Svangyzenstatic int	all_locales = 0;
72310040Svangyzenstatic int	all_charmaps = 0;
73310040Svangyzenstatic int	prt_categories = 0;
74310040Svangyzenstatic int	prt_keywords = 0;
75105239Sphantom
76310040Svangyzenstatic const struct _lcinfo {
77116616Sphantom	const char	*name;
78116616Sphantom	int		id;
79105239Sphantom} lcinfo [] = {
80116616Sphantom	{ "LC_CTYPE",		LC_CTYPE },
81116616Sphantom	{ "LC_COLLATE",		LC_COLLATE },
82116612Sphantom	{ "LC_TIME",		LC_TIME },
83116612Sphantom	{ "LC_NUMERIC",		LC_NUMERIC },
84116612Sphantom	{ "LC_MONETARY",	LC_MONETARY },
85116612Sphantom	{ "LC_MESSAGES",	LC_MESSAGES }
86105239Sphantom};
87298169Sbapt#define	NLCINFO nitems(lcinfo)
88105239Sphantom
89116612Sphantom/* ids for values not referenced by nl_langinfo() */
90116612Sphantom#define	KW_ZERO			10000
91116612Sphantom#define	KW_GROUPING		(KW_ZERO+1)
92242851Sgrog#define	KW_INT_CURR_SYMBOL	(KW_ZERO+2)
93242851Sgrog#define	KW_CURRENCY_SYMBOL	(KW_ZERO+3)
94242851Sgrog#define	KW_MON_DECIMAL_POINT	(KW_ZERO+4)
95242851Sgrog#define	KW_MON_THOUSANDS_SEP	(KW_ZERO+5)
96242851Sgrog#define	KW_MON_GROUPING		(KW_ZERO+6)
97242851Sgrog#define	KW_POSITIVE_SIGN	(KW_ZERO+7)
98242851Sgrog#define	KW_NEGATIVE_SIGN	(KW_ZERO+8)
99242851Sgrog#define	KW_INT_FRAC_DIGITS	(KW_ZERO+9)
100242851Sgrog#define	KW_FRAC_DIGITS		(KW_ZERO+10)
101242851Sgrog#define	KW_P_CS_PRECEDES	(KW_ZERO+11)
102242851Sgrog#define	KW_P_SEP_BY_SPACE	(KW_ZERO+12)
103242851Sgrog#define	KW_N_CS_PRECEDES	(KW_ZERO+13)
104242851Sgrog#define	KW_N_SEP_BY_SPACE	(KW_ZERO+14)
105242851Sgrog#define	KW_P_SIGN_POSN		(KW_ZERO+15)
106242851Sgrog#define	KW_N_SIGN_POSN		(KW_ZERO+16)
107242851Sgrog#define	KW_INT_P_CS_PRECEDES	(KW_ZERO+17)
108242851Sgrog#define	KW_INT_P_SEP_BY_SPACE	(KW_ZERO+18)
109242851Sgrog#define	KW_INT_N_CS_PRECEDES	(KW_ZERO+19)
110242851Sgrog#define	KW_INT_N_SEP_BY_SPACE	(KW_ZERO+20)
111242851Sgrog#define	KW_INT_P_SIGN_POSN	(KW_ZERO+21)
112242851Sgrog#define	KW_INT_N_SIGN_POSN	(KW_ZERO+22)
113116612Sphantom
114310040Svangyzenstatic const struct _kwinfo {
115116616Sphantom	const char	*name;
116116616Sphantom	int		isstr;		/* true - string, false - number */
117116616Sphantom	int		catid;		/* LC_* */
118116616Sphantom	int		value_ref;
119116675Sphantom	const char	*comment;
120116612Sphantom} kwinfo [] = {
121116675Sphantom	{ "charmap",		1, LC_CTYPE,	CODESET, "" },	/* hack */
122116612Sphantom
123116675Sphantom	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR, "" },
124116675Sphantom	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP, "" },
125116675Sphantom	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING, "" },
126116675Sphantom	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR,
127116675Sphantom	  "Same as decimal_point (FreeBSD only)" },		/* compat */
128116675Sphantom	{ "thousep",		1, LC_NUMERIC,	THOUSEP,
129116675Sphantom	  "Same as thousands_sep (FreeBSD only)" },		/* compat */
130116612Sphantom
131116675Sphantom	{ "int_curr_symbol",	1, LC_MONETARY,	KW_INT_CURR_SYMBOL, "" },
132116675Sphantom	{ "currency_symbol",	1, LC_MONETARY,	KW_CURRENCY_SYMBOL, "" },
133116675Sphantom	{ "mon_decimal_point",	1, LC_MONETARY,	KW_MON_DECIMAL_POINT, "" },
134116675Sphantom	{ "mon_thousands_sep",	1, LC_MONETARY,	KW_MON_THOUSANDS_SEP, "" },
135116675Sphantom	{ "mon_grouping",	1, LC_MONETARY,	KW_MON_GROUPING, "" },
136116675Sphantom	{ "positive_sign",	1, LC_MONETARY,	KW_POSITIVE_SIGN, "" },
137116675Sphantom	{ "negative_sign",	1, LC_MONETARY,	KW_NEGATIVE_SIGN, "" },
138116612Sphantom
139116675Sphantom	{ "int_frac_digits",	0, LC_MONETARY,	KW_INT_FRAC_DIGITS, "" },
140116675Sphantom	{ "frac_digits",	0, LC_MONETARY,	KW_FRAC_DIGITS, "" },
141116675Sphantom	{ "p_cs_precedes",	0, LC_MONETARY,	KW_P_CS_PRECEDES, "" },
142116675Sphantom	{ "p_sep_by_space",	0, LC_MONETARY,	KW_P_SEP_BY_SPACE, "" },
143116675Sphantom	{ "n_cs_precedes",	0, LC_MONETARY,	KW_N_CS_PRECEDES, "" },
144116675Sphantom	{ "n_sep_by_space",	0, LC_MONETARY,	KW_N_SEP_BY_SPACE, "" },
145116675Sphantom	{ "p_sign_posn",	0, LC_MONETARY,	KW_P_SIGN_POSN, "" },
146116675Sphantom	{ "n_sign_posn",	0, LC_MONETARY,	KW_N_SIGN_POSN, "" },
147116873Sphantom	{ "int_p_cs_precedes",	0, LC_MONETARY,	KW_INT_P_CS_PRECEDES, "" },
148116873Sphantom	{ "int_p_sep_by_space",	0, LC_MONETARY,	KW_INT_P_SEP_BY_SPACE, "" },
149116873Sphantom	{ "int_n_cs_precedes",	0, LC_MONETARY,	KW_INT_N_CS_PRECEDES, "" },
150116873Sphantom	{ "int_n_sep_by_space",	0, LC_MONETARY,	KW_INT_N_SEP_BY_SPACE, "" },
151116873Sphantom	{ "int_p_sign_posn",	0, LC_MONETARY,	KW_INT_P_SIGN_POSN, "" },
152116873Sphantom	{ "int_n_sign_posn",	0, LC_MONETARY,	KW_INT_N_SIGN_POSN, "" },
153116612Sphantom
154116675Sphantom	{ "d_t_fmt",		1, LC_TIME,	D_T_FMT, "" },
155116675Sphantom	{ "d_fmt",		1, LC_TIME,	D_FMT, "" },
156116675Sphantom	{ "t_fmt",		1, LC_TIME,	T_FMT, "" },
157116675Sphantom	{ "am_str",		1, LC_TIME,	AM_STR, "" },
158116675Sphantom	{ "pm_str",		1, LC_TIME,	PM_STR, "" },
159116675Sphantom	{ "t_fmt_ampm",		1, LC_TIME,	T_FMT_AMPM, "" },
160116675Sphantom	{ "day_1",		1, LC_TIME,	DAY_1, "" },
161116675Sphantom	{ "day_2",		1, LC_TIME,	DAY_2, "" },
162116675Sphantom	{ "day_3",		1, LC_TIME,	DAY_3, "" },
163116675Sphantom	{ "day_4",		1, LC_TIME,	DAY_4, "" },
164116675Sphantom	{ "day_5",		1, LC_TIME,	DAY_5, "" },
165116675Sphantom	{ "day_6",		1, LC_TIME,	DAY_6, "" },
166116675Sphantom	{ "day_7",		1, LC_TIME,	DAY_7, "" },
167116675Sphantom	{ "abday_1",		1, LC_TIME,	ABDAY_1, "" },
168116675Sphantom	{ "abday_2",		1, LC_TIME,	ABDAY_2, "" },
169116675Sphantom	{ "abday_3",		1, LC_TIME,	ABDAY_3, "" },
170116675Sphantom	{ "abday_4",		1, LC_TIME,	ABDAY_4, "" },
171116675Sphantom	{ "abday_5",		1, LC_TIME,	ABDAY_5, "" },
172116675Sphantom	{ "abday_6",		1, LC_TIME,	ABDAY_6, "" },
173116675Sphantom	{ "abday_7",		1, LC_TIME,	ABDAY_7, "" },
174116675Sphantom	{ "mon_1",		1, LC_TIME,	MON_1, "" },
175116675Sphantom	{ "mon_2",		1, LC_TIME,	MON_2, "" },
176116675Sphantom	{ "mon_3",		1, LC_TIME,	MON_3, "" },
177116675Sphantom	{ "mon_4",		1, LC_TIME,	MON_4, "" },
178116675Sphantom	{ "mon_5",		1, LC_TIME,	MON_5, "" },
179116675Sphantom	{ "mon_6",		1, LC_TIME,	MON_6, "" },
180116675Sphantom	{ "mon_7",		1, LC_TIME,	MON_7, "" },
181116675Sphantom	{ "mon_8",		1, LC_TIME,	MON_8, "" },
182116675Sphantom	{ "mon_9",		1, LC_TIME,	MON_9, "" },
183116675Sphantom	{ "mon_10",		1, LC_TIME,	MON_10, "" },
184116675Sphantom	{ "mon_11",		1, LC_TIME,	MON_11, "" },
185116675Sphantom	{ "mon_12",		1, LC_TIME,	MON_12, "" },
186116675Sphantom	{ "abmon_1",		1, LC_TIME,	ABMON_1, "" },
187116675Sphantom	{ "abmon_2",		1, LC_TIME,	ABMON_2, "" },
188116675Sphantom	{ "abmon_3",		1, LC_TIME,	ABMON_3, "" },
189116675Sphantom	{ "abmon_4",		1, LC_TIME,	ABMON_4, "" },
190116675Sphantom	{ "abmon_5",		1, LC_TIME,	ABMON_5, "" },
191116675Sphantom	{ "abmon_6",		1, LC_TIME,	ABMON_6, "" },
192116675Sphantom	{ "abmon_7",		1, LC_TIME,	ABMON_7, "" },
193116675Sphantom	{ "abmon_8",		1, LC_TIME,	ABMON_8, "" },
194116675Sphantom	{ "abmon_9",		1, LC_TIME,	ABMON_9, "" },
195116675Sphantom	{ "abmon_10",		1, LC_TIME,	ABMON_10, "" },
196116675Sphantom	{ "abmon_11",		1, LC_TIME,	ABMON_11, "" },
197116675Sphantom	{ "abmon_12",		1, LC_TIME,	ABMON_12, "" },
198197847Sedwin	{ "altmon_1",		1, LC_TIME,	ALTMON_1, "(FreeBSD only)" },
199197847Sedwin	{ "altmon_2",		1, LC_TIME,	ALTMON_2, "(FreeBSD only)" },
200197847Sedwin	{ "altmon_3",		1, LC_TIME,	ALTMON_3, "(FreeBSD only)" },
201197847Sedwin	{ "altmon_4",		1, LC_TIME,	ALTMON_4, "(FreeBSD only)" },
202197847Sedwin	{ "altmon_5",		1, LC_TIME,	ALTMON_5, "(FreeBSD only)" },
203197847Sedwin	{ "altmon_6",		1, LC_TIME,	ALTMON_6, "(FreeBSD only)" },
204197847Sedwin	{ "altmon_7",		1, LC_TIME,	ALTMON_7, "(FreeBSD only)" },
205197847Sedwin	{ "altmon_8",		1, LC_TIME,	ALTMON_8, "(FreeBSD only)" },
206197847Sedwin	{ "altmon_9",		1, LC_TIME,	ALTMON_9, "(FreeBSD only)" },
207197847Sedwin	{ "altmon_10",		1, LC_TIME,	ALTMON_10, "(FreeBSD only)" },
208197847Sedwin	{ "altmon_11",		1, LC_TIME,	ALTMON_11, "(FreeBSD only)" },
209197847Sedwin	{ "altmon_12",		1, LC_TIME,	ALTMON_12, "(FreeBSD only)" },
210116675Sphantom	{ "era",		1, LC_TIME,	ERA, "(unavailable)" },
211116675Sphantom	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT, "(unavailable)" },
212116675Sphantom	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT, "(unavailable)" },
213116675Sphantom	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT, "(unavailable)" },
214116675Sphantom	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS, "" },
215116675Sphantom	{ "d_md_order",		1, LC_TIME,	D_MD_ORDER,
216116675Sphantom	  "(FreeBSD only)"				},	/* local */
217116612Sphantom
218116675Sphantom	{ "yesexpr",		1, LC_MESSAGES, YESEXPR, "" },
219116675Sphantom	{ "noexpr",		1, LC_MESSAGES, NOEXPR, "" },
220116675Sphantom	{ "yesstr",		1, LC_MESSAGES, YESSTR,
221116675Sphantom	  "(POSIX legacy)" },					/* compat */
222116675Sphantom	{ "nostr",		1, LC_MESSAGES, NOSTR,
223116675Sphantom	  "(POSIX legacy)" }					/* compat */
224116612Sphantom
225116612Sphantom};
226307697Saraujo#define	NKWINFO (nitems(kwinfo))
227116612Sphantom
228310040Svangyzenstatic const char *boguslocales[] = { "UTF-8" };
229307697Saraujo#define	NBOGUS	(nitems(boguslocales))
230133013Stjr
231105239Sphantomint
232105239Sphantommain(int argc, char *argv[])
233105239Sphantom{
234124830Sgrehan	int	ch;
235116675Sphantom	int	tmp;
236105239Sphantom
237197764Sedwin	while ((ch = getopt(argc, argv, "ackms:")) != -1) {
238105239Sphantom		switch (ch) {
239105239Sphantom		case 'a':
240105239Sphantom			all_locales = 1;
241105239Sphantom			break;
242116612Sphantom		case 'c':
243116612Sphantom			prt_categories = 1;
244105239Sphantom			break;
245116612Sphantom		case 'k':
246116612Sphantom			prt_keywords = 1;
247116612Sphantom			break;
248116612Sphantom		case 'm':
249116612Sphantom			all_charmaps = 1;
250116612Sphantom			break;
251105239Sphantom		default:
252105239Sphantom			usage();
253105239Sphantom		}
254116612Sphantom	}
255105239Sphantom	argc -= optind;
256105239Sphantom	argv += optind;
257105239Sphantom
258116612Sphantom	/* validate arguments */
259116612Sphantom	if (all_locales && all_charmaps)
260116612Sphantom		usage();
261242743Sgrog	if ((all_locales || all_charmaps) && argc > 0)
262116612Sphantom		usage();
263116612Sphantom	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
264116612Sphantom		usage();
265116612Sphantom
266116612Sphantom	/* process '-a' */
267105239Sphantom	if (all_locales) {
268105239Sphantom		list_locales();
269105239Sphantom		exit(0);
270105239Sphantom	}
271105239Sphantom
272116612Sphantom	/* process '-m' */
273116612Sphantom	if (all_charmaps) {
274116876Sphantom		list_charmaps();
275116876Sphantom		exit(0);
276116612Sphantom	}
277116612Sphantom
278116675Sphantom	/* check for special case '-k list' */
279116675Sphantom	tmp = 0;
280116675Sphantom	if (prt_keywords && argc > 0)
281116675Sphantom		while (tmp < argc)
282116675Sphantom			if (strcasecmp(argv[tmp++], "list") == 0) {
283197764Sedwin				showkeywordslist(argv[tmp]);
284116675Sphantom				exit(0);
285116675Sphantom			}
286116675Sphantom
287243201Sgrog	/* process '-c', '-k', or command line arguments. */
288243201Sgrog	if (prt_categories || prt_keywords || argc > 0) {
289309180Sume		if (prt_keywords || argc > 0)
290309180Sume			setlocale(LC_ALL, "");
291242743Sgrog		if (argc > 0) {
292242808Sgrog			while (argc > 0) {
293242743Sgrog				showdetails(*argv);
294242808Sgrog				argv++;
295242808Sgrog				argc--;
296242808Sgrog			}
297242808Sgrog		} else {
298242743Sgrog			uint i;
299298169Sbapt			for (i = 0; i < nitems(kwinfo); i++)
300310040Svangyzen				showdetails(kwinfo[i].name);
301242808Sgrog		}
302116612Sphantom		exit(0);
303116612Sphantom	}
304116612Sphantom
305116612Sphantom	/* no arguments, show current locale state */
306116612Sphantom	showlocale();
307116612Sphantom
308105239Sphantom	return (0);
309105239Sphantom}
310105239Sphantom
311105239Sphantomvoid
312105239Sphantomusage(void)
313105239Sphantom{
314116612Sphantom	printf("Usage: locale [ -a | -m ]\n"
315242808Sgrog	       "       locale -k list [prefix]\n"
316242808Sgrog	       "       locale [ -ck ] [keyword ...]\n");
317105239Sphantom	exit(1);
318105239Sphantom}
319105239Sphantom
320116612Sphantom/*
321116612Sphantom * Output information about all available locales
322116612Sphantom *
323116612Sphantom * XXX actually output of this function does not guarantee that locale
324116612Sphantom *     is really available to application, since it can be broken or
325242851Sgrog *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
326116612Sphantom *     also validate these locales?
327116612Sphantom */
328105239Sphantomvoid
329105239Sphantomlist_locales(void)
330105239Sphantom{
331116616Sphantom	size_t i;
332105239Sphantom
333116612Sphantom	init_locales_list();
334105239Sphantom	for (i = 0; i < locales->sl_cur; i++) {
335105239Sphantom		printf("%s\n", locales->sl_str[i]);
336105239Sphantom	}
337105239Sphantom}
338105239Sphantom
339116876Sphantom/*
340116877Sphantom * qsort() helper function
341116877Sphantom */
342116877Sphantomstatic int
343116877Sphantomscmp(const void *s1, const void *s2)
344116877Sphantom{
345310040Svangyzen	return strcmp(*(const char * const *)s1, *(const char * const *)s2);
346116877Sphantom}
347116877Sphantom
348116877Sphantom/*
349116876Sphantom * Output information about all available charmaps
350116876Sphantom *
351116876Sphantom * XXX this function is doing a task in hackish way, i.e. by scaning
352116876Sphantom *     list of locales, spliting their codeset part and building list of
353116876Sphantom *     them.
354116876Sphantom */
355116876Sphantomvoid
356116876Sphantomlist_charmaps(void)
357116876Sphantom{
358116876Sphantom	size_t i;
359116876Sphantom	char *s, *cs;
360116876Sphantom	StringList *charmaps;
361116612Sphantom
362116876Sphantom	/* initialize StringList */
363116876Sphantom	charmaps = sl_init();
364116876Sphantom	if (charmaps == NULL)
365116876Sphantom		err(1, "could not allocate memory");
366116876Sphantom
367116876Sphantom	/* fetch locales list */
368116876Sphantom	init_locales_list();
369116876Sphantom
370116876Sphantom	/* split codesets and build their list */
371116876Sphantom	for (i = 0; i < locales->sl_cur; i++) {
372116876Sphantom		s = locales->sl_str[i];
373116876Sphantom		if ((cs = strchr(s, '.')) != NULL) {
374116876Sphantom			cs++;
375116876Sphantom			if (sl_find(charmaps, cs) == NULL)
376116876Sphantom				sl_add(charmaps, cs);
377116876Sphantom		}
378116876Sphantom	}
379116876Sphantom
380116876Sphantom	/* add US-ASCII, if not yet added */
381116876Sphantom	if (sl_find(charmaps, "US-ASCII") == NULL)
382310040Svangyzen		sl_add(charmaps, strdup("US-ASCII"));
383116876Sphantom
384116876Sphantom	/* sort the list */
385116876Sphantom	qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
386116876Sphantom
387116876Sphantom	/* print results */
388116876Sphantom	for (i = 0; i < charmaps->sl_cur; i++) {
389116876Sphantom		printf("%s\n", charmaps->sl_str[i]);
390116876Sphantom	}
391116876Sphantom}
392116876Sphantom
393116612Sphantom/*
394116612Sphantom * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
395116612Sphantom * environment variable is set)
396116612Sphantom */
397105239Sphantomvoid
398116612Sphantominit_locales_list(void)
399105239Sphantom{
400116612Sphantom	DIR *dirp;
401116612Sphantom	struct dirent *dp;
402133013Stjr	size_t i;
403133013Stjr	int bogus;
404105239Sphantom
405116612Sphantom	/* why call this function twice ? */
406116612Sphantom	if (locales != NULL)
407105239Sphantom		return;
408116612Sphantom
409116612Sphantom	/* initialize StringList */
410116612Sphantom	locales = sl_init();
411116612Sphantom	if (locales == NULL)
412116612Sphantom		err(1, "could not allocate memory");
413116612Sphantom
414116612Sphantom	/* get actual locales directory name */
415116851Sphantom	if (__detect_path_locale() != 0)
416116851Sphantom		err(1, "unable to find locales storage");
417116612Sphantom
418116612Sphantom	/* open locales directory */
419116851Sphantom	dirp = opendir(_PathLocale);
420116612Sphantom	if (dirp == NULL)
421116851Sphantom		err(1, "could not open directory '%s'", _PathLocale);
422116612Sphantom
423116612Sphantom	/* scan directory and store its contents except "." and ".." */
424116612Sphantom	while ((dp = readdir(dirp)) != NULL) {
425116612Sphantom		if (*(dp->d_name) == '.')
426116612Sphantom			continue;		/* exclude "." and ".." */
427133013Stjr		for (bogus = i = 0; i < NBOGUS; i++)
428133013Stjr			if (strncmp(dp->d_name, boguslocales[i],
429133013Stjr			    strlen(boguslocales[i])) == 0)
430133013Stjr				bogus = 1;
431133013Stjr		if (!bogus)
432133013Stjr			sl_add(locales, strdup(dp->d_name));
433105239Sphantom	}
434116612Sphantom	closedir(dirp);
435105239Sphantom
436242808Sgrog	/* make sure that 'POSIX' and 'C' locales are present in the list.
437116612Sphantom	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
438242808Sgrog	 * we also list 'C' for constistency
439242808Sgrog	 */
440116612Sphantom	if (sl_find(locales, "POSIX") == NULL)
441310040Svangyzen		sl_add(locales, strdup("POSIX"));
442105239Sphantom
443116612Sphantom	if (sl_find(locales, "C") == NULL)
444310040Svangyzen		sl_add(locales, strdup("C"));
445105239Sphantom
446116612Sphantom	/* make output nicer, sort the list */
447116612Sphantom	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
448105239Sphantom}
449105239Sphantom
450116612Sphantom/*
451116612Sphantom * Show current locale status, depending on environment variables
452116612Sphantom */
453105239Sphantomvoid
454116612Sphantomshowlocale(void)
455105239Sphantom{
456116616Sphantom	size_t	i;
457125329Sache	const char *lang, *vval, *eval;
458105239Sphantom
459125329Sache	setlocale(LC_ALL, "");
460105239Sphantom
461125329Sache	lang = getenv("LANG");
462125329Sache	if (lang == NULL) {
463116612Sphantom		lang = "";
464125329Sache	}
465125329Sache	printf("LANG=%s\n", lang);
466116616Sphantom	/* XXX: if LANG is null, then set it to "C" to get implied values? */
467105239Sphantom
468116612Sphantom	for (i = 0; i < NLCINFO; i++) {
469116612Sphantom		vval = setlocale(lcinfo[i].id, NULL);
470116612Sphantom		eval = getenv(lcinfo[i].name);
471116612Sphantom		if (eval != NULL && !strcmp(eval, vval)
472116612Sphantom				&& strcmp(lang, vval)) {
473116612Sphantom			/*
474116612Sphantom			 * Appropriate environment variable set, its value
475289677Seadler			 * is valid and not overridden by LC_ALL
476116612Sphantom			 *
477116612Sphantom			 * XXX: possible side effect: if both LANG and
478289677Seadler			 * overridden environment variable are set into same
479116612Sphantom			 * value, then it'll be assumed as 'implied'
480116612Sphantom			 */
481116612Sphantom			printf("%s=%s\n", lcinfo[i].name, vval);
482116612Sphantom		} else {
483116612Sphantom			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
484116612Sphantom		}
485105239Sphantom	}
486105239Sphantom
487125329Sache	vval = getenv("LC_ALL");
488125329Sache	if (vval == NULL) {
489125329Sache		vval = "";
490125329Sache	}
491125329Sache	printf("LC_ALL=%s\n", vval);
492105239Sphantom}
493105239Sphantom
494309330Svangyzenchar *
495309330Svangyzenformat_grouping(const char *binary)
496309330Svangyzen{
497309330Svangyzen	static char rval[64];
498309330Svangyzen	const char *cp;
499310040Svangyzen	size_t roff;
500310040Svangyzen	int len;
501309330Svangyzen
502309330Svangyzen	rval[0] = '\0';
503310040Svangyzen	roff = 0;
504309330Svangyzen	for (cp = binary; *cp != '\0'; ++cp) {
505310040Svangyzen#if CHAR_MIN != 0
506310040Svangyzen		if (*cp < 0)
507310040Svangyzen			break;		/* garbage input */
508310040Svangyzen#endif
509310040Svangyzen		len = snprintf(&rval[roff], sizeof(rval) - roff, "%u;", *cp);
510310040Svangyzen		if (len < 0 || (unsigned)len >= sizeof(rval) - roff)
511310040Svangyzen			break;		/* insufficient space for output */
512310040Svangyzen		roff += len;
513310040Svangyzen		if (*cp == CHAR_MAX)
514310040Svangyzen			break;		/* special termination */
515309330Svangyzen	}
516309330Svangyzen
517310040Svangyzen	/* Truncate at the last successfully snprintf()ed semicolon. */
518310040Svangyzen	if (roff != 0)
519310040Svangyzen		rval[roff - 1] = '\0';
520309330Svangyzen
521310040Svangyzen	return (&rval[0]);
522309330Svangyzen}
523309330Svangyzen
524116612Sphantom/*
525116612Sphantom * keyword value lookup helper (via localeconv())
526116612Sphantom */
527116612Sphantomchar *
528116612Sphantomkwval_lconv(int id)
529105239Sphantom{
530116616Sphantom	struct lconv *lc;
531116616Sphantom	char *rval;
532105239Sphantom
533116616Sphantom	rval = NULL;
534116616Sphantom	lc = localeconv();
535116612Sphantom	switch (id) {
536116612Sphantom		case KW_GROUPING:
537309330Svangyzen			rval = format_grouping(lc->grouping);
538116612Sphantom			break;
539116612Sphantom		case KW_INT_CURR_SYMBOL:
540116612Sphantom			rval = lc->int_curr_symbol;
541116612Sphantom			break;
542116612Sphantom		case KW_CURRENCY_SYMBOL:
543116612Sphantom			rval = lc->currency_symbol;
544116612Sphantom			break;
545116612Sphantom		case KW_MON_DECIMAL_POINT:
546116612Sphantom			rval = lc->mon_decimal_point;
547116612Sphantom			break;
548116612Sphantom		case KW_MON_THOUSANDS_SEP:
549116612Sphantom			rval = lc->mon_thousands_sep;
550116612Sphantom			break;
551116612Sphantom		case KW_MON_GROUPING:
552309330Svangyzen			rval = format_grouping(lc->mon_grouping);
553116612Sphantom			break;
554116612Sphantom		case KW_POSITIVE_SIGN:
555116612Sphantom			rval = lc->positive_sign;
556116612Sphantom			break;
557116612Sphantom		case KW_NEGATIVE_SIGN:
558116612Sphantom			rval = lc->negative_sign;
559116612Sphantom			break;
560116612Sphantom		case KW_INT_FRAC_DIGITS:
561116612Sphantom			rval = &(lc->int_frac_digits);
562116612Sphantom			break;
563116612Sphantom		case KW_FRAC_DIGITS:
564116612Sphantom			rval = &(lc->frac_digits);
565116612Sphantom			break;
566116612Sphantom		case KW_P_CS_PRECEDES:
567116612Sphantom			rval = &(lc->p_cs_precedes);
568116612Sphantom			break;
569116612Sphantom		case KW_P_SEP_BY_SPACE:
570116612Sphantom			rval = &(lc->p_sep_by_space);
571116612Sphantom			break;
572116612Sphantom		case KW_N_CS_PRECEDES:
573116612Sphantom			rval = &(lc->n_cs_precedes);
574116612Sphantom			break;
575116612Sphantom		case KW_N_SEP_BY_SPACE:
576116612Sphantom			rval = &(lc->n_sep_by_space);
577116612Sphantom			break;
578116612Sphantom		case KW_P_SIGN_POSN:
579116612Sphantom			rval = &(lc->p_sign_posn);
580116612Sphantom			break;
581116612Sphantom		case KW_N_SIGN_POSN:
582116612Sphantom			rval = &(lc->n_sign_posn);
583116612Sphantom			break;
584116873Sphantom		case KW_INT_P_CS_PRECEDES:
585116873Sphantom			rval = &(lc->int_p_cs_precedes);
586116873Sphantom			break;
587116873Sphantom		case KW_INT_P_SEP_BY_SPACE:
588116873Sphantom			rval = &(lc->int_p_sep_by_space);
589116873Sphantom			break;
590116873Sphantom		case KW_INT_N_CS_PRECEDES:
591116873Sphantom			rval = &(lc->int_n_cs_precedes);
592116873Sphantom			break;
593116873Sphantom		case KW_INT_N_SEP_BY_SPACE:
594116873Sphantom			rval = &(lc->int_n_sep_by_space);
595116873Sphantom			break;
596116873Sphantom		case KW_INT_P_SIGN_POSN:
597116873Sphantom			rval = &(lc->int_p_sign_posn);
598116873Sphantom			break;
599116873Sphantom		case KW_INT_N_SIGN_POSN:
600116873Sphantom			rval = &(lc->int_n_sign_posn);
601116873Sphantom			break;
602116612Sphantom		default:
603116612Sphantom			break;
604116612Sphantom	}
605116612Sphantom	return (rval);
606105239Sphantom}
607105239Sphantom
608116612Sphantom/*
609116612Sphantom * keyword value and properties lookup
610116612Sphantom */
611116612Sphantomint
612310040Svangyzenkwval_lookup(const char *kwname, char **kwval, int *cat, int *isstr)
613105239Sphantom{
614116616Sphantom	int	rval;
615116616Sphantom	size_t	i;
616116612Sphantom
617116616Sphantom	rval = 0;
618116612Sphantom	for (i = 0; i < NKWINFO; i++) {
619116612Sphantom		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
620116612Sphantom			rval = 1;
621116612Sphantom			*cat = kwinfo[i].catid;
622116612Sphantom			*isstr = kwinfo[i].isstr;
623116612Sphantom			if (kwinfo[i].value_ref < KW_ZERO) {
624116612Sphantom				*kwval = nl_langinfo(kwinfo[i].value_ref);
625116612Sphantom			} else {
626116612Sphantom				*kwval = kwval_lconv(kwinfo[i].value_ref);
627116612Sphantom			}
628116612Sphantom			break;
629116612Sphantom		}
630116612Sphantom	}
631116612Sphantom
632116612Sphantom	return (rval);
633105239Sphantom}
634105239Sphantom
635116612Sphantom/*
636116612Sphantom * Show details about requested keyword according to '-k' and/or '-c'
637116612Sphantom * command line options specified.
638116612Sphantom */
639105239Sphantomvoid
640310040Svangyzenshowdetails(const char *kw)
641105239Sphantom{
642116616Sphantom	int	isstr, cat, tmpval;
643116616Sphantom	char	*kwval;
644105239Sphantom
645116612Sphantom	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
646116612Sphantom		/*
647116612Sphantom		 * invalid keyword specified.
648116612Sphantom		 * XXX: any actions?
649116612Sphantom		 */
650197764Sedwin		fprintf(stderr, "Unknown keyword: `%s'\n", kw);
651105239Sphantom		return;
652116612Sphantom	}
653105239Sphantom
654116612Sphantom	if (prt_categories) {
655310040Svangyzen		if (prt_keywords)
656242743Sgrog			printf("%-20s ", lookup_localecat(cat));
657310040Svangyzen		else
658242808Sgrog			printf("%-20s\t%s\n", kw, lookup_localecat(cat));
659116612Sphantom	}
660105239Sphantom
661116612Sphantom	if (prt_keywords) {
662116612Sphantom		if (isstr) {
663116612Sphantom			printf("%s=\"%s\"\n", kw, kwval);
664116612Sphantom		} else {
665116616Sphantom			tmpval = (char) *kwval;
666116616Sphantom			printf("%s=%d\n", kw, tmpval);
667116612Sphantom		}
668116612Sphantom	}
669105239Sphantom
670116612Sphantom	if (!prt_categories && !prt_keywords) {
671116612Sphantom		if (isstr) {
672116612Sphantom			printf("%s\n", kwval);
673116612Sphantom		} else {
674116616Sphantom			tmpval = (char) *kwval;
675116616Sphantom			printf("%d\n", tmpval);
676116612Sphantom		}
677105239Sphantom	}
678105239Sphantom}
679116675Sphantom
680116675Sphantom/*
681116675Sphantom * Convert locale category id into string
682116675Sphantom */
683116675Sphantomconst char *
684116675Sphantomlookup_localecat(int cat)
685116675Sphantom{
686116675Sphantom	size_t	i;
687116675Sphantom
688116675Sphantom	for (i = 0; i < NLCINFO; i++)
689116675Sphantom		if (lcinfo[i].id == cat) {
690116675Sphantom			return (lcinfo[i].name);
691116675Sphantom		}
692116675Sphantom	return ("UNKNOWN");
693116675Sphantom}
694116675Sphantom
695116675Sphantom/*
696116675Sphantom * Show list of keywords
697116675Sphantom */
698116675Sphantomvoid
699197764Sedwinshowkeywordslist(char *substring)
700116675Sphantom{
701116675Sphantom	size_t	i;
702116675Sphantom
703242851Sgrog#define	FMT "%-20s %-12s %-7s %-20s\n"
704116675Sphantom
705197764Sedwin	if (substring == NULL)
706197764Sedwin		printf("List of available keywords\n\n");
707197764Sedwin	else
708197764Sedwin		printf("List of available keywords starting with '%s'\n\n",
709197764Sedwin		    substring);
710116675Sphantom	printf(FMT, "Keyword", "Category", "Type", "Comment");
711116675Sphantom	printf("-------------------- ------------ ------- --------------------\n");
712116675Sphantom	for (i = 0; i < NKWINFO; i++) {
713197764Sedwin		if (substring != NULL) {
714197764Sedwin			if (strncmp(kwinfo[i].name, substring,
715197764Sedwin			    strlen(substring)) != 0)
716197764Sedwin				continue;
717197764Sedwin		}
718116675Sphantom		printf(FMT,
719116675Sphantom			kwinfo[i].name,
720116675Sphantom			lookup_localecat(kwinfo[i].catid),
721116675Sphantom			(kwinfo[i].isstr == 0) ? "number" : "string",
722116675Sphantom			kwinfo[i].comment);
723116675Sphantom	}
724116675Sphantom}
725