locale.c revision 116845
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 116845 2003-06-25 22:31:42Z phantom $
27 */
28
29/*
30 * XXX: implement missing int_* (LC_MONETARY) (require libc modification) and
31 *	era_* (LC_TIME) keywords (require libc & nl_langinfo(3) extensions)
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
38#include <sys/types.h>
39#include <dirent.h>
40#include <err.h>
41#include <locale.h>
42#include <langinfo.h>
43#include <paths.h>		/* for _PATH_LOCALE */
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <stringlist.h>
48#include <unistd.h>
49
50/* Local prototypes */
51void	init_locales_list(void);
52void	list_locales(void);
53const char *lookup_localecat(int);
54char	*kwval_lconv(int);
55int	kwval_lookup(char *, char **, int *, int *);
56void	showdetails(char *);
57void	showkeywordslist(void);
58void	showlocale(void);
59void	usage(void);
60
61/* Global variables */
62static StringList *locales = NULL;
63
64int	all_locales = 0;
65int	all_charmaps = 0;
66int	prt_categories = 0;
67int	prt_keywords = 0;
68int	more_params = 0;
69
70struct _lcinfo {
71	const char	*name;
72	int		id;
73} lcinfo [] = {
74	{ "LC_CTYPE",		LC_CTYPE },
75	{ "LC_COLLATE",		LC_COLLATE },
76	{ "LC_TIME",		LC_TIME },
77	{ "LC_NUMERIC",		LC_NUMERIC },
78	{ "LC_MONETARY",	LC_MONETARY },
79	{ "LC_MESSAGES",	LC_MESSAGES }
80};
81#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
82
83/* ids for values not referenced by nl_langinfo() */
84#define	KW_ZERO			10000
85#define	KW_GROUPING		(KW_ZERO+1)
86#define KW_INT_CURR_SYMBOL 	(KW_ZERO+2)
87#define KW_CURRENCY_SYMBOL 	(KW_ZERO+3)
88#define KW_MON_DECIMAL_POINT 	(KW_ZERO+4)
89#define KW_MON_THOUSANDS_SEP 	(KW_ZERO+5)
90#define KW_MON_GROUPING 	(KW_ZERO+6)
91#define KW_POSITIVE_SIGN 	(KW_ZERO+7)
92#define KW_NEGATIVE_SIGN 	(KW_ZERO+8)
93#define KW_INT_FRAC_DIGITS 	(KW_ZERO+9)
94#define KW_FRAC_DIGITS 		(KW_ZERO+10)
95#define KW_P_CS_PRECEDES 	(KW_ZERO+11)
96#define KW_P_SEP_BY_SPACE 	(KW_ZERO+12)
97#define KW_N_CS_PRECEDES 	(KW_ZERO+13)
98#define KW_N_SEP_BY_SPACE 	(KW_ZERO+14)
99#define KW_P_SIGN_POSN 		(KW_ZERO+15)
100#define KW_N_SIGN_POSN 		(KW_ZERO+16)
101
102struct _kwinfo {
103	const char	*name;
104	int		isstr;		/* true - string, false - number */
105	int		catid;		/* LC_* */
106	int		value_ref;
107	const char	*comment;
108} kwinfo [] = {
109	{ "charmap",		1, LC_CTYPE,	CODESET, "" },	/* hack */
110
111	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR, "" },
112	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP, "" },
113	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING, "" },
114	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR,
115	  "Same as decimal_point (FreeBSD only)" },		/* compat */
116	{ "thousep",		1, LC_NUMERIC,	THOUSEP,
117	  "Same as thousands_sep (FreeBSD only)" },		/* compat */
118
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, "(unavailable)" },
181	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT, "(unavailable)" },
182	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT, "(unavailable)" },
183	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT, "(unavailable)" },
184	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS, "" },
185	{ "d_md_order",		1, LC_TIME,	D_MD_ORDER,
186	  "(FreeBSD only)"				},	/* local */
187
188	{ "yesexpr",		1, LC_MESSAGES, YESEXPR, "" },
189	{ "noexpr",		1, LC_MESSAGES, NOEXPR, "" },
190	{ "yesstr",		1, LC_MESSAGES, YESSTR,
191	  "(POSIX legacy)" },					/* compat */
192	{ "nostr",		1, LC_MESSAGES, NOSTR,
193	  "(POSIX legacy)" }					/* compat */
194
195};
196#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
197
198int
199main(int argc, char *argv[])
200{
201	char	ch;
202	int	tmp;
203
204	while ((ch = getopt(argc, argv, "ackm")) != -1) {
205		switch (ch) {
206		case 'a':
207			all_locales = 1;
208			break;
209		case 'c':
210			prt_categories = 1;
211			break;
212		case 'k':
213			prt_keywords = 1;
214			break;
215		case 'm':
216			all_charmaps = 1;
217			break;
218		default:
219			usage();
220		}
221	}
222	argc -= optind;
223	argv += optind;
224
225	/* validate arguments */
226	if (all_locales && all_charmaps)
227		usage();
228	if ((all_locales || all_charmaps) && argc > 0)
229		usage();
230	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
231		usage();
232	if ((prt_categories || prt_keywords) && argc <= 0)
233		usage();
234
235	/* process '-a' */
236	if (all_locales) {
237		list_locales();
238		exit(0);
239	}
240
241	/* process '-m' */
242	if (all_charmaps) {
243		/*
244		 * XXX: charmaps are not supported by FreeBSD now.  It
245		 * need to be implemented as soon as localedef(1) implemented.
246		 */
247		exit(1);
248	}
249
250	/* check for special case '-k list' */
251	tmp = 0;
252	if (prt_keywords && argc > 0)
253		while (tmp < argc)
254			if (strcasecmp(argv[tmp++], "list") == 0) {
255				showkeywordslist();
256				exit(0);
257			}
258
259	/* process '-c' and/or '-k' */
260	if (prt_categories || prt_keywords || argc > 0) {
261		setlocale(LC_ALL, "");
262		while (argc > 0) {
263			showdetails(*argv);
264			argv++;
265			argc--;
266		}
267		exit(0);
268	}
269
270	/* no arguments, show current locale state */
271	showlocale();
272
273	return (0);
274}
275
276void
277usage(void)
278{
279	printf("Usage: locale [ -a | -m ]\n"
280               "       locale [ -ck ] name ...\n");
281	exit(1);
282}
283
284/*
285 * Output information about all available locales
286 *
287 * XXX actually output of this function does not guarantee that locale
288 *     is really available to application, since it can be broken or
289 *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
290 *     also validate these locales?
291 */
292void
293list_locales(void)
294{
295	size_t i;
296
297	init_locales_list();
298	for (i = 0; i < locales->sl_cur; i++) {
299		printf("%s\n", locales->sl_str[i]);
300	}
301}
302
303
304/*
305 * qsort() helper function
306 */
307static int
308scmp(const void *s1, const void *s2)
309{
310	return strcmp(*(const char **)s1, *(const char **)s2);
311}
312
313
314/*
315 * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
316 * environment variable is set)
317 */
318void
319init_locales_list(void)
320{
321	DIR *dirp;
322	struct dirent *dp;
323	const char *dirname;
324
325	/* why call this function twice ? */
326	if (locales != NULL)
327		return;
328
329	/* initialize StringList */
330	locales = sl_init();
331	if (locales == NULL)
332		err(1, "could not allocate memory");
333
334	/* get actual locales directory name */
335	dirname = getenv("PATH_LOCALE");
336	if (dirname == NULL)
337		dirname = _PATH_LOCALE;
338
339	/* open locales directory */
340	dirp = opendir(dirname);
341	if (dirp == NULL)
342		err(1, "could not open directory '%s'", dirname);
343
344	/* scan directory and store its contents except "." and ".." */
345	while ((dp = readdir(dirp)) != NULL) {
346		if (*(dp->d_name) == '.')
347			continue;		/* exclude "." and ".." */
348		sl_add(locales, strdup(dp->d_name));
349	}
350	closedir(dirp);
351
352        /* make sure that 'POSIX' and 'C' locales are present in the list.
353	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
354         * we also list 'C' for constistency
355         */
356	if (sl_find(locales, "POSIX") == NULL)
357		sl_add(locales, "POSIX");
358
359	if (sl_find(locales, "C") == NULL)
360		sl_add(locales, "C");
361
362	/* make output nicer, sort the list */
363	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
364}
365
366/*
367 * Show current locale status, depending on environment variables
368 */
369void
370showlocale(void)
371{
372	size_t	i;
373	const char *lang, *vval, *eval;
374
375	setlocale(LC_ALL, "");
376
377	lang = getenv("LANG");
378	if (lang == NULL) {
379		lang = "";
380	}
381	printf("LANG=%s\n", lang);
382	/* XXX: if LANG is null, then set it to "C" to get implied values? */
383
384	for (i = 0; i < NLCINFO; i++) {
385		vval = setlocale(lcinfo[i].id, NULL);
386		eval = getenv(lcinfo[i].name);
387		if (eval != NULL && !strcmp(eval, vval)
388				&& strcmp(lang, vval)) {
389			/*
390			 * Appropriate environment variable set, its value
391			 * is valid and not overriden by LC_ALL
392			 *
393			 * XXX: possible side effect: if both LANG and
394			 * overriden environment variable are set into same
395			 * value, then it'll be assumed as 'implied'
396			 */
397			printf("%s=%s\n", lcinfo[i].name, vval);
398		} else {
399			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
400		}
401	}
402
403	vval = getenv("LC_ALL");
404	if (vval == NULL) {
405		vval = "";
406	}
407	printf("LC_ALL=%s\n", vval);
408}
409
410/*
411 * keyword value lookup helper (via localeconv())
412 */
413char *
414kwval_lconv(int id)
415{
416	struct lconv *lc;
417	char *rval;
418
419	rval = NULL;
420	lc = localeconv();
421	switch (id) {
422		case KW_GROUPING:
423			rval = lc->grouping;
424			break;
425		case KW_INT_CURR_SYMBOL:
426			rval = lc->int_curr_symbol;
427			break;
428		case KW_CURRENCY_SYMBOL:
429			rval = lc->currency_symbol;
430			break;
431		case KW_MON_DECIMAL_POINT:
432			rval = lc->mon_decimal_point;
433			break;
434		case KW_MON_THOUSANDS_SEP:
435			rval = lc->mon_thousands_sep;
436			break;
437		case KW_MON_GROUPING:
438			rval = lc->mon_grouping;
439			break;
440		case KW_POSITIVE_SIGN:
441			rval = lc->positive_sign;
442			break;
443		case KW_NEGATIVE_SIGN:
444			rval = lc->negative_sign;
445			break;
446		case KW_INT_FRAC_DIGITS:
447			rval = &(lc->int_frac_digits);
448			break;
449		case KW_FRAC_DIGITS:
450			rval = &(lc->frac_digits);
451			break;
452		case KW_P_CS_PRECEDES:
453			rval = &(lc->p_cs_precedes);
454			break;
455		case KW_P_SEP_BY_SPACE:
456			rval = &(lc->p_sep_by_space);
457			break;
458		case KW_N_CS_PRECEDES:
459			rval = &(lc->n_cs_precedes);
460			break;
461		case KW_N_SEP_BY_SPACE:
462			rval = &(lc->n_sep_by_space);
463			break;
464		case KW_P_SIGN_POSN:
465			rval = &(lc->p_sign_posn);
466			break;
467		case KW_N_SIGN_POSN:
468			rval = &(lc->n_sign_posn);
469			break;
470		default:
471			break;
472	}
473	return (rval);
474}
475
476/*
477 * keyword value and properties lookup
478 */
479int
480kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
481{
482	int	rval;
483	size_t	i;
484
485	rval = 0;
486	for (i = 0; i < NKWINFO; i++) {
487		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
488			rval = 1;
489			*cat = kwinfo[i].catid;
490			*isstr = kwinfo[i].isstr;
491			if (kwinfo[i].value_ref < KW_ZERO) {
492				*kwval = nl_langinfo(kwinfo[i].value_ref);
493			} else {
494				*kwval = kwval_lconv(kwinfo[i].value_ref);
495			}
496			break;
497		}
498	}
499
500	return (rval);
501}
502
503/*
504 * Show details about requested keyword according to '-k' and/or '-c'
505 * command line options specified.
506 */
507void
508showdetails(char *kw)
509{
510	int	isstr, cat, tmpval;
511	char	*kwval;
512
513	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
514		/*
515		 * invalid keyword specified.
516		 * XXX: any actions?
517		 */
518		return;
519	}
520
521	if (prt_categories) {
522		printf("%s\n", lookup_localecat(cat));
523	}
524
525	if (prt_keywords) {
526		if (isstr) {
527			printf("%s=\"%s\"\n", kw, kwval);
528		} else {
529			tmpval = (char) *kwval;
530			printf("%s=%d\n", kw, tmpval);
531		}
532	}
533
534	if (!prt_categories && !prt_keywords) {
535		if (isstr) {
536			printf("%s\n", kwval);
537		} else {
538			tmpval = (char) *kwval;
539			printf("%d\n", tmpval);
540		}
541	}
542}
543
544/*
545 * Convert locale category id into string
546 */
547const char *
548lookup_localecat(int cat)
549{
550	size_t	i;
551
552	for (i = 0; i < NLCINFO; i++)
553		if (lcinfo[i].id == cat) {
554			return (lcinfo[i].name);
555		}
556	return ("UNKNOWN");
557}
558
559/*
560 * Show list of keywords
561 */
562void
563showkeywordslist(void)
564{
565	size_t	i;
566
567#define FMT "%-20s %-12s %-7s %-20s\n"
568
569	printf("List of available keywords\n\n");
570	printf(FMT, "Keyword", "Category", "Type", "Comment");
571	printf("-------------------- ------------ ------- --------------------\n");
572	for (i = 0; i < NKWINFO; i++) {
573		printf(FMT,
574			kwinfo[i].name,
575			lookup_localecat(kwinfo[i].catid),
576			(kwinfo[i].isstr == 0) ? "number" : "string",
577			kwinfo[i].comment);
578	}
579}
580