1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 * locale -- get current locale information
29 *
30 * Copyright 1991, 1993 by Mortice Kern Systems Inc.  All rights reserved.
31 *
32 */
33
34#pragma ident	"%Z%%M%	%I%	%E% SMI"
35
36/*
37 * locale: get locale-specific information
38 * usage:  locale [-a|-m]
39 *         locale [-ck] name ...
40 */
41
42/*
43 * New members added in the struct lconv by IEEE Std 1003.1-2001
44 * are always activated in the locale object.
45 * See <iso/locale_iso.h>.
46 */
47#define	_LCONV_C99
48
49#include <stdio.h>
50#include <stdlib.h>
51#include <limits.h>
52#include <string.h>
53#include <dirent.h>
54#include <ctype.h>
55#include <stddef.h>
56#include <nl_types.h>
57#include <langinfo.h>
58#include <locale.h>
59#include <sys/types.h>
60#include <sys/stat.h>
61
62#define	LC_LOCDEF	999	/* Random number! */
63
64#define	LOCALE_DIR		"/usr/lib/locale/"
65#define	CHARMAP_DIR		"/usr/lib/localedef/src/"
66#define	CHARMAP_NAME	"charmap.src"
67
68#define	GET_LOCALE	0
69#define	GET_CHARMAP	1
70#define	CSSIZE	128
71
72#ifndef isblank
73#define	isblank(c)	((__ctype + 1)[c] & _B)
74#endif
75
76enum types {
77	TYPE_STR,	/* char * */
78	TYPE_GROUP,	/* char *, for mon_grouping, and grouping */
79	TYPE_INT,	/* int */
80	TYPE_CHR,	/* char, printed as signed integer */
81	TYPE_PCHR,	/* char, printed as printable character */
82	TYPE_CTP,	/* ctype entry */
83	TYPE_CNVL,	/* convert to lower */
84	TYPE_CNVU,	/* convert to upper */
85	TYPE_COLLEL	/* print the multi-character collating elements */
86};
87
88static int	print_locale_info(char *keyword, int cflag, int kflag);
89static int	print_category(int category, int cflag, int kflag);
90static int	print_keyword(char *name, int cflag, int kflag);
91static void	usage(void);
92static void	print_all_info(int);
93static void	print_cur_locale(void);
94static void	outstr(char *s);
95static void	outchar(int);
96static void	prt_ctp(char *);
97static void	prt_cnv(char *);
98static void	prt_collel(char *);
99static char	get_escapechar(void);
100static char	get_commentchar(void);
101
102static char	*save_loc;
103
104/*
105 * yes/no is not in the localeconv structure for xpg style.
106 * We dummy up a new structure for purposes of the code below.
107 * If YESEXPR is available per XPG4, we use it.
108 * Otherwise, use YESSTR, the old method with less functionality from XPG3.
109 */
110struct yesno {
111	char	*yes_expr;
112	char	*no_expr;
113	char	*yes_str;
114	char	*no_str;
115};
116
117struct dtconv {
118	char	*date_time_format;
119	char	*date_format;
120	char	*time_format;
121	char	*time_format_ampm;
122	char	*am_string;
123	char	*pm_string;
124	char	*abbrev_day_names[7];
125	char	*day_names[7];
126	char	*abbrev_month_names[12];
127	char	*month_names[12];
128	char	*era;
129	char	*era_d_fmt;
130	char	*era_d_t_fmt;
131	char	*era_t_fmt;
132	char	*alt_digits;
133};
134
135struct localedef {
136	char	*charmap;
137	char	*code_set_name;
138	char	escape_char;
139	char	comment_char;
140	int		mb_cur_max;
141	int		mb_cur_min;
142};
143
144static struct yesno *
145getyesno(void)
146{
147	static struct yesno	yn;
148	static int	loaded = 0;
149
150	if (loaded) {
151		return (&yn);
152		/* NOTREACHED */
153	}
154
155	yn.yes_expr = strdup(nl_langinfo(YESEXPR));
156	yn.no_expr = strdup(nl_langinfo(NOEXPR));
157	yn.yes_str = strdup(nl_langinfo(YESSTR));
158	yn.no_str = strdup(nl_langinfo(NOSTR));
159
160	loaded = 1;
161	return (&yn);
162}
163
164static struct dtconv *
165localedtconv(void)
166{
167	static struct dtconv	_dtconv;
168	static int				loaded = 0;
169
170	if (loaded) {
171		return (&_dtconv);
172		/* NOTREACHED */
173	}
174
175	_dtconv.date_time_format = strdup(nl_langinfo(D_T_FMT));
176	_dtconv.date_format = strdup(nl_langinfo(D_FMT));
177	_dtconv.time_format = strdup(nl_langinfo(T_FMT));
178	_dtconv.time_format_ampm = strdup(nl_langinfo(T_FMT_AMPM));
179	_dtconv.am_string = strdup(nl_langinfo(AM_STR));
180	_dtconv.pm_string = strdup(nl_langinfo(PM_STR));
181	_dtconv.abbrev_day_names[0] = strdup(nl_langinfo(ABDAY_1));
182	_dtconv.abbrev_day_names[1] = strdup(nl_langinfo(ABDAY_2));
183	_dtconv.abbrev_day_names[2] = strdup(nl_langinfo(ABDAY_3));
184	_dtconv.abbrev_day_names[3] = strdup(nl_langinfo(ABDAY_4));
185	_dtconv.abbrev_day_names[4] = strdup(nl_langinfo(ABDAY_5));
186	_dtconv.abbrev_day_names[5] = strdup(nl_langinfo(ABDAY_6));
187	_dtconv.abbrev_day_names[6] = strdup(nl_langinfo(ABDAY_7));
188	_dtconv.day_names[0] = strdup(nl_langinfo(DAY_1));
189	_dtconv.day_names[1] = strdup(nl_langinfo(DAY_2));
190	_dtconv.day_names[2] = strdup(nl_langinfo(DAY_3));
191	_dtconv.day_names[3] = strdup(nl_langinfo(DAY_4));
192	_dtconv.day_names[4] = strdup(nl_langinfo(DAY_5));
193	_dtconv.day_names[5] = strdup(nl_langinfo(DAY_6));
194	_dtconv.day_names[6] = strdup(nl_langinfo(DAY_7));
195	_dtconv.abbrev_month_names[0] = strdup(nl_langinfo(ABMON_1));
196	_dtconv.abbrev_month_names[1] = strdup(nl_langinfo(ABMON_2));
197	_dtconv.abbrev_month_names[2] = strdup(nl_langinfo(ABMON_3));
198	_dtconv.abbrev_month_names[3] = strdup(nl_langinfo(ABMON_4));
199	_dtconv.abbrev_month_names[4] = strdup(nl_langinfo(ABMON_5));
200	_dtconv.abbrev_month_names[5] = strdup(nl_langinfo(ABMON_6));
201	_dtconv.abbrev_month_names[6] = strdup(nl_langinfo(ABMON_7));
202	_dtconv.abbrev_month_names[7] = strdup(nl_langinfo(ABMON_8));
203	_dtconv.abbrev_month_names[8] = strdup(nl_langinfo(ABMON_9));
204	_dtconv.abbrev_month_names[9] = strdup(nl_langinfo(ABMON_10));
205	_dtconv.abbrev_month_names[10] = strdup(nl_langinfo(ABMON_11));
206	_dtconv.abbrev_month_names[11] = strdup(nl_langinfo(ABMON_12));
207	_dtconv.month_names[0] = strdup(nl_langinfo(MON_1));
208	_dtconv.month_names[1] = strdup(nl_langinfo(MON_2));
209	_dtconv.month_names[2] = strdup(nl_langinfo(MON_3));
210	_dtconv.month_names[3] = strdup(nl_langinfo(MON_4));
211	_dtconv.month_names[4] = strdup(nl_langinfo(MON_5));
212	_dtconv.month_names[5] = strdup(nl_langinfo(MON_6));
213	_dtconv.month_names[6] = strdup(nl_langinfo(MON_7));
214	_dtconv.month_names[7] = strdup(nl_langinfo(MON_8));
215	_dtconv.month_names[8] = strdup(nl_langinfo(MON_9));
216	_dtconv.month_names[9] = strdup(nl_langinfo(MON_10));
217	_dtconv.month_names[10] = strdup(nl_langinfo(MON_11));
218	_dtconv.month_names[11] = strdup(nl_langinfo(MON_12));
219	_dtconv.era = strdup(nl_langinfo(ERA));
220	_dtconv.era_d_fmt = strdup(nl_langinfo(ERA_D_FMT));
221	_dtconv.era_d_t_fmt = strdup(nl_langinfo(ERA_D_T_FMT));
222	_dtconv.era_t_fmt = strdup(nl_langinfo(ERA_T_FMT));
223	_dtconv.alt_digits = strdup(nl_langinfo(ALT_DIGITS));
224
225	loaded = 1;
226	return (&_dtconv);
227}
228
229static struct localedef *
230localeldconv(void)
231{
232	static struct localedef	_locdef;
233	static int	loaded = 0;
234
235	if (loaded) {
236		return (&_locdef);
237		/* NOTREACHED */
238	}
239
240	_locdef.charmap = strdup(nl_langinfo(CODESET));
241	_locdef.code_set_name = strdup(nl_langinfo(CODESET));
242	_locdef.mb_cur_max = MB_CUR_MAX;
243	_locdef.mb_cur_min = 1;
244	_locdef.escape_char = get_escapechar();
245	_locdef.comment_char = get_commentchar();
246
247	loaded = 1;
248	return (&_locdef);
249}
250
251/*
252 * The locale_name array also defines a canonical ordering for the categories.
253 * The function tocanon() translates the LC_* manifests to their canonical
254 * values.
255 */
256static struct locale_name {
257	char	*name;
258	int 	category;
259} locale_name[] = {
260	{"LC_CTYPE",	LC_CTYPE},
261	{"LC_NUMERIC",	LC_NUMERIC},
262	{"LC_TIME",		LC_TIME},
263	{"LC_COLLATE",	LC_COLLATE},
264	{"LC_MONETARY",	LC_MONETARY},
265	{"LC_MESSAGES",	LC_MESSAGES},
266	{"LC_ALL",		LC_ALL},
267	NULL
268};
269
270/*
271 * The structure key contains all keywords string name,
272 * symbolic name, category, and type (STR INT ...)
273 * the type will decide the way the value of the item be printed out
274 */
275static struct key {
276	char		*name;
277	void		*(*structure)(void);
278	int			offset;
279	int			count;
280	int			category;
281	enum types	type;
282} key[] = {
283
284#define	SPECIAL		0, 0, 0,
285	{"lower",	SPECIAL	LC_CTYPE,	TYPE_CTP},
286	{"upper",	SPECIAL	LC_CTYPE,	TYPE_CTP},
287	{"alpha",	SPECIAL	LC_CTYPE,	TYPE_CTP},
288	{"digit",	SPECIAL	LC_CTYPE,	TYPE_CTP},
289	{"space",	SPECIAL	LC_CTYPE,	TYPE_CTP},
290	{"cntrl",	SPECIAL	LC_CTYPE,	TYPE_CTP},
291	{"punct",	SPECIAL	LC_CTYPE,	TYPE_CTP},
292	{"graph",	SPECIAL	LC_CTYPE,	TYPE_CTP},
293	{"print",	SPECIAL	LC_CTYPE,	TYPE_CTP},
294	{"xdigit",	SPECIAL	LC_CTYPE,	TYPE_CTP},
295	{"blank",	SPECIAL	LC_CTYPE,	TYPE_CTP},
296
297	{"tolower",	SPECIAL	LC_CTYPE,	TYPE_CNVL},
298	{"toupper",	SPECIAL	LC_CTYPE,	TYPE_CNVU},
299
300	{"collating-element",	0, 0, 0, LC_COLLATE,	TYPE_COLLEL},
301	{"character-collation",	0, 1, 0, LC_COLLATE,	TYPE_COLLEL},
302
303#define	dt(member, count) \
304		(void *(*)(void))localedtconv, \
305		offsetof(struct dtconv, member), \
306		count, \
307		LC_TIME, \
308		TYPE_STR
309	{"d_t_fmt",	dt(date_time_format, 1)},
310	{"d_fmt",	dt(date_format, 1)},
311	{"t_fmt",	dt(time_format, 1)},
312	{"t_fmt_ampm",	dt(time_format_ampm, 1)},
313	{"am_pm",	dt(am_string, 2)},
314	{"day",		dt(day_names, 7)},
315	{"abday",	dt(abbrev_day_names, 7)},
316	{"mon",		dt(month_names, 12)},
317	{"abmon",	dt(abbrev_month_names, 12)},
318	{"era",		dt(era, 1)},
319	{"era_d_fmt",	dt(era_d_fmt, 1)},
320	{"era_d_t_fmt",	dt(era_d_t_fmt, 1)},
321	{"era_t_fmt",	dt(era_t_fmt, 1)},
322	{"alt_digits",	dt(alt_digits, 1)},
323
324#undef dt
325
326#define	lc(member, locale, type) \
327		(void *(*)(void))localeconv, \
328		offsetof(struct lconv, member), \
329		1, \
330		locale, \
331		type
332{"decimal_point",	lc(decimal_point, 	LC_NUMERIC, TYPE_STR) },
333{"thousands_sep",	lc(thousands_sep, 	LC_NUMERIC, TYPE_STR) },
334{"grouping",		lc(grouping,		LC_NUMERIC, TYPE_GROUP)},
335{"int_curr_symbol",	lc(int_curr_symbol,	LC_MONETARY, TYPE_STR)},
336{"currency_symbol",	lc(currency_symbol,	LC_MONETARY, TYPE_STR)},
337{"mon_decimal_point",	lc(mon_decimal_point,	LC_MONETARY, TYPE_STR)},
338{"mon_thousands_sep",	lc(mon_thousands_sep,	LC_MONETARY, TYPE_STR)},
339{"mon_grouping",	lc(mon_grouping,	LC_MONETARY, TYPE_GROUP)},
340{"positive_sign",	lc(positive_sign,	LC_MONETARY, TYPE_STR)},
341{"negative_sign",	lc(negative_sign,	LC_MONETARY, TYPE_STR)},
342
343{"int_frac_digits",	lc(int_frac_digits,	LC_MONETARY, TYPE_CHR)},
344{"frac_digits",		lc(frac_digits,		LC_MONETARY, TYPE_CHR)},
345{"p_cs_precedes",	lc(p_cs_precedes,	LC_MONETARY, TYPE_CHR)},
346{"p_sep_by_space",	lc(p_sep_by_space,	LC_MONETARY, TYPE_CHR)},
347{"n_cs_precedes",	lc(n_cs_precedes,	LC_MONETARY, TYPE_CHR)},
348{"n_sep_by_space",	lc(n_sep_by_space,	LC_MONETARY, TYPE_CHR)},
349{"p_sign_posn",		lc(p_sign_posn,		LC_MONETARY, TYPE_CHR)},
350{"n_sign_posn",		lc(n_sign_posn,		LC_MONETARY, TYPE_CHR)},
351{"int_p_cs_precedes",	lc(int_p_cs_precedes,	LC_MONETARY, TYPE_CHR)},
352{"int_p_sep_by_space",	lc(int_p_sep_by_space,	LC_MONETARY, TYPE_CHR)},
353{"int_n_cs_precedes",	lc(int_n_cs_precedes,	LC_MONETARY, TYPE_CHR)},
354{"int_n_sep_by_space",	lc(int_n_sep_by_space,	LC_MONETARY, TYPE_CHR)},
355{"int_p_sign_posn",	lc(int_p_sign_posn,	LC_MONETARY, TYPE_CHR)},
356{"int_n_sign_posn",	lc(int_n_sign_posn,	LC_MONETARY, TYPE_CHR)},
357
358#undef lc
359#define	lc(member) \
360		(void *(*)(void))getyesno, \
361		offsetof(struct yesno, member), \
362		1, \
363		LC_MESSAGES, \
364		TYPE_STR
365	{"yesexpr",		lc(yes_expr)},
366	{"noexpr",		lc(no_expr)},
367	{"yesstr",		lc(yes_str)},
368	{"nostr",		lc(no_str)},
369#undef lc
370
371	/*
372	 * Following keywords have no official method of obtaining them
373	 */
374#define	ld(member, locale, type) \
375		(void *(*)(void))localeldconv, \
376		offsetof(struct localedef, member), \
377		1, \
378		locale, \
379		type
380	{"charmap",		ld(charmap,		LC_LOCDEF, TYPE_STR)},
381	{"code_set_name",	ld(code_set_name,	LC_LOCDEF, TYPE_STR)},
382	{"escape_char",		ld(escape_char,		LC_LOCDEF, TYPE_PCHR)},
383	{"comment_char",	ld(comment_char,	LC_LOCDEF, TYPE_PCHR)},
384	{"mb_cur_max",		ld(mb_cur_max,		LC_LOCDEF, TYPE_INT)},
385	{"mb_cur_min",		ld(mb_cur_min,		LC_LOCDEF, TYPE_INT)},
386#undef ld
387
388	{NULL,			NULL,			0, 0}
389};
390
391static char escapec;
392
393int
394main(int argc, char **argv)
395{
396	int		c;
397	int		retval = 0;
398	int		cflag, kflag, aflag, mflag;
399
400	(void) setlocale(LC_ALL, "");
401#if !defined(TEXT_DOMAIN)
402#define	TEXT_DOMAIN	"SYS_TEST"
403#endif
404	(void) textdomain(TEXT_DOMAIN);
405
406	cflag = kflag = aflag = mflag = 0;
407
408	while ((c = getopt(argc, argv, "amck")) != EOF) {
409		switch (c) {
410		case 'a':
411			aflag = 1;
412			break;
413		case 'm':
414			mflag = 1;
415			break;
416		case 'c':
417			cflag = 1;
418			break;
419		case 'k':
420			kflag = 1;
421			break;
422		default:
423			usage();
424			/* NOTREACHED */
425			break;
426		}
427	}
428
429	/* -a OR -m OR (-c and/or -k) */
430	if ((aflag && mflag) || ((aflag || mflag) && (cflag || kflag))) {
431		usage();
432		/* NOTREACHED */
433	}
434
435	escapec = get_escapechar();
436
437	if (aflag) {
438		print_all_info(GET_LOCALE);
439		/* NOTREACHED */
440	}
441
442	if (mflag) {
443		print_all_info(GET_CHARMAP);
444		/* NOTREACHED */
445	}
446
447	if (optind == argc && !cflag && !kflag) {
448		print_cur_locale();
449		/* NOTREACHED */
450	}
451	if (optind == argc) {
452		usage();
453		/* NOTREACHED */
454	}
455
456	for (; optind < argc; optind++) {
457		retval += print_locale_info(argv[optind], cflag, kflag);
458	}
459	return (retval);
460}
461
462/*
463 * No options or operands.
464 * Print out the current locale names from the environment, or implied.
465 * Variables directly set in the environment are printed as-is, those
466 * implied are printed in quotes.
467 * The strings are printed ``appropriately quoted for possible later re-entry
468 * to the shell''.  We use the routine outstr to do this -- however we
469 * want the shell escape character, the backslash, not the locale escape
470 * character, so we quietly save and restore the locale escape character.
471 */
472static void
473print_cur_locale(void)
474{
475	char	*lc_allp;
476	char	*env, *eff;
477	int		i;
478
479	if ((env = getenv("LANG")) != NULL) {
480		(void) printf("LANG=%s\n", env);
481	} else {
482		(void) printf("LANG=\n");
483	}
484
485	lc_allp = getenv("LC_ALL");
486
487	for (i = 0; i < LC_ALL; i++) {
488		(void) printf("%s=", locale_name[i].name);
489		eff = setlocale(i, NULL);
490		if (eff == NULL) {
491			eff = "";
492		}
493		env = getenv(locale_name[i].name);
494
495		if (env == NULL) {
496			(void) putchar('"');
497			outstr(eff);
498			(void) putchar('"');
499		} else {
500			if (strcmp(env, eff) != 0) {
501				(void) putchar('"');
502				outstr(eff);
503				(void) putchar('"');
504			} else {
505				outstr(eff);
506			}
507		}
508		(void) putchar('\n');
509	}
510
511	(void) printf("LC_ALL=");
512	if (lc_allp != NULL) {
513		outstr(lc_allp);
514	}
515	(void) putchar('\n');
516	exit(0);
517}
518
519static int	num_of_loc = 0;
520static int	num_of_entries = 0;
521static char	**entries = NULL;
522
523static void
524add_loc_entry(char *loc)
525{
526#define	_INC_NUM	10
527	char	*s;
528
529	if (num_of_loc >= num_of_entries) {
530		char	**tmp;
531		num_of_entries += _INC_NUM;
532		tmp = realloc(entries, sizeof (char *) * num_of_entries);
533		if (tmp == NULL) {
534			/* restoring original locale */
535			(void) setlocale(LC_ALL, save_loc);
536			(void) fprintf(stderr,
537			    gettext("locale: cannot allocate buffer"));
538			exit(1);
539		}
540		entries = tmp;
541	}
542	s = strdup(loc);
543	if (s == NULL) {
544		/* restoring original locale */
545		(void) setlocale(LC_ALL, save_loc);
546		(void) fprintf(stderr,
547		    gettext("locale: cannot allocate buffer"));
548		exit(1);
549	}
550	entries[num_of_loc] = s;
551
552	num_of_loc++;
553}
554
555static int
556loccmp(const char **str1, const char **str2)
557{
558	return (strcmp(*str1, *str2));
559}
560
561static void
562show_loc_entry(void)
563{
564	int	i;
565
566	qsort(entries, num_of_loc, sizeof (char *),
567	    (int (*)(const void *, const void *))loccmp);
568	for (i = 0; i < num_of_loc; i++) {
569		(void) printf("%s\n", entries[i]);
570	}
571}
572
573static void
574check_loc(char *loc)
575{
576	int	cat;
577
578	/* first, try LC_ALL */
579	if (setlocale(LC_ALL, loc) != NULL) {
580		/* succeeded */
581		add_loc_entry(loc);
582		return;
583	}
584
585	/*
586	 * LC_ALL failed.
587	 * try each category.
588	 */
589	for (cat = 0; cat <= _LastCategory; cat++) {
590		if (setlocale(cat, loc) != NULL) {
591			/* succeeded */
592			add_loc_entry(loc);
593			return;
594		}
595	}
596
597	/* loc is not a valid locale */
598}
599
600/*
601 * print_all_info(): Print out all the locales and
602 *                   charmaps supported by the system
603 */
604static void
605print_all_info(int flag)
606{
607	struct dirent	*direntp;
608	DIR				*dirp;
609	char			*filename;		/* filename[PATH_MAX] */
610	char			*p;
611
612	if ((filename = malloc(PATH_MAX)) == NULL) {
613		(void) fprintf(stderr,
614		    gettext("locale: cannot allocate buffer"));
615		exit(1);
616	}
617
618	(void) memset(filename, 0, PATH_MAX);
619
620	if (flag == GET_LOCALE) {
621		/* save the current locale */
622		save_loc = setlocale(LC_ALL, NULL);
623
624		(void) strcpy(filename, LOCALE_DIR);
625		add_loc_entry("POSIX");
626	} else {						/* CHARMAP */
627		(void) strcpy(filename, CHARMAP_DIR);
628	}
629
630	if ((dirp = opendir(filename)) == NULL) {
631		if (flag == GET_LOCALE)
632			exit(0);
633		else {					/* CHARMAP */
634			(void) fprintf(stderr, gettext(
635			    "locale: charmap information not available.\n"));
636			exit(2);
637		}
638	}
639
640	p = filename + strlen(filename);
641	while ((direntp = readdir(dirp)) != NULL) {
642		struct stat stbuf;
643
644		(void) strcpy(p, direntp->d_name);
645		if (stat(filename, &stbuf) < 0) {
646			continue;
647		}
648
649		if (flag == GET_LOCALE) {
650			if (S_ISDIR(stbuf.st_mode) &&
651			    (direntp->d_name[0] != '.') &&
652			    /* "POSIX" has already been printed out */
653			    strcmp(direntp->d_name, "POSIX") != 0) {
654				check_loc(direntp->d_name);
655			}
656		} else {			/* CHARMAP */
657			if (S_ISDIR(stbuf.st_mode) &&
658			    direntp->d_name[0] != '.') {
659				struct dirent	*direntc;
660				DIR		*dirc;
661				char		*charmap;
662				char		*c;
663
664				if ((charmap = malloc(PATH_MAX)) == NULL) {
665					(void) fprintf(stderr,
666				    gettext("locale: cannot allocate buffer"));
667					exit(1);
668				}
669
670				(void) memset(charmap, 0, PATH_MAX);
671
672				(void) strcpy(charmap, filename);
673
674				if ((dirc = opendir(charmap)) == NULL) {
675					exit(0);
676				}
677
678				c = charmap + strlen(charmap);
679				*c++ = '/';
680				while ((direntc = readdir(dirc)) != NULL) {
681					struct stat stbuf;
682
683					(void) strcpy(c, direntc->d_name);
684					if (stat(charmap, &stbuf) < 0) {
685						continue;
686					}
687
688					if (S_ISREG(stbuf.st_mode) &&
689						(strcmp(direntc->d_name,
690							CHARMAP_NAME) == 0) &&
691						(direntc->d_name[0] != '.')) {
692						(void) printf("%s/%s\n",
693							p, direntc->d_name);
694					}
695				}
696				(void) closedir(dirc);
697				free(charmap);
698			}
699		}
700	}
701	if (flag == GET_LOCALE) {
702		/* restore the saved loc */
703		(void) setlocale(LC_ALL, save_loc);
704		show_loc_entry();
705	}
706	(void) closedir(dirp);
707	free(filename);
708	exit(0);
709}
710
711/*
712 * Print out the keyword value or category info.
713 * Call print_category() to print the entire locale category, if the name
714 * given is recognized as a category.
715 * Otherwise, assume that it is a keyword, and call print_keyword().
716 */
717static int
718print_locale_info(char *name, int cflag, int kflag)
719{
720	int i;
721
722	for (i = 0; locale_name[i].name != NULL; i++) {
723		if (strcmp(locale_name[i].name, name) == 0) {
724			/*
725			 * name is a category name
726			 * print out all keywords in this category
727			 */
728			return (print_category(locale_name[i].category,
729				cflag, kflag));
730		}
731	}
732
733	/* The name is a keyword name */
734	return (print_keyword(name, cflag, kflag));
735}
736
737/*
738 * Print out the value of the keyword
739 */
740static int
741print_keyword(char *name, int cflag, int kflag)
742{
743	int		i, j;
744	int		first_flag = 1;
745	int		found = 0;
746
747	for (i = 0; key[i].name != NULL; i++) {
748		if (strcmp(key[i].name, name) != 0) {
749			continue;
750		}
751
752		found = 1;
753		if (first_flag && cflag && key[i].category != LC_LOCDEF) {
754			/* print out this category's name */
755			(void) printf("%s\n",
756				locale_name[key[i].category].name);
757		}
758		if (kflag) {
759			(void) printf("%s=", name);
760		}
761		switch (key[i].type) {
762
763		/*
764		 * The grouping fields are a set of bytes, each of which
765		 * is the numeric value of the next group size, terminated
766		 * by a \0, or by CHAR_MAX
767		 */
768		case TYPE_GROUP:
769			{
770				void	*s;
771				char	*q;
772				int		first = 1;
773
774				s = (*key[i].structure)();
775				/* LINTED */
776				q = *(char **)((char *)s + key[i].offset);
777				if (*q == '\0') {
778					(void) printf("-1");
779					break;
780				}
781				while (*q != '\0' && *q != CHAR_MAX) {
782					if (!first) {
783						(void) putchar(';');
784					}
785					first = 0;
786					(void) printf("%u",
787					    *(unsigned char *)q++);
788				}
789				/* CHAR_MAX: no further grouping performed. */
790				if (!first) {
791					(void) putchar(';');
792				}
793				if (*q == CHAR_MAX) {
794					(void) printf("-1");
795				} else {
796					(void) putchar('0');
797				}
798			}
799			break;
800
801		/*
802		 * Entries like decimal_point states ``the decimal-point
803		 * character...'' not string.  However, it is a char *.
804		 * This assumes single, narrow, character.
805		 * Should it permit multibyte characters?
806		 * Should it permit a whole string, in that case?
807		 */
808		case TYPE_STR:
809			{
810				void	*s;
811				char	**q;
812
813				s = (*key[i].structure)();
814				/* LINTED */
815				q = (char **)((char *)s + key[i].offset);
816				for (j = 0; j < key[i].count; j++) {
817					if (j != 0) {
818						(void) printf(";");
819					}
820					if (kflag) {
821						(void) printf("\"");
822						outstr(q[j]);
823						(void) printf("\"");
824					} else {
825						(void) printf("%s", q[j]);
826					}
827				}
828			}
829			break;
830
831		case TYPE_INT:
832			{
833				void	*s;
834				int		*q;
835
836				s = (*key[i].structure)();
837				/* LINTED */
838				q = (int *)((char *)s + key[i].offset);
839				(void) printf("%d", *q);
840			}
841			break;
842
843		/*
844		 * TYPE_CHR: Single byte integer.
845		 */
846		case TYPE_CHR:
847			{
848				void	*s;
849				char	*q;
850
851				s = (*key[i].structure)();
852				q = (char *)((char *)s + key[i].offset);
853				if (*q == CHAR_MAX) {
854					(void) printf("-1");
855				} else {
856					(void) printf("%u",
857					    *(unsigned char *)q);
858				}
859			}
860			break;
861
862		/*
863		 * TYPE_PCHR: Single byte, printed as a character if printable
864		 */
865		case TYPE_PCHR:
866			{
867				void	*s;
868				char	*q;
869
870				s = (*key[i].structure)();
871				q = (char *)((char *)s + key[i].offset);
872				if (isprint(*(unsigned char *)q)) {
873					if (kflag) {
874						(void) printf("\"");
875						if ((*q == '\\') ||
876							(*q == ';') ||
877							(*q == '"')) {
878							(void) putchar(escapec);
879							(void) printf("%c",
880							*(unsigned char *)q);
881						} else {
882							(void) printf("%c",
883							*(unsigned char *)q);
884						}
885						(void) printf("\"");
886					} else {
887						(void) printf("%c",
888						    *(unsigned char *)q);
889					}
890				} else if (*q == (char)-1) {
891					/* In case no signed chars */
892					(void) printf("-1");
893				} else {
894					(void) printf("%u",
895					    *(unsigned char *)q);
896				}
897			}
898			break;
899
900		case TYPE_CTP:
901			{
902				prt_ctp(key[i].name);
903			}
904			break;
905
906		case TYPE_CNVU:
907			{
908				prt_cnv(key[i].name);
909			}
910			break;
911
912		case TYPE_CNVL:
913			{
914				prt_cnv(key[i].name);
915			}
916			break;
917
918		case TYPE_COLLEL:
919			{
920				prt_collel(key[i].name);
921			}
922			break;
923		}
924	}
925	if (found) {
926		(void) printf("\n");
927		return (0);
928	} else {
929		(void) fprintf(stderr,
930		    gettext("Unknown keyword name '%s'.\n"), name);
931		return (1);
932	}
933}
934
935/*
936 * Strings being outputed have to use an unambiguous format --  escape
937 * any potentially bad output characters.
938 * The standard says that any control character shall be preceeded by
939 * the escape character.  But it doesn't say that you can format that
940 * character at all.
941 * Question: If the multibyte character contains a quoting character,
942 * should that *byte* be escaped?
943 */
944static void
945outstr(char *s)
946{
947	wchar_t	ws;
948	int		c;
949	size_t	mbcurmax = MB_CUR_MAX;
950
951	while (*s != '\0') {
952		c = mbtowc(&ws, s, mbcurmax);
953		if (c < 0) {
954			s++;
955		} else if (c == 1) {
956			outchar(*s++);
957		} else {
958			for (; c > 0; c--) {
959				(void) putchar(*s++);
960			}
961		}
962	}
963}
964
965static void
966outchar(int c)
967{
968	unsigned char	uc;
969
970	uc = (unsigned char) c;
971
972	if ((uc == '\\') || (uc == ';') || (uc == '"')) {
973		(void) putchar(escapec);
974		(void) putchar(uc);
975	} else if (iscntrl(uc)) {
976		(void) printf("%cx%02x", escapec, uc);
977	} else {
978		(void) putchar(uc);
979	}
980}
981
982/*
983 * print_category(): Print out all the keyword's value
984 *                  in the given category
985 */
986static int
987print_category(int category, int cflag, int kflag)
988{
989	int		i;
990	int		retval = 0;
991
992	if (category == LC_ALL) {
993		retval += print_category(LC_CTYPE, cflag, kflag);
994		retval += print_category(LC_NUMERIC, cflag, kflag);
995		retval += print_category(LC_TIME, cflag, kflag);
996		retval += print_category(LC_COLLATE, cflag, kflag);
997		retval += print_category(LC_MONETARY, cflag, kflag);
998		retval += print_category(LC_MESSAGES, cflag, kflag);
999	} else {
1000		if (cflag) {
1001			(void) printf("%s\n",
1002			    locale_name[category].name);
1003		}
1004
1005		for (i = 0; key[i].name != NULL; i++) {
1006			if (key[i].category == category) {
1007				retval += print_keyword(key[i].name, 0, kflag);
1008			}
1009		}
1010	}
1011	return (retval);
1012}
1013
1014/*
1015 * usage message for locale
1016 */
1017static void
1018usage(void)
1019{
1020	(void) fprintf(stderr, gettext(
1021	    "Usage: locale [-a|-m]\n"
1022	    "       locale [-ck] name ...\n"));
1023	exit(2);
1024}
1025
1026static void
1027prt_ctp(char *name)
1028{
1029	int		idx, i, mem;
1030	int		first = 1;
1031
1032	static const char	*reg_names[] = {
1033		"upper", "lower", "alpha", "digit", "space", "cntrl",
1034		"punct", "graph", "print", "xdigit", "blank", NULL
1035	};
1036	for (idx = 0; reg_names[idx] != NULL; idx++) {
1037		if (strcmp(name, reg_names[idx]) == 0) {
1038			break;
1039		}
1040	}
1041	if (reg_names[idx] == NULL) {
1042		return;
1043	}
1044
1045	for (i = 0; i < CSSIZE; i++) {
1046		mem = 0;
1047		switch (idx) {
1048		case 0:
1049			mem = isupper(i);
1050			break;
1051		case 1:
1052			mem = islower(i);
1053			break;
1054		case 2:
1055			mem = isalpha(i);
1056			break;
1057		case 3:
1058			mem = isdigit(i);
1059			break;
1060		case 4:
1061			mem = isspace(i);
1062			break;
1063		case 5:
1064			mem = iscntrl(i);
1065			break;
1066		case 6:
1067			mem = ispunct(i);
1068			break;
1069		case 7:
1070			mem = isgraph(i);
1071			break;
1072		case 8:
1073			mem = isprint(i);
1074			break;
1075		case 9:
1076			mem = isxdigit(i);
1077			break;
1078		case 10:
1079			mem = isblank(i);
1080			break;
1081		}
1082		if (mem) {
1083			if (!first) {
1084				(void) putchar(';');
1085			}
1086			first = 0;
1087			(void) printf("\"");
1088			outchar(i);
1089			(void) printf("\"");
1090		}
1091	}
1092}
1093
1094static void
1095prt_cnv(char *name)
1096{
1097	int		idx, i, q;
1098	int		first = 1;
1099
1100	static const char	*reg_names[] = {
1101		"toupper", "tolower", NULL
1102	};
1103	for (idx = 0; reg_names[idx] != NULL; idx++) {
1104		if (strcmp(name, reg_names[idx]) == 0) {
1105			break;
1106		}
1107	}
1108	if (reg_names[idx] == NULL) {
1109		return;
1110	}
1111
1112	for (i = 0; i < CSSIZE; i++) {
1113		switch (idx) {
1114		case 0:
1115			q = toupper(i);
1116			if (q == i) {
1117				continue;
1118			}
1119			if (!first) {
1120				(void) putchar(';');
1121			}
1122			first = 0;
1123			/* BEGIN CSTYLED */
1124			(void) printf("\"<'");
1125			/* END CSTYLED */
1126			outchar(i);
1127			(void) printf("','");
1128			outchar(q);
1129			(void) printf("'>\"");
1130			break;
1131		case 1:
1132			q = tolower(i);
1133			if (q == i) {
1134				continue;
1135			}
1136			if (!first) {
1137				(void) putchar(';');
1138			}
1139			first = 0;
1140			/* BEGIN CSTYLED */
1141			(void) printf("\"<'");
1142			/* END CSTYLED */
1143			outchar(i);
1144			(void) printf("','");
1145			outchar(q);
1146			(void) printf("'>\"");
1147			break;
1148		}
1149	}
1150}
1151
1152/*
1153 * prt_collel(): Stub for the collate class which does nothing.
1154 */
1155/* ARGSUSED */
1156static void
1157prt_collel(char *name)
1158{
1159}
1160
1161static char
1162get_escapechar(void)
1163{
1164	return ('\\');
1165}
1166
1167static char
1168get_commentchar(void)
1169{
1170	return ('#');
1171}
1172