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