xref: /illumos-gate/usr/src/cmd/locale/locale.c (revision 7c478bd9)
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 
76 enum 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 
88 static int	print_locale_info(char *keyword, int cflag, int kflag);
89 static int	print_category(int category, int cflag, int kflag);
90 static int	print_keyword(char *name, int cflag, int kflag);
91 static void	usage(void);
92 static void	print_all_info(int);
93 static void	print_cur_locale(void);
94 static void	outstr(char *s);
95 static void	outchar(int);
96 static void	prt_ctp(char *);
97 static void	prt_cnv(char *);
98 static void	prt_collel(char *);
99 static char	get_escapechar(void);
100 static char	get_commentchar(void);
101 
102 static 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  */
110 struct yesno {
111 	char	*yes_expr;
112 	char	*no_expr;
113 	char	*yes_str;
114 	char	*no_str;
115 };
116 
117 struct 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 
135 struct 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 
144 static struct yesno *
145 getyesno(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 
164 static struct dtconv *
165 localedtconv(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 
229 static struct localedef *
230 localeldconv(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  */
256 static 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  */
275 static 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 
391 static char escapec;
392 
393 int
394 main(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  */
472 static void
473 print_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 
519 static int	num_of_loc = 0;
520 static int	num_of_entries = 0;
521 static char	**entries = NULL;
522 
523 static void
524 add_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 
555 static int
556 loccmp(const char **str1, const char **str2)
557 {
558 	return (strcmp(*str1, *str2));
559 }
560 
561 static void
562 show_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 
573 static void
574 check_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  */
604 static void
605 print_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  */
717 static int
718 print_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  */
740 static int
741 print_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  */
944 static void
945 outstr(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 
965 static void
966 outchar(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  */
986 static int
987 print_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  */
1017 static void
1018 usage(void)
1019 {
1020 	(void) fprintf(stderr, gettext(
1021 	    "Usage: locale [-a|-m]\n"
1022 	    "       locale [-ck] name ...\n"));
1023 	exit(2);
1024 }
1025 
1026 static void
1027 prt_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 
1094 static void
1095 prt_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 */
1156 static void
1157 prt_collel(char *name)
1158 {
1159 }
1160 
1161 static char
1162 get_escapechar(void)
1163 {
1164 	return ('\\');
1165 }
1166 
1167 static char
1168 get_commentchar(void)
1169 {
1170 	return ('#');
1171 }
1172