root/usr/src/cmd/locale/locale.c
/*
 * 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.
 *
 */

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