view 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 source

/*
 * 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 ('#');
}