]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/locale/locale.c
Fix nmount invalid pointer dereference
[FreeBSD/FreeBSD.git] / usr.bin / locale / locale.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  * $FreeBSD$
29  */
30
31 /*
32  * XXX: implement missing era_* (LC_TIME) keywords (require libc &
33  *      nl_langinfo(3) extensions)
34  *
35  * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
36  *      localedef(1) implementation).  Currently it's handled via
37  *      nl_langinfo(CODESET).
38  */
39
40 #include <sys/param.h>
41 #include <sys/types.h>
42
43 #include <dirent.h>
44 #include <err.h>
45 #include <limits.h>
46 #include <locale.h>
47 #include <langinfo.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <stringlist.h>
52 #include <unistd.h>
53 #include "setlocale.h"
54
55 /* Local prototypes */
56 char    *format_grouping(const char *);
57 void    init_locales_list(void);
58 void    list_charmaps(void);
59 void    list_locales(void);
60 const char *lookup_localecat(int);
61 char    *kwval_lconv(int);
62 int     kwval_lookup(const char *, char **, int *, int *);
63 void    showdetails(const char *);
64 void    showkeywordslist(char *substring);
65 void    showlocale(void);
66 void    usage(void);
67
68 /* Global variables */
69 static StringList *locales = NULL;
70
71 static int      all_locales = 0;
72 static int      all_charmaps = 0;
73 static int      prt_categories = 0;
74 static int      prt_keywords = 0;
75
76 static const struct _lcinfo {
77         const char      *name;
78         int             id;
79 } lcinfo [] = {
80         { "LC_CTYPE",           LC_CTYPE },
81         { "LC_COLLATE",         LC_COLLATE },
82         { "LC_TIME",            LC_TIME },
83         { "LC_NUMERIC",         LC_NUMERIC },
84         { "LC_MONETARY",        LC_MONETARY },
85         { "LC_MESSAGES",        LC_MESSAGES }
86 };
87 #define NLCINFO nitems(lcinfo)
88
89 /* ids for values not referenced by nl_langinfo() */
90 #define KW_ZERO                 10000
91 #define KW_GROUPING             (KW_ZERO+1)
92 #define KW_INT_CURR_SYMBOL      (KW_ZERO+2)
93 #define KW_CURRENCY_SYMBOL      (KW_ZERO+3)
94 #define KW_MON_DECIMAL_POINT    (KW_ZERO+4)
95 #define KW_MON_THOUSANDS_SEP    (KW_ZERO+5)
96 #define KW_MON_GROUPING         (KW_ZERO+6)
97 #define KW_POSITIVE_SIGN        (KW_ZERO+7)
98 #define KW_NEGATIVE_SIGN        (KW_ZERO+8)
99 #define KW_INT_FRAC_DIGITS      (KW_ZERO+9)
100 #define KW_FRAC_DIGITS          (KW_ZERO+10)
101 #define KW_P_CS_PRECEDES        (KW_ZERO+11)
102 #define KW_P_SEP_BY_SPACE       (KW_ZERO+12)
103 #define KW_N_CS_PRECEDES        (KW_ZERO+13)
104 #define KW_N_SEP_BY_SPACE       (KW_ZERO+14)
105 #define KW_P_SIGN_POSN          (KW_ZERO+15)
106 #define KW_N_SIGN_POSN          (KW_ZERO+16)
107 #define KW_INT_P_CS_PRECEDES    (KW_ZERO+17)
108 #define KW_INT_P_SEP_BY_SPACE   (KW_ZERO+18)
109 #define KW_INT_N_CS_PRECEDES    (KW_ZERO+19)
110 #define KW_INT_N_SEP_BY_SPACE   (KW_ZERO+20)
111 #define KW_INT_P_SIGN_POSN      (KW_ZERO+21)
112 #define KW_INT_N_SIGN_POSN      (KW_ZERO+22)
113
114 static const struct _kwinfo {
115         const char      *name;
116         int             isstr;          /* true - string, false - number */
117         int             catid;          /* LC_* */
118         int             value_ref;
119         const char      *comment;
120 } kwinfo [] = {
121         { "charmap",            1, LC_CTYPE,    CODESET, "" },  /* hack */
122
123         { "decimal_point",      1, LC_NUMERIC,  RADIXCHAR, "" },
124         { "thousands_sep",      1, LC_NUMERIC,  THOUSEP, "" },
125         { "grouping",           1, LC_NUMERIC,  KW_GROUPING, "" },
126         { "radixchar",          1, LC_NUMERIC,  RADIXCHAR,
127           "Same as decimal_point (FreeBSD only)" },             /* compat */
128         { "thousep",            1, LC_NUMERIC,  THOUSEP,
129           "Same as thousands_sep (FreeBSD only)" },             /* compat */
130
131         { "int_curr_symbol",    1, LC_MONETARY, KW_INT_CURR_SYMBOL, "" },
132         { "currency_symbol",    1, LC_MONETARY, KW_CURRENCY_SYMBOL, "" },
133         { "mon_decimal_point",  1, LC_MONETARY, KW_MON_DECIMAL_POINT, "" },
134         { "mon_thousands_sep",  1, LC_MONETARY, KW_MON_THOUSANDS_SEP, "" },
135         { "mon_grouping",       1, LC_MONETARY, KW_MON_GROUPING, "" },
136         { "positive_sign",      1, LC_MONETARY, KW_POSITIVE_SIGN, "" },
137         { "negative_sign",      1, LC_MONETARY, KW_NEGATIVE_SIGN, "" },
138
139         { "int_frac_digits",    0, LC_MONETARY, KW_INT_FRAC_DIGITS, "" },
140         { "frac_digits",        0, LC_MONETARY, KW_FRAC_DIGITS, "" },
141         { "p_cs_precedes",      0, LC_MONETARY, KW_P_CS_PRECEDES, "" },
142         { "p_sep_by_space",     0, LC_MONETARY, KW_P_SEP_BY_SPACE, "" },
143         { "n_cs_precedes",      0, LC_MONETARY, KW_N_CS_PRECEDES, "" },
144         { "n_sep_by_space",     0, LC_MONETARY, KW_N_SEP_BY_SPACE, "" },
145         { "p_sign_posn",        0, LC_MONETARY, KW_P_SIGN_POSN, "" },
146         { "n_sign_posn",        0, LC_MONETARY, KW_N_SIGN_POSN, "" },
147         { "int_p_cs_precedes",  0, LC_MONETARY, KW_INT_P_CS_PRECEDES, "" },
148         { "int_p_sep_by_space", 0, LC_MONETARY, KW_INT_P_SEP_BY_SPACE, "" },
149         { "int_n_cs_precedes",  0, LC_MONETARY, KW_INT_N_CS_PRECEDES, "" },
150         { "int_n_sep_by_space", 0, LC_MONETARY, KW_INT_N_SEP_BY_SPACE, "" },
151         { "int_p_sign_posn",    0, LC_MONETARY, KW_INT_P_SIGN_POSN, "" },
152         { "int_n_sign_posn",    0, LC_MONETARY, KW_INT_N_SIGN_POSN, "" },
153
154         { "d_t_fmt",            1, LC_TIME,     D_T_FMT, "" },
155         { "d_fmt",              1, LC_TIME,     D_FMT, "" },
156         { "t_fmt",              1, LC_TIME,     T_FMT, "" },
157         { "am_str",             1, LC_TIME,     AM_STR, "" },
158         { "pm_str",             1, LC_TIME,     PM_STR, "" },
159         { "t_fmt_ampm",         1, LC_TIME,     T_FMT_AMPM, "" },
160         { "day_1",              1, LC_TIME,     DAY_1, "" },
161         { "day_2",              1, LC_TIME,     DAY_2, "" },
162         { "day_3",              1, LC_TIME,     DAY_3, "" },
163         { "day_4",              1, LC_TIME,     DAY_4, "" },
164         { "day_5",              1, LC_TIME,     DAY_5, "" },
165         { "day_6",              1, LC_TIME,     DAY_6, "" },
166         { "day_7",              1, LC_TIME,     DAY_7, "" },
167         { "abday_1",            1, LC_TIME,     ABDAY_1, "" },
168         { "abday_2",            1, LC_TIME,     ABDAY_2, "" },
169         { "abday_3",            1, LC_TIME,     ABDAY_3, "" },
170         { "abday_4",            1, LC_TIME,     ABDAY_4, "" },
171         { "abday_5",            1, LC_TIME,     ABDAY_5, "" },
172         { "abday_6",            1, LC_TIME,     ABDAY_6, "" },
173         { "abday_7",            1, LC_TIME,     ABDAY_7, "" },
174         { "mon_1",              1, LC_TIME,     MON_1, "" },
175         { "mon_2",              1, LC_TIME,     MON_2, "" },
176         { "mon_3",              1, LC_TIME,     MON_3, "" },
177         { "mon_4",              1, LC_TIME,     MON_4, "" },
178         { "mon_5",              1, LC_TIME,     MON_5, "" },
179         { "mon_6",              1, LC_TIME,     MON_6, "" },
180         { "mon_7",              1, LC_TIME,     MON_7, "" },
181         { "mon_8",              1, LC_TIME,     MON_8, "" },
182         { "mon_9",              1, LC_TIME,     MON_9, "" },
183         { "mon_10",             1, LC_TIME,     MON_10, "" },
184         { "mon_11",             1, LC_TIME,     MON_11, "" },
185         { "mon_12",             1, LC_TIME,     MON_12, "" },
186         { "abmon_1",            1, LC_TIME,     ABMON_1, "" },
187         { "abmon_2",            1, LC_TIME,     ABMON_2, "" },
188         { "abmon_3",            1, LC_TIME,     ABMON_3, "" },
189         { "abmon_4",            1, LC_TIME,     ABMON_4, "" },
190         { "abmon_5",            1, LC_TIME,     ABMON_5, "" },
191         { "abmon_6",            1, LC_TIME,     ABMON_6, "" },
192         { "abmon_7",            1, LC_TIME,     ABMON_7, "" },
193         { "abmon_8",            1, LC_TIME,     ABMON_8, "" },
194         { "abmon_9",            1, LC_TIME,     ABMON_9, "" },
195         { "abmon_10",           1, LC_TIME,     ABMON_10, "" },
196         { "abmon_11",           1, LC_TIME,     ABMON_11, "" },
197         { "abmon_12",           1, LC_TIME,     ABMON_12, "" },
198         { "altmon_1",           1, LC_TIME,     ALTMON_1, "(FreeBSD only)" },
199         { "altmon_2",           1, LC_TIME,     ALTMON_2, "(FreeBSD only)" },
200         { "altmon_3",           1, LC_TIME,     ALTMON_3, "(FreeBSD only)" },
201         { "altmon_4",           1, LC_TIME,     ALTMON_4, "(FreeBSD only)" },
202         { "altmon_5",           1, LC_TIME,     ALTMON_5, "(FreeBSD only)" },
203         { "altmon_6",           1, LC_TIME,     ALTMON_6, "(FreeBSD only)" },
204         { "altmon_7",           1, LC_TIME,     ALTMON_7, "(FreeBSD only)" },
205         { "altmon_8",           1, LC_TIME,     ALTMON_8, "(FreeBSD only)" },
206         { "altmon_9",           1, LC_TIME,     ALTMON_9, "(FreeBSD only)" },
207         { "altmon_10",          1, LC_TIME,     ALTMON_10, "(FreeBSD only)" },
208         { "altmon_11",          1, LC_TIME,     ALTMON_11, "(FreeBSD only)" },
209         { "altmon_12",          1, LC_TIME,     ALTMON_12, "(FreeBSD only)" },
210         { "era",                1, LC_TIME,     ERA, "(unavailable)" },
211         { "era_d_fmt",          1, LC_TIME,     ERA_D_FMT, "(unavailable)" },
212         { "era_d_t_fmt",        1, LC_TIME,     ERA_D_T_FMT, "(unavailable)" },
213         { "era_t_fmt",          1, LC_TIME,     ERA_T_FMT, "(unavailable)" },
214         { "alt_digits",         1, LC_TIME,     ALT_DIGITS, "" },
215         { "d_md_order",         1, LC_TIME,     D_MD_ORDER,
216           "(FreeBSD only)"                              },      /* local */
217
218         { "yesexpr",            1, LC_MESSAGES, YESEXPR, "" },
219         { "noexpr",             1, LC_MESSAGES, NOEXPR, "" },
220         { "yesstr",             1, LC_MESSAGES, YESSTR,
221           "(POSIX legacy)" },                                   /* compat */
222         { "nostr",              1, LC_MESSAGES, NOSTR,
223           "(POSIX legacy)" }                                    /* compat */
224
225 };
226 #define NKWINFO (nitems(kwinfo))
227
228 static const char *boguslocales[] = { "UTF-8" };
229 #define NBOGUS  (nitems(boguslocales))
230
231 int
232 main(int argc, char *argv[])
233 {
234         int     ch;
235         int     tmp;
236
237         while ((ch = getopt(argc, argv, "ackms:")) != -1) {
238                 switch (ch) {
239                 case 'a':
240                         all_locales = 1;
241                         break;
242                 case 'c':
243                         prt_categories = 1;
244                         break;
245                 case 'k':
246                         prt_keywords = 1;
247                         break;
248                 case 'm':
249                         all_charmaps = 1;
250                         break;
251                 default:
252                         usage();
253                 }
254         }
255         argc -= optind;
256         argv += optind;
257
258         /* validate arguments */
259         if (all_locales && all_charmaps)
260                 usage();
261         if ((all_locales || all_charmaps) && argc > 0)
262                 usage();
263         if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
264                 usage();
265
266         /* process '-a' */
267         if (all_locales) {
268                 list_locales();
269                 exit(0);
270         }
271
272         /* process '-m' */
273         if (all_charmaps) {
274                 list_charmaps();
275                 exit(0);
276         }
277
278         /* check for special case '-k list' */
279         tmp = 0;
280         if (prt_keywords && argc > 0)
281                 while (tmp < argc)
282                         if (strcasecmp(argv[tmp++], "list") == 0) {
283                                 showkeywordslist(argv[tmp]);
284                                 exit(0);
285                         }
286
287         /* process '-c', '-k', or command line arguments. */
288         if (prt_categories || prt_keywords || argc > 0) {
289                 if (prt_keywords || argc > 0)
290                         setlocale(LC_ALL, "");
291                 if (argc > 0) {
292                         while (argc > 0) {
293                                 showdetails(*argv);
294                                 argv++;
295                                 argc--;
296                         }
297                 } else {
298                         uint i;
299                         for (i = 0; i < nitems(kwinfo); i++)
300                                 showdetails(kwinfo[i].name);
301                 }
302                 exit(0);
303         }
304
305         /* no arguments, show current locale state */
306         showlocale();
307
308         return (0);
309 }
310
311 void
312 usage(void)
313 {
314         printf("Usage: locale [ -a | -m ]\n"
315                "       locale -k list [prefix]\n"
316                "       locale [ -ck ] [keyword ...]\n");
317         exit(1);
318 }
319
320 /*
321  * Output information about all available locales
322  *
323  * XXX actually output of this function does not guarantee that locale
324  *     is really available to application, since it can be broken or
325  *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
326  *     also validate these locales?
327  */
328 void
329 list_locales(void)
330 {
331         size_t i;
332
333         init_locales_list();
334         for (i = 0; i < locales->sl_cur; i++) {
335                 printf("%s\n", locales->sl_str[i]);
336         }
337 }
338
339 /*
340  * qsort() helper function
341  */
342 static int
343 scmp(const void *s1, const void *s2)
344 {
345         return strcmp(*(const char * const *)s1, *(const char * const *)s2);
346 }
347
348 /*
349  * Output information about all available charmaps
350  *
351  * XXX this function is doing a task in hackish way, i.e. by scaning
352  *     list of locales, spliting their codeset part and building list of
353  *     them.
354  */
355 void
356 list_charmaps(void)
357 {
358         size_t i;
359         char *s, *cs;
360         StringList *charmaps;
361
362         /* initialize StringList */
363         charmaps = sl_init();
364         if (charmaps == NULL)
365                 err(1, "could not allocate memory");
366
367         /* fetch locales list */
368         init_locales_list();
369
370         /* split codesets and build their list */
371         for (i = 0; i < locales->sl_cur; i++) {
372                 s = locales->sl_str[i];
373                 if ((cs = strchr(s, '.')) != NULL) {
374                         cs++;
375                         if (sl_find(charmaps, cs) == NULL)
376                                 sl_add(charmaps, cs);
377                 }
378         }
379
380         /* add US-ASCII, if not yet added */
381         if (sl_find(charmaps, "US-ASCII") == NULL)
382                 sl_add(charmaps, strdup("US-ASCII"));
383
384         /* sort the list */
385         qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
386
387         /* print results */
388         for (i = 0; i < charmaps->sl_cur; i++) {
389                 printf("%s\n", charmaps->sl_str[i]);
390         }
391 }
392
393 /*
394  * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
395  * environment variable is set)
396  */
397 void
398 init_locales_list(void)
399 {
400         DIR *dirp;
401         struct dirent *dp;
402         size_t i;
403         int bogus;
404
405         /* why call this function twice ? */
406         if (locales != NULL)
407                 return;
408
409         /* initialize StringList */
410         locales = sl_init();
411         if (locales == NULL)
412                 err(1, "could not allocate memory");
413
414         /* get actual locales directory name */
415         if (__detect_path_locale() != 0)
416                 err(1, "unable to find locales storage");
417
418         /* open locales directory */
419         dirp = opendir(_PathLocale);
420         if (dirp == NULL)
421                 err(1, "could not open directory '%s'", _PathLocale);
422
423         /* scan directory and store its contents except "." and ".." */
424         while ((dp = readdir(dirp)) != NULL) {
425                 if (*(dp->d_name) == '.')
426                         continue;               /* exclude "." and ".." */
427                 for (bogus = i = 0; i < NBOGUS; i++)
428                         if (strncmp(dp->d_name, boguslocales[i],
429                             strlen(boguslocales[i])) == 0)
430                                 bogus = 1;
431                 if (!bogus)
432                         sl_add(locales, strdup(dp->d_name));
433         }
434         closedir(dirp);
435
436         /* make sure that 'POSIX' and 'C' locales are present in the list.
437          * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
438          * we also list 'C' for constistency
439          */
440         if (sl_find(locales, "POSIX") == NULL)
441                 sl_add(locales, strdup("POSIX"));
442
443         if (sl_find(locales, "C") == NULL)
444                 sl_add(locales, strdup("C"));
445
446         /* make output nicer, sort the list */
447         qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
448 }
449
450 /*
451  * Show current locale status, depending on environment variables
452  */
453 void
454 showlocale(void)
455 {
456         size_t  i;
457         const char *lang, *vval, *eval;
458
459         setlocale(LC_ALL, "");
460
461         lang = getenv("LANG");
462         if (lang == NULL) {
463                 lang = "";
464         }
465         printf("LANG=%s\n", lang);
466         /* XXX: if LANG is null, then set it to "C" to get implied values? */
467
468         for (i = 0; i < NLCINFO; i++) {
469                 vval = setlocale(lcinfo[i].id, NULL);
470                 eval = getenv(lcinfo[i].name);
471                 if (eval != NULL && !strcmp(eval, vval)
472                                 && strcmp(lang, vval)) {
473                         /*
474                          * Appropriate environment variable set, its value
475                          * is valid and not overridden by LC_ALL
476                          *
477                          * XXX: possible side effect: if both LANG and
478                          * overridden environment variable are set into same
479                          * value, then it'll be assumed as 'implied'
480                          */
481                         printf("%s=%s\n", lcinfo[i].name, vval);
482                 } else {
483                         printf("%s=\"%s\"\n", lcinfo[i].name, vval);
484                 }
485         }
486
487         vval = getenv("LC_ALL");
488         if (vval == NULL) {
489                 vval = "";
490         }
491         printf("LC_ALL=%s\n", vval);
492 }
493
494 char *
495 format_grouping(const char *binary)
496 {
497         static char rval[64];
498         const char *cp;
499         size_t roff;
500         int len;
501
502         rval[0] = '\0';
503         roff = 0;
504         for (cp = binary; *cp != '\0'; ++cp) {
505 #if CHAR_MIN != 0
506                 if (*cp < 0)
507                         break;          /* garbage input */
508 #endif
509                 len = snprintf(&rval[roff], sizeof(rval) - roff, "%u;", *cp);
510                 if (len < 0 || (unsigned)len >= sizeof(rval) - roff)
511                         break;          /* insufficient space for output */
512                 roff += len;
513                 if (*cp == CHAR_MAX)
514                         break;          /* special termination */
515         }
516
517         /* Truncate at the last successfully snprintf()ed semicolon. */
518         if (roff != 0)
519                 rval[roff - 1] = '\0';
520
521         return (&rval[0]);
522 }
523
524 /*
525  * keyword value lookup helper (via localeconv())
526  */
527 char *
528 kwval_lconv(int id)
529 {
530         struct lconv *lc;
531         char *rval;
532
533         rval = NULL;
534         lc = localeconv();
535         switch (id) {
536                 case KW_GROUPING:
537                         rval = format_grouping(lc->grouping);
538                         break;
539                 case KW_INT_CURR_SYMBOL:
540                         rval = lc->int_curr_symbol;
541                         break;
542                 case KW_CURRENCY_SYMBOL:
543                         rval = lc->currency_symbol;
544                         break;
545                 case KW_MON_DECIMAL_POINT:
546                         rval = lc->mon_decimal_point;
547                         break;
548                 case KW_MON_THOUSANDS_SEP:
549                         rval = lc->mon_thousands_sep;
550                         break;
551                 case KW_MON_GROUPING:
552                         rval = format_grouping(lc->mon_grouping);
553                         break;
554                 case KW_POSITIVE_SIGN:
555                         rval = lc->positive_sign;
556                         break;
557                 case KW_NEGATIVE_SIGN:
558                         rval = lc->negative_sign;
559                         break;
560                 case KW_INT_FRAC_DIGITS:
561                         rval = &(lc->int_frac_digits);
562                         break;
563                 case KW_FRAC_DIGITS:
564                         rval = &(lc->frac_digits);
565                         break;
566                 case KW_P_CS_PRECEDES:
567                         rval = &(lc->p_cs_precedes);
568                         break;
569                 case KW_P_SEP_BY_SPACE:
570                         rval = &(lc->p_sep_by_space);
571                         break;
572                 case KW_N_CS_PRECEDES:
573                         rval = &(lc->n_cs_precedes);
574                         break;
575                 case KW_N_SEP_BY_SPACE:
576                         rval = &(lc->n_sep_by_space);
577                         break;
578                 case KW_P_SIGN_POSN:
579                         rval = &(lc->p_sign_posn);
580                         break;
581                 case KW_N_SIGN_POSN:
582                         rval = &(lc->n_sign_posn);
583                         break;
584                 case KW_INT_P_CS_PRECEDES:
585                         rval = &(lc->int_p_cs_precedes);
586                         break;
587                 case KW_INT_P_SEP_BY_SPACE:
588                         rval = &(lc->int_p_sep_by_space);
589                         break;
590                 case KW_INT_N_CS_PRECEDES:
591                         rval = &(lc->int_n_cs_precedes);
592                         break;
593                 case KW_INT_N_SEP_BY_SPACE:
594                         rval = &(lc->int_n_sep_by_space);
595                         break;
596                 case KW_INT_P_SIGN_POSN:
597                         rval = &(lc->int_p_sign_posn);
598                         break;
599                 case KW_INT_N_SIGN_POSN:
600                         rval = &(lc->int_n_sign_posn);
601                         break;
602                 default:
603                         break;
604         }
605         return (rval);
606 }
607
608 /*
609  * keyword value and properties lookup
610  */
611 int
612 kwval_lookup(const char *kwname, char **kwval, int *cat, int *isstr)
613 {
614         int     rval;
615         size_t  i;
616
617         rval = 0;
618         for (i = 0; i < NKWINFO; i++) {
619                 if (strcasecmp(kwname, kwinfo[i].name) == 0) {
620                         rval = 1;
621                         *cat = kwinfo[i].catid;
622                         *isstr = kwinfo[i].isstr;
623                         if (kwinfo[i].value_ref < KW_ZERO) {
624                                 *kwval = nl_langinfo(kwinfo[i].value_ref);
625                         } else {
626                                 *kwval = kwval_lconv(kwinfo[i].value_ref);
627                         }
628                         break;
629                 }
630         }
631
632         return (rval);
633 }
634
635 /*
636  * Show details about requested keyword according to '-k' and/or '-c'
637  * command line options specified.
638  */
639 void
640 showdetails(const char *kw)
641 {
642         int     isstr, cat, tmpval;
643         char    *kwval;
644
645         if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
646                 /*
647                  * invalid keyword specified.
648                  * XXX: any actions?
649                  */
650                 fprintf(stderr, "Unknown keyword: `%s'\n", kw);
651                 return;
652         }
653
654         if (prt_categories) {
655                 if (prt_keywords)
656                         printf("%-20s ", lookup_localecat(cat));
657                 else
658                         printf("%-20s\t%s\n", kw, lookup_localecat(cat));
659         }
660
661         if (prt_keywords) {
662                 if (isstr) {
663                         printf("%s=\"%s\"\n", kw, kwval);
664                 } else {
665                         tmpval = (char) *kwval;
666                         printf("%s=%d\n", kw, tmpval);
667                 }
668         }
669
670         if (!prt_categories && !prt_keywords) {
671                 if (isstr) {
672                         printf("%s\n", kwval);
673                 } else {
674                         tmpval = (char) *kwval;
675                         printf("%d\n", tmpval);
676                 }
677         }
678 }
679
680 /*
681  * Convert locale category id into string
682  */
683 const char *
684 lookup_localecat(int cat)
685 {
686         size_t  i;
687
688         for (i = 0; i < NLCINFO; i++)
689                 if (lcinfo[i].id == cat) {
690                         return (lcinfo[i].name);
691                 }
692         return ("UNKNOWN");
693 }
694
695 /*
696  * Show list of keywords
697  */
698 void
699 showkeywordslist(char *substring)
700 {
701         size_t  i;
702
703 #define FMT "%-20s %-12s %-7s %-20s\n"
704
705         if (substring == NULL)
706                 printf("List of available keywords\n\n");
707         else
708                 printf("List of available keywords starting with '%s'\n\n",
709                     substring);
710         printf(FMT, "Keyword", "Category", "Type", "Comment");
711         printf("-------------------- ------------ ------- --------------------\n");
712         for (i = 0; i < NKWINFO; i++) {
713                 if (substring != NULL) {
714                         if (strncmp(kwinfo[i].name, substring,
715                             strlen(substring)) != 0)
716                                 continue;
717                 }
718                 printf(FMT,
719                         kwinfo[i].name,
720                         lookup_localecat(kwinfo[i].catid),
721                         (kwinfo[i].isstr == 0) ? "number" : "string",
722                         kwinfo[i].comment);
723         }
724 }