diff usr/src/cmd/locale/locale.c @ 0:c9caec207d52 b86

Initial porting based on b86
author Koji Uno <koji.uno@sun.com>
date Tue, 02 Jun 2009 18:56:50 +0900
parents
children 1a15d5aaf794
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/locale/locale.c	Tue Jun 02 18:56:50 2009 +0900
@@ -0,0 +1,1171 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * locale -- get current locale information
+ *
+ * Copyright 1991, 1993 by Mortice Kern Systems Inc.  All rights reserved.
+ *
+ */
+
+#pragma ident	"@(#)locale.c	1.13	05/06/08 SMI"
+
+/*
+ * locale: get locale-specific information
+ * usage:  locale [-a|-m]
+ *         locale [-ck] name ...
+ */
+
+/*
+ * New members added in the struct lconv by IEEE Std 1003.1-2001
+ * are always activated in the locale object.
+ * See <iso/locale_iso.h>.
+ */
+#define	_LCONV_C99
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <nl_types.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define	LC_LOCDEF	999	/* Random number! */
+
+#define	LOCALE_DIR		"/usr/lib/locale/"
+#define	CHARMAP_DIR		"/usr/lib/localedef/src/"
+#define	CHARMAP_NAME	"charmap.src"
+
+#define	GET_LOCALE	0
+#define	GET_CHARMAP	1
+#define	CSSIZE	128
+
+#ifndef isblank
+#define	isblank(c)	((__ctype + 1)[c] & _B)
+#endif
+
+enum types {
+	TYPE_STR,	/* char * */
+	TYPE_GROUP,	/* char *, for mon_grouping, and grouping */
+	TYPE_INT,	/* int */
+	TYPE_CHR,	/* char, printed as signed integer */
+	TYPE_PCHR,	/* char, printed as printable character */
+	TYPE_CTP,	/* ctype entry */
+	TYPE_CNVL,	/* convert to lower */
+	TYPE_CNVU,	/* convert to upper */
+	TYPE_COLLEL	/* print the multi-character collating elements */
+};
+
+static int	print_locale_info(char *keyword, int cflag, int kflag);
+static int	print_category(int category, int cflag, int kflag);
+static int	print_keyword(char *name, int cflag, int kflag);
+static void	usage(void);
+static void	print_all_info(int);
+static void	print_cur_locale(void);
+static void	outstr(char *s);
+static void	outchar(int);
+static void	prt_ctp(char *);
+static void	prt_cnv(char *);
+static void	prt_collel(char *);
+static char	get_escapechar(void);
+static char	get_commentchar(void);
+
+static char	*save_loc;
+
+/*
+ * yes/no is not in the localeconv structure for xpg style.
+ * We dummy up a new structure for purposes of the code below.
+ * If YESEXPR is available per XPG4, we use it.
+ * Otherwise, use YESSTR, the old method with less functionality from XPG3.
+ */
+struct yesno {
+	char	*yes_expr;
+	char	*no_expr;
+	char	*yes_str;
+	char	*no_str;
+};
+
+struct dtconv {
+	char	*date_time_format;
+	char	*date_format;
+	char	*time_format;
+	char	*time_format_ampm;
+	char	*am_string;
+	char	*pm_string;
+	char	*abbrev_day_names[7];
+	char	*day_names[7];
+	char	*abbrev_month_names[12];
+	char	*month_names[12];
+	char	*era;
+	char	*era_d_fmt;
+	char	*era_d_t_fmt;
+	char	*era_t_fmt;
+	char	*alt_digits;
+};
+
+struct localedef {
+	char	*charmap;
+	char	*code_set_name;
+	char	escape_char;
+	char	comment_char;
+	int		mb_cur_max;
+	int		mb_cur_min;
+};
+
+static struct yesno *
+getyesno(void)
+{
+	static struct yesno	yn;
+	static int	loaded = 0;
+
+	if (loaded) {
+		return (&yn);
+		/* NOTREACHED */
+	}
+
+	yn.yes_expr = strdup(nl_langinfo(YESEXPR));
+	yn.no_expr = strdup(nl_langinfo(NOEXPR));
+	yn.yes_str = strdup(nl_langinfo(YESSTR));
+	yn.no_str = strdup(nl_langinfo(NOSTR));
+
+	loaded = 1;
+	return (&yn);
+}
+
+static struct dtconv *
+localedtconv(void)
+{
+	static struct dtconv	_dtconv;
+	static int				loaded = 0;
+
+	if (loaded) {
+		return (&_dtconv);
+		/* NOTREACHED */
+	}
+
+	_dtconv.date_time_format = strdup(nl_langinfo(D_T_FMT));
+	_dtconv.date_format = strdup(nl_langinfo(D_FMT));
+	_dtconv.time_format = strdup(nl_langinfo(T_FMT));
+	_dtconv.time_format_ampm = strdup(nl_langinfo(T_FMT_AMPM));
+	_dtconv.am_string = strdup(nl_langinfo(AM_STR));
+	_dtconv.pm_string = strdup(nl_langinfo(PM_STR));
+	_dtconv.abbrev_day_names[0] = strdup(nl_langinfo(ABDAY_1));
+	_dtconv.abbrev_day_names[1] = strdup(nl_langinfo(ABDAY_2));
+	_dtconv.abbrev_day_names[2] = strdup(nl_langinfo(ABDAY_3));
+	_dtconv.abbrev_day_names[3] = strdup(nl_langinfo(ABDAY_4));
+	_dtconv.abbrev_day_names[4] = strdup(nl_langinfo(ABDAY_5));
+	_dtconv.abbrev_day_names[5] = strdup(nl_langinfo(ABDAY_6));
+	_dtconv.abbrev_day_names[6] = strdup(nl_langinfo(ABDAY_7));
+	_dtconv.day_names[0] = strdup(nl_langinfo(DAY_1));
+	_dtconv.day_names[1] = strdup(nl_langinfo(DAY_2));
+	_dtconv.day_names[2] = strdup(nl_langinfo(DAY_3));
+	_dtconv.day_names[3] = strdup(nl_langinfo(DAY_4));
+	_dtconv.day_names[4] = strdup(nl_langinfo(DAY_5));
+	_dtconv.day_names[5] = strdup(nl_langinfo(DAY_6));
+	_dtconv.day_names[6] = strdup(nl_langinfo(DAY_7));
+	_dtconv.abbrev_month_names[0] = strdup(nl_langinfo(ABMON_1));
+	_dtconv.abbrev_month_names[1] = strdup(nl_langinfo(ABMON_2));
+	_dtconv.abbrev_month_names[2] = strdup(nl_langinfo(ABMON_3));
+	_dtconv.abbrev_month_names[3] = strdup(nl_langinfo(ABMON_4));
+	_dtconv.abbrev_month_names[4] = strdup(nl_langinfo(ABMON_5));
+	_dtconv.abbrev_month_names[5] = strdup(nl_langinfo(ABMON_6));
+	_dtconv.abbrev_month_names[6] = strdup(nl_langinfo(ABMON_7));
+	_dtconv.abbrev_month_names[7] = strdup(nl_langinfo(ABMON_8));
+	_dtconv.abbrev_month_names[8] = strdup(nl_langinfo(ABMON_9));
+	_dtconv.abbrev_month_names[9] = strdup(nl_langinfo(ABMON_10));
+	_dtconv.abbrev_month_names[10] = strdup(nl_langinfo(ABMON_11));
+	_dtconv.abbrev_month_names[11] = strdup(nl_langinfo(ABMON_12));
+	_dtconv.month_names[0] = strdup(nl_langinfo(MON_1));
+	_dtconv.month_names[1] = strdup(nl_langinfo(MON_2));
+	_dtconv.month_names[2] = strdup(nl_langinfo(MON_3));
+	_dtconv.month_names[3] = strdup(nl_langinfo(MON_4));
+	_dtconv.month_names[4] = strdup(nl_langinfo(MON_5));
+	_dtconv.month_names[5] = strdup(nl_langinfo(MON_6));
+	_dtconv.month_names[6] = strdup(nl_langinfo(MON_7));
+	_dtconv.month_names[7] = strdup(nl_langinfo(MON_8));
+	_dtconv.month_names[8] = strdup(nl_langinfo(MON_9));
+	_dtconv.month_names[9] = strdup(nl_langinfo(MON_10));
+	_dtconv.month_names[10] = strdup(nl_langinfo(MON_11));
+	_dtconv.month_names[11] = strdup(nl_langinfo(MON_12));
+	_dtconv.era = strdup(nl_langinfo(ERA));
+	_dtconv.era_d_fmt = strdup(nl_langinfo(ERA_D_FMT));
+	_dtconv.era_d_t_fmt = strdup(nl_langinfo(ERA_D_T_FMT));
+	_dtconv.era_t_fmt = strdup(nl_langinfo(ERA_T_FMT));
+	_dtconv.alt_digits = strdup(nl_langinfo(ALT_DIGITS));
+
+	loaded = 1;
+	return (&_dtconv);
+}
+
+static struct localedef *
+localeldconv(void)
+{
+	static struct localedef	_locdef;
+	static int	loaded = 0;
+
+	if (loaded) {
+		return (&_locdef);
+		/* NOTREACHED */
+	}
+
+	_locdef.charmap = strdup(nl_langinfo(CODESET));
+	_locdef.code_set_name = strdup(nl_langinfo(CODESET));
+	_locdef.mb_cur_max = MB_CUR_MAX;
+	_locdef.mb_cur_min = 1;
+	_locdef.escape_char = get_escapechar();
+	_locdef.comment_char = get_commentchar();
+
+	loaded = 1;
+	return (&_locdef);
+}
+
+/*
+ * The locale_name array also defines a canonical ordering for the categories.
+ * The function tocanon() translates the LC_* manifests to their canonical
+ * values.
+ */
+static struct locale_name {
+	char	*name;
+	int 	category;
+} locale_name[] = {
+	{"LC_CTYPE",	LC_CTYPE},
+	{"LC_NUMERIC",	LC_NUMERIC},
+	{"LC_TIME",		LC_TIME},
+	{"LC_COLLATE",	LC_COLLATE},
+	{"LC_MONETARY",	LC_MONETARY},
+	{"LC_MESSAGES",	LC_MESSAGES},
+	{"LC_ALL",		LC_ALL},
+	NULL
+};
+
+/*
+ * The structure key contains all keywords string name,
+ * symbolic name, category, and type (STR INT ...)
+ * the type will decide the way the value of the item be printed out
+ */
+static struct key {
+	char		*name;
+	void		*(*structure)(void);
+	int			offset;
+	int			count;
+	int			category;
+	enum types	type;
+} key[] = {
+
+#define	SPECIAL		0, 0, 0,
+	{"lower",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"upper",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"alpha",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"digit",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"space",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"cntrl",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"punct",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"graph",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"print",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"xdigit",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+	{"blank",	SPECIAL	LC_CTYPE,	TYPE_CTP},
+
+	{"tolower",	SPECIAL	LC_CTYPE,	TYPE_CNVL},
+	{"toupper",	SPECIAL	LC_CTYPE,	TYPE_CNVU},
+
+	{"collating-element",	0, 0, 0, LC_COLLATE,	TYPE_COLLEL},
+	{"character-collation",	0, 1, 0, LC_COLLATE,	TYPE_COLLEL},
+
+#define	dt(member, count) \
+		(void *(*)(void))localedtconv, \
+		offsetof(struct dtconv, member), \
+		count, \
+		LC_TIME, \
+		TYPE_STR
+	{"d_t_fmt",	dt(date_time_format, 1)},
+	{"d_fmt",	dt(date_format, 1)},
+	{"t_fmt",	dt(time_format, 1)},
+	{"t_fmt_ampm",	dt(time_format_ampm, 1)},
+	{"am_pm",	dt(am_string, 2)},
+	{"day",		dt(day_names, 7)},
+	{"abday",	dt(abbrev_day_names, 7)},
+	{"mon",		dt(month_names, 12)},
+	{"abmon",	dt(abbrev_month_names, 12)},
+	{"era",		dt(era, 1)},
+	{"era_d_fmt",	dt(era_d_fmt, 1)},
+	{"era_d_t_fmt",	dt(era_d_t_fmt, 1)},
+	{"era_t_fmt",	dt(era_t_fmt, 1)},
+	{"alt_digits",	dt(alt_digits, 1)},
+
+#undef dt
+
+#define	lc(member, locale, type) \
+		(void *(*)(void))localeconv, \
+		offsetof(struct lconv, member), \
+		1, \
+		locale, \
+		type
+{"decimal_point",	lc(decimal_point, 	LC_NUMERIC, TYPE_STR) },
+{"thousands_sep",	lc(thousands_sep, 	LC_NUMERIC, TYPE_STR) },
+{"grouping",		lc(grouping,		LC_NUMERIC, TYPE_GROUP)},
+{"int_curr_symbol",	lc(int_curr_symbol,	LC_MONETARY, TYPE_STR)},
+{"currency_symbol",	lc(currency_symbol,	LC_MONETARY, TYPE_STR)},
+{"mon_decimal_point",	lc(mon_decimal_point,	LC_MONETARY, TYPE_STR)},
+{"mon_thousands_sep",	lc(mon_thousands_sep,	LC_MONETARY, TYPE_STR)},
+{"mon_grouping",	lc(mon_grouping,	LC_MONETARY, TYPE_GROUP)},
+{"positive_sign",	lc(positive_sign,	LC_MONETARY, TYPE_STR)},
+{"negative_sign",	lc(negative_sign,	LC_MONETARY, TYPE_STR)},
+
+{"int_frac_digits",	lc(int_frac_digits,	LC_MONETARY, TYPE_CHR)},
+{"frac_digits",		lc(frac_digits,		LC_MONETARY, TYPE_CHR)},
+{"p_cs_precedes",	lc(p_cs_precedes,	LC_MONETARY, TYPE_CHR)},
+{"p_sep_by_space",	lc(p_sep_by_space,	LC_MONETARY, TYPE_CHR)},
+{"n_cs_precedes",	lc(n_cs_precedes,	LC_MONETARY, TYPE_CHR)},
+{"n_sep_by_space",	lc(n_sep_by_space,	LC_MONETARY, TYPE_CHR)},
+{"p_sign_posn",		lc(p_sign_posn,		LC_MONETARY, TYPE_CHR)},
+{"n_sign_posn",		lc(n_sign_posn,		LC_MONETARY, TYPE_CHR)},
+{"int_p_cs_precedes",	lc(int_p_cs_precedes,	LC_MONETARY, TYPE_CHR)},
+{"int_p_sep_by_space",	lc(int_p_sep_by_space,	LC_MONETARY, TYPE_CHR)},
+{"int_n_cs_precedes",	lc(int_n_cs_precedes,	LC_MONETARY, TYPE_CHR)},
+{"int_n_sep_by_space",	lc(int_n_sep_by_space,	LC_MONETARY, TYPE_CHR)},
+{"int_p_sign_posn",	lc(int_p_sign_posn,	LC_MONETARY, TYPE_CHR)},
+{"int_n_sign_posn",	lc(int_n_sign_posn,	LC_MONETARY, TYPE_CHR)},
+
+#undef lc
+#define	lc(member) \
+		(void *(*)(void))getyesno, \
+		offsetof(struct yesno, member), \
+		1, \
+		LC_MESSAGES, \
+		TYPE_STR
+	{"yesexpr",		lc(yes_expr)},
+	{"noexpr",		lc(no_expr)},
+	{"yesstr",		lc(yes_str)},
+	{"nostr",		lc(no_str)},
+#undef lc
+
+	/*
+	 * Following keywords have no official method of obtaining them
+	 */
+#define	ld(member, locale, type) \
+		(void *(*)(void))localeldconv, \
+		offsetof(struct localedef, member), \
+		1, \
+		locale, \
+		type
+	{"charmap",		ld(charmap,		LC_LOCDEF, TYPE_STR)},
+	{"code_set_name",	ld(code_set_name,	LC_LOCDEF, TYPE_STR)},
+	{"escape_char",		ld(escape_char,		LC_LOCDEF, TYPE_PCHR)},
+	{"comment_char",	ld(comment_char,	LC_LOCDEF, TYPE_PCHR)},
+	{"mb_cur_max",		ld(mb_cur_max,		LC_LOCDEF, TYPE_INT)},
+	{"mb_cur_min",		ld(mb_cur_min,		LC_LOCDEF, TYPE_INT)},
+#undef ld
+
+	{NULL,			NULL,			0, 0}
+};
+
+static char escapec;
+
+int
+main(int argc, char **argv)
+{
+	int		c;
+	int		retval = 0;
+	int		cflag, kflag, aflag, mflag;
+
+	(void) setlocale(LC_ALL, "");
+#if !defined(TEXT_DOMAIN)
+#define	TEXT_DOMAIN	"SYS_TEST"
+#endif
+	(void) textdomain(TEXT_DOMAIN);
+
+	cflag = kflag = aflag = mflag = 0;
+
+	while ((c = getopt(argc, argv, "amck")) != EOF) {
+		switch (c) {
+		case 'a':
+			aflag = 1;
+			break;
+		case 'm':
+			mflag = 1;
+			break;
+		case 'c':
+			cflag = 1;
+			break;
+		case 'k':
+			kflag = 1;
+			break;
+		default:
+			usage();
+			/* NOTREACHED */
+			break;
+		}
+	}
+
+	/* -a OR -m OR (-c and/or -k) */
+	if ((aflag && mflag) || ((aflag || mflag) && (cflag || kflag))) {
+		usage();
+		/* NOTREACHED */
+	}
+
+	escapec = get_escapechar();
+
+	if (aflag) {
+		print_all_info(GET_LOCALE);
+		/* NOTREACHED */
+	}
+
+	if (mflag) {
+		print_all_info(GET_CHARMAP);
+		/* NOTREACHED */
+	}
+
+	if (optind == argc && !cflag && !kflag) {
+		print_cur_locale();
+		/* NOTREACHED */
+	}
+	if (optind == argc) {
+		usage();
+		/* NOTREACHED */
+	}
+
+	for (; optind < argc; optind++) {
+		retval += print_locale_info(argv[optind], cflag, kflag);
+	}
+	return (retval);
+}
+
+/*
+ * No options or operands.
+ * Print out the current locale names from the environment, or implied.
+ * Variables directly set in the environment are printed as-is, those
+ * implied are printed in quotes.
+ * The strings are printed ``appropriately quoted for possible later re-entry
+ * to the shell''.  We use the routine outstr to do this -- however we
+ * want the shell escape character, the backslash, not the locale escape
+ * character, so we quietly save and restore the locale escape character.
+ */
+static void
+print_cur_locale(void)
+{
+	char	*lc_allp;
+	char	*env, *eff;
+	int		i;
+
+	if ((env = getenv("LANG")) != NULL) {
+		(void) printf("LANG=%s\n", env);
+	} else {
+		(void) printf("LANG=\n");
+	}
+
+	lc_allp = getenv("LC_ALL");
+
+	for (i = 0; i < LC_ALL; i++) {
+		(void) printf("%s=", locale_name[i].name);
+		eff = setlocale(i, NULL);
+		if (eff == NULL) {
+			eff = "";
+		}
+		env = getenv(locale_name[i].name);
+
+		if (env == NULL) {
+			(void) putchar('"');
+			outstr(eff);
+			(void) putchar('"');
+		} else {
+			if (strcmp(env, eff) != 0) {
+				(void) putchar('"');
+				outstr(eff);
+				(void) putchar('"');
+			} else {
+				outstr(eff);
+			}
+		}
+		(void) putchar('\n');
+	}
+
+	(void) printf("LC_ALL=");
+	if (lc_allp != NULL) {
+		outstr(lc_allp);
+	}
+	(void) putchar('\n');
+	exit(0);
+}
+
+static int	num_of_loc = 0;
+static int	num_of_entries = 0;
+static char	**entries = NULL;
+
+static void
+add_loc_entry(char *loc)
+{
+#define	_INC_NUM	10
+	char	*s;
+
+	if (num_of_loc >= num_of_entries) {
+		char	**tmp;
+		num_of_entries += _INC_NUM;
+		tmp = realloc(entries, sizeof (char *) * num_of_entries);
+		if (tmp == NULL) {
+			/* restoring original locale */
+			(void) setlocale(LC_ALL, save_loc);
+			(void) fprintf(stderr,
+			    gettext("locale: cannot allocate buffer"));
+			exit(1);
+		}
+		entries = tmp;
+	}
+	s = strdup(loc);
+	if (s == NULL) {
+		/* restoring original locale */
+		(void) setlocale(LC_ALL, save_loc);
+		(void) fprintf(stderr,
+		    gettext("locale: cannot allocate buffer"));
+		exit(1);
+	}
+	entries[num_of_loc] = s;
+
+	num_of_loc++;
+}
+
+static int
+loccmp(const char **str1, const char **str2)
+{
+	return (strcmp(*str1, *str2));
+}
+
+static void
+show_loc_entry(void)
+{
+	int	i;
+
+	qsort(entries, num_of_loc, sizeof (char *),
+	    (int (*)(const void *, const void *))loccmp);
+	for (i = 0; i < num_of_loc; i++) {
+		(void) printf("%s\n", entries[i]);
+	}
+}
+
+static void
+check_loc(char *loc)
+{
+	int	cat;
+
+	/* first, try LC_ALL */
+	if (setlocale(LC_ALL, loc) != NULL) {
+		/* succeeded */
+		add_loc_entry(loc);
+		return;
+	}
+
+	/*
+	 * LC_ALL failed.
+	 * try each category.
+	 */
+	for (cat = 0; cat <= _LastCategory; cat++) {
+		if (setlocale(cat, loc) != NULL) {
+			/* succeeded */
+			add_loc_entry(loc);
+			return;
+		}
+	}
+
+	/* loc is not a valid locale */
+}
+
+/*
+ * print_all_info(): Print out all the locales and
+ *                   charmaps supported by the system
+ */
+static void
+print_all_info(int flag)
+{
+	struct dirent	*direntp;
+	DIR				*dirp;
+	char			*filename;		/* filename[PATH_MAX] */
+	char			*p;
+
+	if ((filename = malloc(PATH_MAX)) == NULL) {
+		(void) fprintf(stderr,
+		    gettext("locale: cannot allocate buffer"));
+		exit(1);
+	}
+
+	(void) memset(filename, 0, PATH_MAX);
+
+	if (flag == GET_LOCALE) {
+		/* save the current locale */
+		save_loc = setlocale(LC_ALL, NULL);
+
+		(void) strcpy(filename, LOCALE_DIR);
+		add_loc_entry("POSIX");
+	} else {						/* CHARMAP */
+		(void) strcpy(filename, CHARMAP_DIR);
+	}
+
+	if ((dirp = opendir(filename)) == NULL) {
+		if (flag == GET_LOCALE)
+			exit(0);
+		else {					/* CHARMAP */
+			(void) fprintf(stderr, gettext(
+			    "locale: charmap information not available.\n"));
+			exit(2);
+		}
+	}
+
+	p = filename + strlen(filename);
+	while ((direntp = readdir(dirp)) != NULL) {
+		struct stat stbuf;
+
+		(void) strcpy(p, direntp->d_name);
+		if (stat(filename, &stbuf) < 0) {
+			continue;
+		}
+
+		if (flag == GET_LOCALE) {
+			if (S_ISDIR(stbuf.st_mode) &&
+			    (direntp->d_name[0] != '.') &&
+			    /* "POSIX" has already been printed out */
+			    strcmp(direntp->d_name, "POSIX") != 0) {
+				check_loc(direntp->d_name);
+			}
+		} else {			/* CHARMAP */
+			if (S_ISDIR(stbuf.st_mode) &&
+			    direntp->d_name[0] != '.') {
+				struct dirent	*direntc;
+				DIR		*dirc;
+				char		*charmap;
+				char		*c;
+
+				if ((charmap = malloc(PATH_MAX)) == NULL) {
+					(void) fprintf(stderr,
+				    gettext("locale: cannot allocate buffer"));
+					exit(1);
+				}
+
+				(void) memset(charmap, 0, PATH_MAX);
+
+				(void) strcpy(charmap, filename);
+
+				if ((dirc = opendir(charmap)) == NULL) {
+					exit(0);
+				}
+
+				c = charmap + strlen(charmap);
+				*c++ = '/';
+				while ((direntc = readdir(dirc)) != NULL) {
+					struct stat stbuf;
+
+					(void) strcpy(c, direntc->d_name);
+					if (stat(charmap, &stbuf) < 0) {
+						continue;
+					}
+
+					if (S_ISREG(stbuf.st_mode) &&
+						(strcmp(direntc->d_name,
+							CHARMAP_NAME) == 0) &&
+						(direntc->d_name[0] != '.')) {
+						(void) printf("%s/%s\n",
+							p, direntc->d_name);
+					}
+				}
+				(void) closedir(dirc);
+				free(charmap);
+			}
+		}
+	}
+	if (flag == GET_LOCALE) {
+		/* restore the saved loc */
+		(void) setlocale(LC_ALL, save_loc);
+		show_loc_entry();
+	}
+	(void) closedir(dirp);
+	free(filename);
+	exit(0);
+}
+
+/*
+ * Print out the keyword value or category info.
+ * Call print_category() to print the entire locale category, if the name
+ * given is recognized as a category.
+ * Otherwise, assume that it is a keyword, and call print_keyword().
+ */
+static int
+print_locale_info(char *name, int cflag, int kflag)
+{
+	int i;
+
+	for (i = 0; locale_name[i].name != NULL; i++) {
+		if (strcmp(locale_name[i].name, name) == 0) {
+			/*
+			 * name is a category name
+			 * print out all keywords in this category
+			 */
+			return (print_category(locale_name[i].category,
+				cflag, kflag));
+		}
+	}
+
+	/* The name is a keyword name */
+	return (print_keyword(name, cflag, kflag));
+}
+
+/*
+ * Print out the value of the keyword
+ */
+static int
+print_keyword(char *name, int cflag, int kflag)
+{
+	int		i, j;
+	int		first_flag = 1;
+	int		found = 0;
+
+	for (i = 0; key[i].name != NULL; i++) {
+		if (strcmp(key[i].name, name) != 0) {
+			continue;
+		}
+
+		found = 1;
+		if (first_flag && cflag && key[i].category != LC_LOCDEF) {
+			/* print out this category's name */
+			(void) printf("%s\n",
+				locale_name[key[i].category].name);
+		}
+		if (kflag) {
+			(void) printf("%s=", name);
+		}
+		switch (key[i].type) {
+
+		/*
+		 * The grouping fields are a set of bytes, each of which
+		 * is the numeric value of the next group size, terminated
+		 * by a \0, or by CHAR_MAX
+		 */
+		case TYPE_GROUP:
+			{
+				void	*s;
+				char	*q;
+				int		first = 1;
+
+				s = (*key[i].structure)();
+				/* LINTED */
+				q = *(char **)((char *)s + key[i].offset);
+				if (*q == '\0') {
+					(void) printf("-1");
+					break;
+				}
+				while (*q != '\0' && *q != CHAR_MAX) {
+					if (!first) {
+						(void) putchar(';');
+					}
+					first = 0;
+					(void) printf("%u",
+					    *(unsigned char *)q++);
+				}
+				/* CHAR_MAX: no further grouping performed. */
+				if (!first) {
+					(void) putchar(';');
+				}
+				if (*q == CHAR_MAX) {
+					(void) printf("-1");
+				} else {
+					(void) putchar('0');
+				}
+			}
+			break;
+
+		/*
+		 * Entries like decimal_point states ``the decimal-point
+		 * character...'' not string.  However, it is a char *.
+		 * This assumes single, narrow, character.
+		 * Should it permit multibyte characters?
+		 * Should it permit a whole string, in that case?
+		 */
+		case TYPE_STR:
+			{
+				void	*s;
+				char	**q;
+
+				s = (*key[i].structure)();
+				/* LINTED */
+				q = (char **)((char *)s + key[i].offset);
+				for (j = 0; j < key[i].count; j++) {
+					if (j != 0) {
+						(void) printf(";");
+					}
+					if (kflag) {
+						(void) printf("\"");
+						outstr(q[j]);
+						(void) printf("\"");
+					} else {
+						(void) printf("%s", q[j]);
+					}
+				}
+			}
+			break;
+
+		case TYPE_INT:
+			{
+				void	*s;
+				int		*q;
+
+				s = (*key[i].structure)();
+				/* LINTED */
+				q = (int *)((char *)s + key[i].offset);
+				(void) printf("%d", *q);
+			}
+			break;
+
+		/*
+		 * TYPE_CHR: Single byte integer.
+		 */
+		case TYPE_CHR:
+			{
+				void	*s;
+				char	*q;
+
+				s = (*key[i].structure)();
+				q = (char *)((char *)s + key[i].offset);
+				if (*q == CHAR_MAX) {
+					(void) printf("-1");
+				} else {
+					(void) printf("%u",
+					    *(unsigned char *)q);
+				}
+			}
+			break;
+
+		/*
+		 * TYPE_PCHR: Single byte, printed as a character if printable
+		 */
+		case TYPE_PCHR:
+			{
+				void	*s;
+				char	*q;
+
+				s = (*key[i].structure)();
+				q = (char *)((char *)s + key[i].offset);
+				if (isprint(*(unsigned char *)q)) {
+					if (kflag) {
+						(void) printf("\"");
+						if ((*q == '\\') ||
+							(*q == ';') ||
+							(*q == '"')) {
+							(void) putchar(escapec);
+							(void) printf("%c",
+							*(unsigned char *)q);
+						} else {
+							(void) printf("%c",
+							*(unsigned char *)q);
+						}
+						(void) printf("\"");
+					} else {
+						(void) printf("%c",
+						    *(unsigned char *)q);
+					}
+				} else if (*q == (char)-1) {
+					/* In case no signed chars */
+					(void) printf("-1");
+				} else {
+					(void) printf("%u",
+					    *(unsigned char *)q);
+				}
+			}
+			break;
+
+		case TYPE_CTP:
+			{
+				prt_ctp(key[i].name);
+			}
+			break;
+
+		case TYPE_CNVU:
+			{
+				prt_cnv(key[i].name);
+			}
+			break;
+
+		case TYPE_CNVL:
+			{
+				prt_cnv(key[i].name);
+			}
+			break;
+
+		case TYPE_COLLEL:
+			{
+				prt_collel(key[i].name);
+			}
+			break;
+		}
+	}
+	if (found) {
+		(void) printf("\n");
+		return (0);
+	} else {
+		(void) fprintf(stderr,
+		    gettext("Unknown keyword name '%s'.\n"), name);
+		return (1);
+	}
+}
+
+/*
+ * Strings being outputed have to use an unambiguous format --  escape
+ * any potentially bad output characters.
+ * The standard says that any control character shall be preceeded by
+ * the escape character.  But it doesn't say that you can format that
+ * character at all.
+ * Question: If the multibyte character contains a quoting character,
+ * should that *byte* be escaped?
+ */
+static void
+outstr(char *s)
+{
+	wchar_t	ws;
+	int		c;
+	size_t	mbcurmax = MB_CUR_MAX;
+
+	while (*s != '\0') {
+		c = mbtowc(&ws, s, mbcurmax);
+		if (c < 0) {
+			s++;
+		} else if (c == 1) {
+			outchar(*s++);
+		} else {
+			for (; c > 0; c--) {
+				(void) putchar(*s++);
+			}
+		}
+	}
+}
+
+static void
+outchar(int c)
+{
+	unsigned char	uc;
+
+	uc = (unsigned char) c;
+
+	if ((uc == '\\') || (uc == ';') || (uc == '"')) {
+		(void) putchar(escapec);
+		(void) putchar(uc);
+	} else if (iscntrl(uc)) {
+		(void) printf("%cx%02x", escapec, uc);
+	} else {
+		(void) putchar(uc);
+	}
+}
+
+/*
+ * print_category(): Print out all the keyword's value
+ *                  in the given category
+ */
+static int
+print_category(int category, int cflag, int kflag)
+{
+	int		i;
+	int		retval = 0;
+
+	if (category == LC_ALL) {
+		retval += print_category(LC_CTYPE, cflag, kflag);
+		retval += print_category(LC_NUMERIC, cflag, kflag);
+		retval += print_category(LC_TIME, cflag, kflag);
+		retval += print_category(LC_COLLATE, cflag, kflag);
+		retval += print_category(LC_MONETARY, cflag, kflag);
+		retval += print_category(LC_MESSAGES, cflag, kflag);
+	} else {
+		if (cflag) {
+			(void) printf("%s\n",
+			    locale_name[category].name);
+		}
+
+		for (i = 0; key[i].name != NULL; i++) {
+			if (key[i].category == category) {
+				retval += print_keyword(key[i].name, 0, kflag);
+			}
+		}
+	}
+	return (retval);
+}
+
+/*
+ * usage message for locale
+ */
+static void
+usage(void)
+{
+	(void) fprintf(stderr, gettext(
+	    "Usage: locale [-a|-m]\n"
+	    "       locale [-ck] name ...\n"));
+	exit(2);
+}
+
+static void
+prt_ctp(char *name)
+{
+	int		idx, i, mem;
+	int		first = 1;
+
+	static const char	*reg_names[] = {
+		"upper", "lower", "alpha", "digit", "space", "cntrl",
+		"punct", "graph", "print", "xdigit", "blank", NULL
+	};
+	for (idx = 0; reg_names[idx] != NULL; idx++) {
+		if (strcmp(name, reg_names[idx]) == 0) {
+			break;
+		}
+	}
+	if (reg_names[idx] == NULL) {
+		return;
+	}
+
+	for (i = 0; i < CSSIZE; i++) {
+		mem = 0;
+		switch (idx) {
+		case 0:
+			mem = isupper(i);
+			break;
+		case 1:
+			mem = islower(i);
+			break;
+		case 2:
+			mem = isalpha(i);
+			break;
+		case 3:
+			mem = isdigit(i);
+			break;
+		case 4:
+			mem = isspace(i);
+			break;
+		case 5:
+			mem = iscntrl(i);
+			break;
+		case 6:
+			mem = ispunct(i);
+			break;
+		case 7:
+			mem = isgraph(i);
+			break;
+		case 8:
+			mem = isprint(i);
+			break;
+		case 9:
+			mem = isxdigit(i);
+			break;
+		case 10:
+			mem = isblank(i);
+			break;
+		}
+		if (mem) {
+			if (!first) {
+				(void) putchar(';');
+			}
+			first = 0;
+			(void) printf("\"");
+			outchar(i);
+			(void) printf("\"");
+		}
+	}
+}
+
+static void
+prt_cnv(char *name)
+{
+	int		idx, i, q;
+	int		first = 1;
+
+	static const char	*reg_names[] = {
+		"toupper", "tolower", NULL
+	};
+	for (idx = 0; reg_names[idx] != NULL; idx++) {
+		if (strcmp(name, reg_names[idx]) == 0) {
+			break;
+		}
+	}
+	if (reg_names[idx] == NULL) {
+		return;
+	}
+
+	for (i = 0; i < CSSIZE; i++) {
+		switch (idx) {
+		case 0:
+			q = toupper(i);
+			if (q == i) {
+				continue;
+			}
+			if (!first) {
+				(void) putchar(';');
+			}
+			first = 0;
+			/* BEGIN CSTYLED */
+			(void) printf("\"<'");
+			/* END CSTYLED */
+			outchar(i);
+			(void) printf("','");
+			outchar(q);
+			(void) printf("'>\"");
+			break;
+		case 1:
+			q = tolower(i);
+			if (q == i) {
+				continue;
+			}
+			if (!first) {
+				(void) putchar(';');
+			}
+			first = 0;
+			/* BEGIN CSTYLED */
+			(void) printf("\"<'");
+			/* END CSTYLED */
+			outchar(i);
+			(void) printf("','");
+			outchar(q);
+			(void) printf("'>\"");
+			break;
+		}
+	}
+}
+
+/*
+ * prt_collel(): Stub for the collate class which does nothing.
+ */
+/* ARGSUSED */
+static void
+prt_collel(char *name)
+{
+}
+
+static char
+get_escapechar(void)
+{
+	return ('\\');
+}
+
+static char
+get_commentchar(void)
+{
+	return ('#');
+}