locale.c revision 116616
1/*-
2 * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: head/usr.bin/locale/locale.c 116616 2003-06-20 13:23:51Z phantom $
27 */
28
29/*
30 * XXX: implement missing int_* (LC_MONETARY) and era_* (LC_CTYIME) keywords
31 *      (require libc modification)
32 *
33 * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
34 *      localedef(1) implementation).  Currently it's handled via
35 *	nl_langinfo(CODESET).
36 *
37 * XXX: implement '-k list' to show all available keywords.  Add descriptions
38 *      for all of keywords (and mention FreeBSD only there)
39 *
40 */
41
42#include <sys/types.h>
43#include <dirent.h>
44#include <err.h>
45#include <locale.h>
46#include <langinfo.h>
47#include <rune.h>		/* for _PATH_LOCALE */
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <stringlist.h>
52#include <unistd.h>
53
54/* Local prototypes */
55void	init_locales_list(void);
56void	list_locales(void);
57char	*kwval_lconv(int);
58int	kwval_lookup(char *, char **, int *, int *);
59void	showdetails(char *);
60void	showlocale(void);
61void	usage(void);
62
63/* Global variables */
64static StringList *locales = NULL;
65
66int	all_locales = 0;
67int	all_charmaps = 0;
68int	prt_categories = 0;
69int	prt_keywords = 0;
70int	more_params = 0;
71
72struct _lcinfo {
73	const char	*name;
74	int		id;
75} lcinfo [] = {
76	{ "LC_CTYPE",		LC_CTYPE },
77	{ "LC_COLLATE",		LC_COLLATE },
78	{ "LC_TIME",		LC_TIME },
79	{ "LC_NUMERIC",		LC_NUMERIC },
80	{ "LC_MONETARY",	LC_MONETARY },
81	{ "LC_MESSAGES",	LC_MESSAGES }
82};
83#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
84
85/* ids for values not referenced by nl_langinfo() */
86#define	KW_ZERO			10000
87#define	KW_GROUPING		(KW_ZERO+1)
88#define KW_INT_CURR_SYMBOL 	(KW_ZERO+2)
89#define KW_CURRENCY_SYMBOL 	(KW_ZERO+3)
90#define KW_MON_DECIMAL_POINT 	(KW_ZERO+4)
91#define KW_MON_THOUSANDS_SEP 	(KW_ZERO+5)
92#define KW_MON_GROUPING 	(KW_ZERO+6)
93#define KW_POSITIVE_SIGN 	(KW_ZERO+7)
94#define KW_NEGATIVE_SIGN 	(KW_ZERO+8)
95#define KW_INT_FRAC_DIGITS 	(KW_ZERO+9)
96#define KW_FRAC_DIGITS 		(KW_ZERO+10)
97#define KW_P_CS_PRECEDES 	(KW_ZERO+11)
98#define KW_P_SEP_BY_SPACE 	(KW_ZERO+12)
99#define KW_N_CS_PRECEDES 	(KW_ZERO+13)
100#define KW_N_SEP_BY_SPACE 	(KW_ZERO+14)
101#define KW_P_SIGN_POSN 		(KW_ZERO+15)
102#define KW_N_SIGN_POSN 		(KW_ZERO+16)
103
104struct _kwinfo {
105	const char	*name;
106	int		isstr;		/* true - string, false - number */
107	int		catid;		/* LC_* */
108	int		value_ref;
109} kwinfo [] = {
110	{ "charmap",		1, LC_CTYPE,	CODESET },	/* hack */
111
112	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR },
113	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP },
114	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING },
115	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR },	/* compat */
116	{ "thousep",		1, LC_NUMERIC,	THOUSEP},	/* compat */
117
118	{ "currency_symbol",	1, LC_MONETARY, KW_CURRENCY_SYMBOL }, /*compat*/
119	{ "int_curr_symbol",	1, LC_MONETARY,	KW_INT_CURR_SYMBOL },
120	{ "currency_symbol",	1, LC_MONETARY,	KW_CURRENCY_SYMBOL },
121	{ "mon_decimal_point",	1, LC_MONETARY,	KW_MON_DECIMAL_POINT },
122	{ "mon_thousands_sep",	1, LC_MONETARY,	KW_MON_THOUSANDS_SEP },
123	{ "mon_grouping",	1, LC_MONETARY,	KW_MON_GROUPING },
124	{ "positive_sign",	1, LC_MONETARY,	KW_POSITIVE_SIGN },
125	{ "negative_sign",	1, LC_MONETARY,	KW_NEGATIVE_SIGN },
126
127	{ "int_frac_digits",	0, LC_MONETARY,	KW_INT_FRAC_DIGITS },
128	{ "frac_digits",	0, LC_MONETARY,	KW_FRAC_DIGITS },
129	{ "p_cs_precedes",	0, LC_MONETARY,	KW_P_CS_PRECEDES },
130	{ "p_sep_by_space",	0, LC_MONETARY,	KW_P_SEP_BY_SPACE },
131	{ "n_cs_precedes",	0, LC_MONETARY,	KW_N_CS_PRECEDES },
132	{ "n_sep_by_space",	0, LC_MONETARY,	KW_N_SEP_BY_SPACE },
133	{ "p_sign_posn",	0, LC_MONETARY,	KW_P_SIGN_POSN },
134	{ "n_sign_posn",	0, LC_MONETARY,	KW_N_SIGN_POSN },
135
136	{ "d_t_fmt",		1, LC_TIME,	D_T_FMT },
137	{ "d_fmt",		1, LC_TIME,	D_FMT },
138	{ "t_fmt",		1, LC_TIME,	T_FMT },
139	{ "am_str",		1, LC_TIME,	AM_STR },
140	{ "pm_str",		1, LC_TIME,	PM_STR },
141	{ "t_fmt_ampm",		1, LC_TIME,	T_FMT_AMPM },
142	{ "day_1",		1, LC_TIME,	DAY_1 },
143	{ "day_2",		1, LC_TIME,	DAY_2 },
144	{ "day_3",		1, LC_TIME,	DAY_3 },
145	{ "day_4",		1, LC_TIME,	DAY_4 },
146	{ "day_5",		1, LC_TIME,	DAY_5 },
147	{ "day_6",		1, LC_TIME,	DAY_6 },
148	{ "day_7",		1, LC_TIME,	DAY_7 },
149	{ "abday_1",		1, LC_TIME,	ABDAY_1 },
150	{ "abday_2",		1, LC_TIME,	ABDAY_2 },
151	{ "abday_3",		1, LC_TIME,	ABDAY_3 },
152	{ "abday_4",		1, LC_TIME,	ABDAY_4 },
153	{ "abday_5",		1, LC_TIME,	ABDAY_5 },
154	{ "abday_6",		1, LC_TIME,	ABDAY_6 },
155	{ "abday_7",		1, LC_TIME,	ABDAY_7 },
156	{ "mon_1",		1, LC_TIME,	MON_1 },
157	{ "mon_2",		1, LC_TIME,	MON_2 },
158	{ "mon_3",		1, LC_TIME,	MON_3 },
159	{ "mon_4",		1, LC_TIME,	MON_4 },
160	{ "mon_5",		1, LC_TIME,	MON_5 },
161	{ "mon_6",		1, LC_TIME,	MON_6 },
162	{ "mon_7",		1, LC_TIME,	MON_7 },
163	{ "mon_8",		1, LC_TIME,	MON_8 },
164	{ "mon_9",		1, LC_TIME,	MON_9 },
165	{ "mon_10",		1, LC_TIME,	MON_10 },
166	{ "mon_11",		1, LC_TIME,	MON_11 },
167	{ "mon_12",		1, LC_TIME,	MON_12 },
168	{ "abmon_1",		1, LC_TIME,	ABMON_1 },
169	{ "abmon_2",		1, LC_TIME,	ABMON_2 },
170	{ "abmon_3",		1, LC_TIME,	ABMON_3 },
171	{ "abmon_4",		1, LC_TIME,	ABMON_4 },
172	{ "abmon_5",		1, LC_TIME,	ABMON_5 },
173	{ "abmon_6",		1, LC_TIME,	ABMON_6 },
174	{ "abmon_7",		1, LC_TIME,	ABMON_7 },
175	{ "abmon_8",		1, LC_TIME,	ABMON_8 },
176	{ "abmon_9",		1, LC_TIME,	ABMON_9 },
177	{ "abmon_10",		1, LC_TIME,	ABMON_10 },
178	{ "abmon_11",		1, LC_TIME,	ABMON_11 },
179	{ "abmon_12",		1, LC_TIME,	ABMON_12 },
180	{ "era",		1, LC_TIME,	ERA },
181	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT },
182	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT },
183	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT },
184	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS },
185	{ "d_md_order",		1, LC_TIME,	D_MD_ORDER },	/* local */
186
187	{ "yesexpr",		1, LC_MESSAGES, YESEXPR },
188	{ "noexpr",		1, LC_MESSAGES, NOEXPR },
189	{ "yesstr",		1, LC_MESSAGES, YESSTR },	/* local */
190	{ "nostr",		1, LC_MESSAGES, NOSTR }		/* local */
191
192};
193#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
194
195int
196main(int argc, char *argv[])
197{
198	char	ch;
199
200	while ((ch = getopt(argc, argv, "ackm")) != -1) {
201		switch (ch) {
202		case 'a':
203			all_locales = 1;
204			break;
205		case 'c':
206			prt_categories = 1;
207			break;
208		case 'k':
209			prt_keywords = 1;
210			break;
211		case 'm':
212			all_charmaps = 1;
213			break;
214		default:
215			usage();
216		}
217	}
218	argc -= optind;
219	argv += optind;
220
221	/* validate arguments */
222	if (all_locales && all_charmaps)
223		usage();
224	if ((all_locales || all_charmaps) && argc > 0)
225		usage();
226	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
227		usage();
228	if ((prt_categories || prt_keywords) && argc <= 0)
229		usage();
230
231	/* process '-a' */
232	if (all_locales) {
233		list_locales();
234		exit(0);
235	}
236
237	/* process '-m' */
238	if (all_charmaps) {
239		/*
240		 * XXX: charmaps are not supported by FreeBSD now.  It
241		 * need to be implemented as soon as localedef(1) implemented.
242		 */
243		exit(1);
244	}
245
246	/* process '-c' and/or '-k' */
247	if (prt_categories || prt_keywords || argc > 0) {
248		setlocale(LC_ALL, "");
249		while (argc > 0) {
250			showdetails(*argv);
251			argv++;
252			argc--;
253		}
254		exit(0);
255	}
256
257	/* no arguments, show current locale state */
258	showlocale();
259
260	return (0);
261}
262
263void
264usage(void)
265{
266	printf("Usage: locale [ -a | -m ]\n"
267               "       locale [ -ck ] name ...\n");
268	exit(1);
269}
270
271/*
272 * Output information about all available locales
273 *
274 * XXX actually output of this function does not guarantee that locale
275 *     is really available to application, since it can be broken or
276 *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
277 *     also validate these locales?
278 */
279void
280list_locales(void)
281{
282	size_t i;
283
284	init_locales_list();
285	for (i = 0; i < locales->sl_cur; i++) {
286		printf("%s\n", locales->sl_str[i]);
287	}
288}
289
290
291/*
292 * qsort() helper function
293 */
294static int
295scmp(const void *s1, const void *s2)
296{
297	return strcmp(*(const char **)s1, *(const char **)s2);
298}
299
300
301/*
302 * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
303 * environment variable is set)
304 */
305void
306init_locales_list(void)
307{
308	DIR *dirp;
309	struct dirent *dp;
310	const char *dirname;
311
312	/* why call this function twice ? */
313	if (locales != NULL)
314		return;
315
316	/* initialize StringList */
317	locales = sl_init();
318	if (locales == NULL)
319		err(1, "could not allocate memory");
320
321	/* get actual locales directory name */
322	dirname = getenv("PATH_LOCALE");
323	if (dirname == NULL)
324		dirname = _PATH_LOCALE;
325
326	/* open locales directory */
327	dirp = opendir(dirname);
328	if (dirp == NULL)
329		err(1, "could not open directory '%s'", dirname);
330
331	/* scan directory and store its contents except "." and ".." */
332	while ((dp = readdir(dirp)) != NULL) {
333		if (*(dp->d_name) == '.')
334			continue;		/* exclude "." and ".." */
335		sl_add(locales, strdup(dp->d_name));
336	}
337	closedir(dirp);
338
339        /* make sure that 'POSIX' and 'C' locales are present in the list.
340	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
341         * we also list 'C' for constistency
342         */
343	if (sl_find(locales, "POSIX") == NULL)
344		sl_add(locales, "POSIX");
345
346	if (sl_find(locales, "C") == NULL)
347		sl_add(locales, "C");
348
349	/* make output nicer, sort the list */
350	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
351}
352
353/*
354 * Show current locale status, depending on environment variables
355 */
356void
357showlocale(void)
358{
359	size_t	i;
360	const char *lang, *vval, *eval;
361
362	setlocale(LC_ALL, "");
363
364	lang = getenv("LANG");
365	if (lang == NULL || *lang == '\0') {
366		lang = "";
367	}
368	printf("LANG=%s\n", lang);
369	/* XXX: if LANG is null, then set it to "C" to get implied values? */
370
371	for (i = 0; i < NLCINFO; i++) {
372		vval = setlocale(lcinfo[i].id, NULL);
373		eval = getenv(lcinfo[i].name);
374		if (eval != NULL && !strcmp(eval, vval)
375				&& strcmp(lang, vval)) {
376			/*
377			 * Appropriate environment variable set, its value
378			 * is valid and not overriden by LC_ALL
379			 *
380			 * XXX: possible side effect: if both LANG and
381			 * overriden environment variable are set into same
382			 * value, then it'll be assumed as 'implied'
383			 */
384			printf("%s=%s\n", lcinfo[i].name, vval);
385		} else {
386			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
387		}
388	}
389
390	vval = getenv("LC_ALL");
391	if (vval == NULL || *vval == '\0') {
392		vval = "";
393	}
394	printf("LC_ALL=%s\n", vval);
395}
396
397/*
398 * keyword value lookup helper (via localeconv())
399 */
400char *
401kwval_lconv(int id)
402{
403	struct lconv *lc;
404	char *rval;
405
406	rval = NULL;
407	lc = localeconv();
408	switch (id) {
409		case KW_GROUPING:
410			rval = lc->grouping;
411			break;
412		case KW_INT_CURR_SYMBOL:
413			rval = lc->int_curr_symbol;
414			break;
415		case KW_CURRENCY_SYMBOL:
416			rval = lc->currency_symbol;
417			break;
418		case KW_MON_DECIMAL_POINT:
419			rval = lc->mon_decimal_point;
420			break;
421		case KW_MON_THOUSANDS_SEP:
422			rval = lc->mon_thousands_sep;
423			break;
424		case KW_MON_GROUPING:
425			rval = lc->mon_grouping;
426			break;
427		case KW_POSITIVE_SIGN:
428			rval = lc->positive_sign;
429			break;
430		case KW_NEGATIVE_SIGN:
431			rval = lc->negative_sign;
432			break;
433		case KW_INT_FRAC_DIGITS:
434			rval = &(lc->int_frac_digits);
435			break;
436		case KW_FRAC_DIGITS:
437			rval = &(lc->frac_digits);
438			break;
439		case KW_P_CS_PRECEDES:
440			rval = &(lc->p_cs_precedes);
441			break;
442		case KW_P_SEP_BY_SPACE:
443			rval = &(lc->p_sep_by_space);
444			break;
445		case KW_N_CS_PRECEDES:
446			rval = &(lc->n_cs_precedes);
447			break;
448		case KW_N_SEP_BY_SPACE:
449			rval = &(lc->n_sep_by_space);
450			break;
451		case KW_P_SIGN_POSN:
452			rval = &(lc->p_sign_posn);
453			break;
454		case KW_N_SIGN_POSN:
455			rval = &(lc->n_sign_posn);
456			break;
457		default:
458			break;
459	}
460	return (rval);
461}
462
463/*
464 * keyword value and properties lookup
465 */
466int
467kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
468{
469	int	rval;
470	size_t	i;
471
472	rval = 0;
473	for (i = 0; i < NKWINFO; i++) {
474		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
475			rval = 1;
476			*cat = kwinfo[i].catid;
477			*isstr = kwinfo[i].isstr;
478			if (kwinfo[i].value_ref < KW_ZERO) {
479				*kwval = nl_langinfo(kwinfo[i].value_ref);
480			} else {
481				*kwval = kwval_lconv(kwinfo[i].value_ref);
482			}
483			break;
484		}
485	}
486
487	return (rval);
488}
489
490/*
491 * Show details about requested keyword according to '-k' and/or '-c'
492 * command line options specified.
493 */
494void
495showdetails(char *kw)
496{
497	int	isstr, cat, tmpval;
498	size_t	i;
499	char	*kwval;
500	const char *tmps;
501
502	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
503		/*
504		 * invalid keyword specified.
505		 * XXX: any actions?
506		 */
507		return;
508	}
509
510	if (prt_categories) {
511		tmps = NULL;
512		for (i = 0; i < NLCINFO; i++)
513			if (lcinfo[i].id == cat) {
514				tmps = lcinfo[i].name;
515				break;
516			}
517		if (tmps == NULL)
518			tmps = "UNKNOWN";
519		printf("%s\n", tmps);
520	}
521
522	if (prt_keywords) {
523		if (isstr) {
524			printf("%s=\"%s\"\n", kw, kwval);
525		} else {
526			tmpval = (char) *kwval;
527			printf("%s=%d\n", kw, tmpval);
528		}
529	}
530
531	if (!prt_categories && !prt_keywords) {
532		if (isstr) {
533			printf("%s\n", kwval);
534		} else {
535			tmpval = (char) *kwval;
536			printf("%d\n", tmpval);
537		}
538	}
539}
540