]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/locale/locale.c
amd64: use register macros for gdb_cpu_getreg()
[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 #include <sys/sbuf.h>
43
44 #include <dirent.h>
45 #include <err.h>
46 #include <limits.h>
47 #include <locale.h>
48 #include <langinfo.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <stringlist.h>
53 #include <unistd.h>
54 #include "setlocale.h"
55
56 /* Local prototypes */
57 char    *format_grouping(char *);
58 void    init_locales_list(void);
59 void    list_charmaps(void);
60 void    list_locales(void);
61 const char *lookup_localecat(int);
62 char    *kwval_lconv(int);
63 int     kwval_lookup(const char *, char **, int *, int *, int *);
64 int     showdetails(const char *);
65 void    showkeywordslist(char *substring);
66 void    showlocale(void);
67 void    usage(void);
68
69 /* Global variables */
70 static StringList *locales = NULL;
71
72 static int      all_locales = 0;
73 static int      all_charmaps = 0;
74 static int      prt_categories = 0;
75 static int      prt_keywords = 0;
76
77 static const struct _lcinfo {
78         const char      *name;
79         int             id;
80 } lcinfo [] = {
81         { "LC_CTYPE",           LC_CTYPE },
82         { "LC_COLLATE",         LC_COLLATE },
83         { "LC_TIME",            LC_TIME },
84         { "LC_NUMERIC",         LC_NUMERIC },
85         { "LC_MONETARY",        LC_MONETARY },
86         { "LC_MESSAGES",        LC_MESSAGES }
87 };
88 #define NLCINFO nitems(lcinfo)
89
90 /* ids for values not referenced by nl_langinfo() */
91 enum {
92         KW_GROUPING,
93         KW_INT_CURR_SYMBOL,
94         KW_CURRENCY_SYMBOL,
95         KW_MON_DECIMAL_POINT,
96         KW_MON_THOUSANDS_SEP,
97         KW_MON_GROUPING,
98         KW_POSITIVE_SIGN,
99         KW_NEGATIVE_SIGN,
100         KW_INT_FRAC_DIGITS,
101         KW_FRAC_DIGITS,
102         KW_P_CS_PRECEDES,
103         KW_P_SEP_BY_SPACE,
104         KW_N_CS_PRECEDES,
105         KW_N_SEP_BY_SPACE,
106         KW_P_SIGN_POSN,
107         KW_N_SIGN_POSN,
108         KW_INT_P_CS_PRECEDES,
109         KW_INT_P_SEP_BY_SPACE,
110         KW_INT_N_CS_PRECEDES,
111         KW_INT_N_SEP_BY_SPACE,
112         KW_INT_P_SIGN_POSN,
113         KW_INT_N_SIGN_POSN,
114         KW_TIME_DAY,
115         KW_TIME_ABDAY,
116         KW_TIME_MON,
117         KW_TIME_ABMON,
118         KW_TIME_AM_PM
119 };
120
121 enum {
122         TYPE_NUM,
123         TYPE_STR,
124         TYPE_UNQ
125 };
126
127 enum {
128         SRC_LINFO,
129         SRC_LCONV,
130         SRC_LTIME
131 };
132
133 static const struct _kwinfo {
134         const char      *name;
135         int             type;
136         int             catid;          /* LC_* */
137         int             source;
138         int             value_ref;
139         const char      *comment;
140 } kwinfo [] = {
141         { "charmap",            TYPE_STR, LC_CTYPE,     SRC_LINFO,
142           CODESET, "" },                                        /* hack */
143
144         /* LC_MONETARY - POSIX */
145         { "int_curr_symbol",    TYPE_STR, LC_MONETARY,  SRC_LCONV,
146           KW_INT_CURR_SYMBOL, "" },
147         { "currency_symbol",    TYPE_STR, LC_MONETARY,  SRC_LCONV,
148           KW_CURRENCY_SYMBOL, "" },
149         { "mon_decimal_point",  TYPE_STR, LC_MONETARY,  SRC_LCONV,
150           KW_MON_DECIMAL_POINT, "" },
151         { "mon_thousands_sep",  TYPE_STR, LC_MONETARY,  SRC_LCONV,
152           KW_MON_THOUSANDS_SEP, "" },
153         { "mon_grouping",       TYPE_UNQ, LC_MONETARY,  SRC_LCONV,
154           KW_MON_GROUPING, "" },
155         { "positive_sign",      TYPE_STR, LC_MONETARY,  SRC_LCONV,
156           KW_POSITIVE_SIGN, "" },
157         { "negative_sign",      TYPE_STR, LC_MONETARY,  SRC_LCONV,
158           KW_NEGATIVE_SIGN, "" },
159         { "int_frac_digits",    TYPE_NUM, LC_MONETARY,  SRC_LCONV,
160           KW_INT_FRAC_DIGITS, "" },
161         { "frac_digits",        TYPE_NUM, LC_MONETARY,  SRC_LCONV,
162           KW_FRAC_DIGITS, "" },
163         { "p_cs_precedes",      TYPE_NUM, LC_MONETARY,  SRC_LCONV,
164           KW_P_CS_PRECEDES, "" },
165         { "p_sep_by_space",     TYPE_NUM, LC_MONETARY,  SRC_LCONV,
166           KW_P_SEP_BY_SPACE, "" },
167         { "n_cs_precedes",      TYPE_NUM, LC_MONETARY,  SRC_LCONV,
168           KW_N_CS_PRECEDES, "" },
169         { "n_sep_by_space",     TYPE_NUM, LC_MONETARY,  SRC_LCONV,
170           KW_N_SEP_BY_SPACE, "" },
171         { "p_sign_posn",        TYPE_NUM, LC_MONETARY,  SRC_LCONV,
172           KW_P_SIGN_POSN, "" },
173         { "n_sign_posn",        TYPE_NUM, LC_MONETARY,  SRC_LCONV,
174           KW_N_SIGN_POSN, "" },
175         { "int_p_cs_precedes",  TYPE_NUM, LC_MONETARY,  SRC_LCONV,
176           KW_INT_P_CS_PRECEDES, "" },
177         { "int_p_sep_by_space", TYPE_NUM, LC_MONETARY,  SRC_LCONV,
178           KW_INT_P_SEP_BY_SPACE, "" },
179         { "int_n_cs_precedes",  TYPE_NUM, LC_MONETARY,  SRC_LCONV,
180           KW_INT_N_CS_PRECEDES, "" },
181         { "int_n_sep_by_space", TYPE_NUM, LC_MONETARY,  SRC_LCONV,
182           KW_INT_N_SEP_BY_SPACE, "" },
183         { "int_p_sign_posn",    TYPE_NUM, LC_MONETARY,  SRC_LCONV,
184           KW_INT_P_SIGN_POSN, "" },
185         { "int_n_sign_posn",    TYPE_NUM, LC_MONETARY,  SRC_LCONV,
186           KW_INT_N_SIGN_POSN, "" },
187
188         /* LC_NUMERIC - POSIX */
189         { "decimal_point",      TYPE_STR, LC_NUMERIC,   SRC_LINFO,
190           RADIXCHAR, "" },
191         { "thousands_sep",      TYPE_STR, LC_NUMERIC,   SRC_LINFO,
192           THOUSEP, "" },
193         { "grouping",           TYPE_UNQ, LC_NUMERIC,   SRC_LCONV,
194           KW_GROUPING, "" },
195         /* LC_NUMERIC - local additions */
196         { "radixchar",          TYPE_STR, LC_NUMERIC,   SRC_LINFO,
197           RADIXCHAR, "Same as decimal_point (FreeBSD only)" },  /* compat */
198         { "thousep",            TYPE_STR, LC_NUMERIC,   SRC_LINFO,
199           THOUSEP, "Same as thousands_sep (FreeBSD only)" },    /* compat */
200
201         /* LC_TIME - POSIX */
202         { "abday",              TYPE_STR, LC_TIME,      SRC_LTIME,
203           KW_TIME_ABDAY, "" },
204         { "day",                TYPE_STR, LC_TIME,      SRC_LTIME,
205           KW_TIME_DAY, "" },
206         { "abmon",              TYPE_STR, LC_TIME,      SRC_LTIME,
207           KW_TIME_ABMON, "" },
208         { "mon",                TYPE_STR, LC_TIME,      SRC_LTIME,
209           KW_TIME_MON, "" },
210         { "d_t_fmt",            TYPE_STR, LC_TIME,      SRC_LINFO,
211           D_T_FMT, "" },
212         { "d_fmt",              TYPE_STR, LC_TIME,      SRC_LINFO,
213           D_FMT, "" },
214         { "t_fmt",              TYPE_STR, LC_TIME,      SRC_LINFO,
215           T_FMT, "" },
216         { "am_pm",              TYPE_STR, LC_TIME,      SRC_LTIME,
217           KW_TIME_AM_PM, "" },
218         { "t_fmt_ampm",         TYPE_STR, LC_TIME,      SRC_LINFO,
219           T_FMT_AMPM, "" },
220         { "era",                TYPE_UNQ, LC_TIME,      SRC_LINFO,
221           ERA, "(unavailable)" },
222         { "era_d_fmt",          TYPE_STR, LC_TIME,      SRC_LINFO,
223           ERA_D_FMT, "(unavailable)" },
224         { "era_d_t_fmt",        TYPE_STR, LC_TIME,      SRC_LINFO,
225           ERA_D_T_FMT, "(unavailable)" },
226         { "era_t_fmt",          TYPE_STR, LC_TIME,      SRC_LINFO,
227           ERA_T_FMT, "(unavailable)" },
228         { "alt_digits",         TYPE_UNQ, LC_TIME,      SRC_LINFO,
229           ALT_DIGITS, "" },
230         /* LC_TIME - local additions */
231         { "abday_1",            TYPE_STR, LC_TIME,      SRC_LINFO,
232           ABDAY_1, "(FreeBSD only)" },
233         { "abday_2",            TYPE_STR, LC_TIME,      SRC_LINFO,
234           ABDAY_2, "(FreeBSD only)" },
235         { "abday_3",            TYPE_STR, LC_TIME,      SRC_LINFO,
236           ABDAY_3, "(FreeBSD only)" },
237         { "abday_4",            TYPE_STR, LC_TIME,      SRC_LINFO,
238           ABDAY_4, "(FreeBSD only)" },
239         { "abday_5",            TYPE_STR, LC_TIME,      SRC_LINFO,
240           ABDAY_5, "(FreeBSD only)" },
241         { "abday_6",            TYPE_STR, LC_TIME,      SRC_LINFO,
242           ABDAY_6, "(FreeBSD only)" },
243         { "abday_7",            TYPE_STR, LC_TIME,      SRC_LINFO,
244           ABDAY_7, "(FreeBSD only)" },
245         { "day_1",              TYPE_STR, LC_TIME,      SRC_LINFO,
246           DAY_1, "(FreeBSD only)" },
247         { "day_2",              TYPE_STR, LC_TIME,      SRC_LINFO,
248           DAY_2, "(FreeBSD only)" },
249         { "day_3",              TYPE_STR, LC_TIME,      SRC_LINFO,
250           DAY_3, "(FreeBSD only)" },
251         { "day_4",              TYPE_STR, LC_TIME,      SRC_LINFO,
252           DAY_4, "(FreeBSD only)" },
253         { "day_5",              TYPE_STR, LC_TIME,      SRC_LINFO,
254           DAY_5, "(FreeBSD only)" },
255         { "day_6",              TYPE_STR, LC_TIME,      SRC_LINFO,
256           DAY_6, "(FreeBSD only)" },
257         { "day_7",              TYPE_STR, LC_TIME,      SRC_LINFO,
258           DAY_7, "(FreeBSD only)" },
259         { "abmon_1",            TYPE_STR, LC_TIME,      SRC_LINFO,
260           ABMON_1, "(FreeBSD only)" },
261         { "abmon_2",            TYPE_STR, LC_TIME,      SRC_LINFO,
262           ABMON_2, "(FreeBSD only)" },
263         { "abmon_3",            TYPE_STR, LC_TIME,      SRC_LINFO,
264           ABMON_3, "(FreeBSD only)" },
265         { "abmon_4",            TYPE_STR, LC_TIME,      SRC_LINFO,
266           ABMON_4, "(FreeBSD only)" },
267         { "abmon_5",            TYPE_STR, LC_TIME,      SRC_LINFO,
268           ABMON_5, "(FreeBSD only)" },
269         { "abmon_6",            TYPE_STR, LC_TIME,      SRC_LINFO,
270           ABMON_6, "(FreeBSD only)" },
271         { "abmon_7",            TYPE_STR, LC_TIME,      SRC_LINFO,
272           ABMON_7, "(FreeBSD only)" },
273         { "abmon_8",            TYPE_STR, LC_TIME,      SRC_LINFO,
274           ABMON_8, "(FreeBSD only)" },
275         { "abmon_9",            TYPE_STR, LC_TIME,      SRC_LINFO,
276           ABMON_9, "(FreeBSD only)" },
277         { "abmon_10",           TYPE_STR, LC_TIME,      SRC_LINFO,
278           ABMON_10, "(FreeBSD only)" },
279         { "abmon_11",           TYPE_STR, LC_TIME,      SRC_LINFO,
280           ABMON_11, "(FreeBSD only)" },
281         { "abmon_12",           TYPE_STR, LC_TIME,      SRC_LINFO,
282           ABMON_12, "(FreeBSD only)" },
283         { "mon_1",              TYPE_STR, LC_TIME,      SRC_LINFO,
284           MON_1, "(FreeBSD only)" },
285         { "mon_2",              TYPE_STR, LC_TIME,      SRC_LINFO,
286           MON_2, "(FreeBSD only)" },
287         { "mon_3",              TYPE_STR, LC_TIME,      SRC_LINFO,
288           MON_3, "(FreeBSD only)" },
289         { "mon_4",              TYPE_STR, LC_TIME,      SRC_LINFO,
290           MON_4, "(FreeBSD only)" },
291         { "mon_5",              TYPE_STR, LC_TIME,      SRC_LINFO,
292           MON_5, "(FreeBSD only)" },
293         { "mon_6",              TYPE_STR, LC_TIME,      SRC_LINFO,
294           MON_6, "(FreeBSD only)" },
295         { "mon_7",              TYPE_STR, LC_TIME,      SRC_LINFO,
296           MON_7, "(FreeBSD only)" },
297         { "mon_8",              TYPE_STR, LC_TIME,      SRC_LINFO,
298           MON_8, "(FreeBSD only)" },
299         { "mon_9",              TYPE_STR, LC_TIME,      SRC_LINFO,
300           MON_9, "(FreeBSD only)" },
301         { "mon_10",             TYPE_STR, LC_TIME,      SRC_LINFO,
302           MON_10, "(FreeBSD only)" },
303         { "mon_11",             TYPE_STR, LC_TIME,      SRC_LINFO,
304           MON_11, "(FreeBSD only)" },
305         { "mon_12",             TYPE_STR, LC_TIME,      SRC_LINFO,
306           MON_12, "(FreeBSD only)" },
307         { "altmon_1",           TYPE_STR, LC_TIME,      SRC_LINFO,
308           ALTMON_1, "(FreeBSD only)" },
309         { "altmon_2",           TYPE_STR, LC_TIME,      SRC_LINFO,
310           ALTMON_2, "(FreeBSD only)" },
311         { "altmon_3",           TYPE_STR, LC_TIME,      SRC_LINFO,
312           ALTMON_3, "(FreeBSD only)" },
313         { "altmon_4",           TYPE_STR, LC_TIME,      SRC_LINFO,
314           ALTMON_4, "(FreeBSD only)" },
315         { "altmon_5",           TYPE_STR, LC_TIME,      SRC_LINFO,
316           ALTMON_5, "(FreeBSD only)" },
317         { "altmon_6",           TYPE_STR, LC_TIME,      SRC_LINFO,
318           ALTMON_6, "(FreeBSD only)" },
319         { "altmon_7",           TYPE_STR, LC_TIME,      SRC_LINFO,
320           ALTMON_7, "(FreeBSD only)" },
321         { "altmon_8",           TYPE_STR, LC_TIME,      SRC_LINFO,
322           ALTMON_8, "(FreeBSD only)" },
323         { "altmon_9",           TYPE_STR, LC_TIME,      SRC_LINFO,
324           ALTMON_9, "(FreeBSD only)" },
325         { "altmon_10",          TYPE_STR, LC_TIME,      SRC_LINFO,
326           ALTMON_10, "(FreeBSD only)" },
327         { "altmon_11",          TYPE_STR, LC_TIME,      SRC_LINFO,
328           ALTMON_11, "(FreeBSD only)" },
329         { "altmon_12",          TYPE_STR, LC_TIME,      SRC_LINFO,
330           ALTMON_12, "(FreeBSD only)" },
331         { "am_str",             TYPE_STR, LC_TIME,      SRC_LINFO,
332           AM_STR, "(FreeBSD only)" },
333         { "pm_str",             TYPE_STR, LC_TIME,      SRC_LINFO,
334           PM_STR, "(FreeBSD only)" },
335         { "d_md_order",         TYPE_STR, LC_TIME,      SRC_LINFO,
336           D_MD_ORDER, "(FreeBSD only)" },                       /* local */
337
338         /* LC_MESSAGES - POSIX */
339         { "yesexpr",            TYPE_STR, LC_MESSAGES, SRC_LINFO,
340           YESEXPR, "" },
341         { "noexpr",             TYPE_STR, LC_MESSAGES, SRC_LINFO,
342           NOEXPR, "" },
343         /* LC_MESSAGES - local additions */
344         { "yesstr",             TYPE_STR, LC_MESSAGES, SRC_LINFO,
345           YESSTR, "(POSIX legacy)" },                           /* compat */
346         { "nostr",              TYPE_STR, LC_MESSAGES, SRC_LINFO,
347           NOSTR, "(POSIX legacy)" }                             /* compat */
348
349 };
350 #define NKWINFO (nitems(kwinfo))
351
352 static const char *boguslocales[] = { "UTF-8" };
353 #define NBOGUS  (nitems(boguslocales))
354
355 int
356 main(int argc, char *argv[])
357 {
358         int     ch;
359         int     tmp;
360
361         while ((ch = getopt(argc, argv, "ackms:")) != -1) {
362                 switch (ch) {
363                 case 'a':
364                         all_locales = 1;
365                         break;
366                 case 'c':
367                         prt_categories = 1;
368                         break;
369                 case 'k':
370                         prt_keywords = 1;
371                         break;
372                 case 'm':
373                         all_charmaps = 1;
374                         break;
375                 default:
376                         usage();
377                 }
378         }
379         argc -= optind;
380         argv += optind;
381
382         /* validate arguments */
383         if (all_locales && all_charmaps)
384                 usage();
385         if ((all_locales || all_charmaps) && argc > 0)
386                 usage();
387         if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
388                 usage();
389
390         /* process '-a' */
391         if (all_locales) {
392                 list_locales();
393                 exit(0);
394         }
395
396         /* process '-m' */
397         if (all_charmaps) {
398                 list_charmaps();
399                 exit(0);
400         }
401
402         /* check for special case '-k list' */
403         tmp = 0;
404         if (prt_keywords && argc > 0)
405                 while (tmp < argc)
406                         if (strcasecmp(argv[tmp++], "list") == 0) {
407                                 showkeywordslist(argv[tmp]);
408                                 exit(0);
409                         }
410
411         /* process '-c', '-k', or command line arguments. */
412         if (prt_categories || prt_keywords || argc > 0) {
413                 if (prt_keywords || argc > 0)
414                         setlocale(LC_ALL, "");
415                 if (argc > 0) {
416                         while (argc > 0) {
417                                 if (showdetails(*argv) != 0)
418                                         exit(EXIT_FAILURE);
419                                 argv++;
420                                 argc--;
421                         }
422                 } else {
423                         uint i;
424                         for (i = 0; i < nitems(kwinfo); i++)
425                                 showdetails(kwinfo[i].name);
426                 }
427                 exit(0);
428         }
429
430         /* no arguments, show current locale state */
431         showlocale();
432
433         return (0);
434 }
435
436 void
437 usage(void)
438 {
439         printf("Usage: locale [ -a | -m ]\n"
440                "       locale -k list [prefix]\n"
441                "       locale [ -ck ] [keyword ...]\n");
442         exit(1);
443 }
444
445 /*
446  * Output information about all available locales
447  *
448  * XXX actually output of this function does not guarantee that locale
449  *     is really available to application, since it can be broken or
450  *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
451  *     also validate these locales?
452  */
453 void
454 list_locales(void)
455 {
456         size_t i;
457
458         init_locales_list();
459         for (i = 0; i < locales->sl_cur; i++) {
460                 printf("%s\n", locales->sl_str[i]);
461         }
462 }
463
464 /*
465  * qsort() helper function
466  */
467 static int
468 scmp(const void *s1, const void *s2)
469 {
470         return strcmp(*(const char * const *)s1, *(const char * const *)s2);
471 }
472
473 /*
474  * Output information about all available charmaps
475  *
476  * XXX this function is doing a task in hackish way, i.e. by scaning
477  *     list of locales, spliting their codeset part and building list of
478  *     them.
479  */
480 void
481 list_charmaps(void)
482 {
483         size_t i;
484         char *s, *cs;
485         StringList *charmaps;
486
487         /* initialize StringList */
488         charmaps = sl_init();
489         if (charmaps == NULL)
490                 err(1, "could not allocate memory");
491
492         /* fetch locales list */
493         init_locales_list();
494
495         /* split codesets and build their list */
496         for (i = 0; i < locales->sl_cur; i++) {
497                 s = locales->sl_str[i];
498                 if ((cs = strchr(s, '.')) != NULL) {
499                         cs++;
500                         if (sl_find(charmaps, cs) == NULL)
501                                 sl_add(charmaps, cs);
502                 }
503         }
504
505         /* add US-ASCII, if not yet added */
506         if (sl_find(charmaps, "US-ASCII") == NULL)
507                 sl_add(charmaps, strdup("US-ASCII"));
508
509         /* sort the list */
510         qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
511
512         /* print results */
513         for (i = 0; i < charmaps->sl_cur; i++) {
514                 printf("%s\n", charmaps->sl_str[i]);
515         }
516 }
517
518 /*
519  * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
520  * environment variable is set)
521  */
522 void
523 init_locales_list(void)
524 {
525         DIR *dirp;
526         struct dirent *dp;
527         size_t i;
528         int bogus;
529
530         /* why call this function twice ? */
531         if (locales != NULL)
532                 return;
533
534         /* initialize StringList */
535         locales = sl_init();
536         if (locales == NULL)
537                 err(1, "could not allocate memory");
538
539         /* get actual locales directory name */
540         if (__detect_path_locale() != 0)
541                 err(1, "unable to find locales storage");
542
543         /* open locales directory */
544         dirp = opendir(_PathLocale);
545         if (dirp == NULL)
546                 err(1, "could not open directory '%s'", _PathLocale);
547
548         /* scan directory and store its contents except "." and ".." */
549         while ((dp = readdir(dirp)) != NULL) {
550                 if (*(dp->d_name) == '.')
551                         continue;               /* exclude "." and ".." */
552                 for (bogus = i = 0; i < NBOGUS; i++)
553                         if (strncmp(dp->d_name, boguslocales[i],
554                             strlen(boguslocales[i])) == 0)
555                                 bogus = 1;
556                 if (!bogus)
557                         sl_add(locales, strdup(dp->d_name));
558         }
559         closedir(dirp);
560
561         /* make sure that 'POSIX' and 'C' locales are present in the list.
562          * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
563          * we also list 'C' for constistency
564          */
565         if (sl_find(locales, "POSIX") == NULL)
566                 sl_add(locales, strdup("POSIX"));
567
568         if (sl_find(locales, "C") == NULL)
569                 sl_add(locales, strdup("C"));
570
571         /* make output nicer, sort the list */
572         qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
573 }
574
575 /*
576  * Show current locale status, depending on environment variables
577  */
578 void
579 showlocale(void)
580 {
581         size_t  i;
582         const char *lang, *vval, *eval;
583
584         setlocale(LC_ALL, "");
585
586         lang = getenv("LANG");
587         if (lang == NULL) {
588                 lang = "";
589         }
590         printf("LANG=%s\n", lang);
591         /* XXX: if LANG is null, then set it to "C" to get implied values? */
592
593         for (i = 0; i < NLCINFO; i++) {
594                 vval = setlocale(lcinfo[i].id, NULL);
595                 eval = getenv(lcinfo[i].name);
596                 if (eval != NULL && !strcmp(eval, vval)
597                                 && strcmp(lang, vval)) {
598                         /*
599                          * Appropriate environment variable set, its value
600                          * is valid and not overridden by LC_ALL
601                          *
602                          * XXX: possible side effect: if both LANG and
603                          * overridden environment variable are set into same
604                          * value, then it'll be assumed as 'implied'
605                          */
606                         printf("%s=%s\n", lcinfo[i].name, vval);
607                 } else {
608                         printf("%s=\"%s\"\n", lcinfo[i].name, vval);
609                 }
610         }
611
612         vval = getenv("LC_ALL");
613         if (vval == NULL) {
614                 vval = "";
615         }
616         printf("LC_ALL=%s\n", vval);
617 }
618
619 char *
620 format_grouping(char *binary)
621 {
622         static char rval[64];
623         const char *cp;
624         size_t roff;
625         int len;
626
627         /*
628          * XXX This check will need to be modified if/when localeconv() is
629          * fixed (PR172215).
630          */
631         if (*binary == CHAR_MAX)
632                 return (binary);
633
634         rval[0] = '\0';
635         roff = 0;
636         for (cp = binary; *cp != '\0'; ++cp) {
637 #if CHAR_MIN != 0
638                 if (*cp < 0)
639                         break;          /* garbage input */
640 #endif
641                 len = snprintf(&rval[roff], sizeof(rval) - roff, "%u;", *cp);
642                 if (len < 0 || (unsigned)len >= sizeof(rval) - roff)
643                         break;          /* insufficient space for output */
644                 roff += len;
645                 if (*cp == CHAR_MAX)
646                         break;          /* special termination */
647         }
648
649         /* Truncate at the last successfully snprintf()ed semicolon. */
650         if (roff != 0)
651                 rval[roff - 1] = '\0';
652
653         return (&rval[0]);
654 }
655
656 /*
657  * keyword value lookup helper for values accessible via localeconv()
658  */
659 char *
660 kwval_lconv(int id)
661 {
662         struct lconv *lc;
663         char *rval;
664
665         rval = NULL;
666         lc = localeconv();
667         switch (id) {
668                 case KW_GROUPING:
669                         rval = format_grouping(lc->grouping);
670                         break;
671                 case KW_INT_CURR_SYMBOL:
672                         rval = lc->int_curr_symbol;
673                         break;
674                 case KW_CURRENCY_SYMBOL:
675                         rval = lc->currency_symbol;
676                         break;
677                 case KW_MON_DECIMAL_POINT:
678                         rval = lc->mon_decimal_point;
679                         break;
680                 case KW_MON_THOUSANDS_SEP:
681                         rval = lc->mon_thousands_sep;
682                         break;
683                 case KW_MON_GROUPING:
684                         rval = format_grouping(lc->mon_grouping);
685                         break;
686                 case KW_POSITIVE_SIGN:
687                         rval = lc->positive_sign;
688                         break;
689                 case KW_NEGATIVE_SIGN:
690                         rval = lc->negative_sign;
691                         break;
692                 case KW_INT_FRAC_DIGITS:
693                         rval = &(lc->int_frac_digits);
694                         break;
695                 case KW_FRAC_DIGITS:
696                         rval = &(lc->frac_digits);
697                         break;
698                 case KW_P_CS_PRECEDES:
699                         rval = &(lc->p_cs_precedes);
700                         break;
701                 case KW_P_SEP_BY_SPACE:
702                         rval = &(lc->p_sep_by_space);
703                         break;
704                 case KW_N_CS_PRECEDES:
705                         rval = &(lc->n_cs_precedes);
706                         break;
707                 case KW_N_SEP_BY_SPACE:
708                         rval = &(lc->n_sep_by_space);
709                         break;
710                 case KW_P_SIGN_POSN:
711                         rval = &(lc->p_sign_posn);
712                         break;
713                 case KW_N_SIGN_POSN:
714                         rval = &(lc->n_sign_posn);
715                         break;
716                 case KW_INT_P_CS_PRECEDES:
717                         rval = &(lc->int_p_cs_precedes);
718                         break;
719                 case KW_INT_P_SEP_BY_SPACE:
720                         rval = &(lc->int_p_sep_by_space);
721                         break;
722                 case KW_INT_N_CS_PRECEDES:
723                         rval = &(lc->int_n_cs_precedes);
724                         break;
725                 case KW_INT_N_SEP_BY_SPACE:
726                         rval = &(lc->int_n_sep_by_space);
727                         break;
728                 case KW_INT_P_SIGN_POSN:
729                         rval = &(lc->int_p_sign_posn);
730                         break;
731                 case KW_INT_N_SIGN_POSN:
732                         rval = &(lc->int_n_sign_posn);
733                         break;
734                 default:
735                         break;
736         }
737         return (rval);
738 }
739
740 /*
741  * keyword value lookup helper for LC_TIME keywords not accessible
742  * via nl_langinfo() or localeconv()
743  */
744 static char *
745 kwval_ltime(int id)
746 {
747         char *rval;
748         struct sbuf *kwsbuf;
749         nl_item i, s_item = 0, e_item = 0;
750
751         switch (id) {
752         case KW_TIME_DAY:
753                 s_item = DAY_1;
754                 e_item = DAY_7;
755                 break;
756         case KW_TIME_ABDAY:
757                 s_item = ABDAY_1;
758                 e_item = ABDAY_7;
759                 break;
760         case KW_TIME_MON:
761                 s_item = MON_1;
762                 e_item = MON_12;
763                 break;
764         case KW_TIME_ABMON:
765                 s_item = ABMON_1;
766                 e_item = ABMON_12;
767                 break;
768         case KW_TIME_AM_PM:
769                 if (asprintf(&rval, "%s;%s",
770                     nl_langinfo(AM_STR),
771                     nl_langinfo(PM_STR)) == -1)
772                         err(1, "asprintf");
773                 return (rval);
774         }
775
776         kwsbuf = sbuf_new_auto();
777         if (kwsbuf == NULL)
778                 err(1, "sbuf");
779         for (i = s_item; i <= e_item; i++) {
780                 (void) sbuf_cat(kwsbuf, nl_langinfo(i));
781                 if (i != e_item)
782                         (void) sbuf_cat(kwsbuf, ";");
783         }
784         (void) sbuf_finish(kwsbuf);
785         rval = strdup(sbuf_data(kwsbuf));
786         if (rval == NULL)
787                 err(1, "strdup");
788         sbuf_delete(kwsbuf);
789         return (rval);
790 }
791
792 /*
793  * keyword value and properties lookup
794  */
795 int
796 kwval_lookup(const char *kwname, char **kwval, int *cat, int *type, int *alloc)
797 {
798         int     rval;
799         size_t  i;
800         static char nastr[3] = "-1";
801
802         rval = 0;
803         *alloc = 0;
804         for (i = 0; i < NKWINFO; i++) {
805                 if (strcasecmp(kwname, kwinfo[i].name) == 0) {
806                         rval = 1;
807                         *cat = kwinfo[i].catid;
808                         *type = kwinfo[i].type;
809                         switch (kwinfo[i].source) {
810                         case SRC_LINFO:
811                                 *kwval = nl_langinfo(kwinfo[i].value_ref);
812                                 break;
813                         case SRC_LCONV:
814                                 *kwval = kwval_lconv(kwinfo[i].value_ref);
815                                 /*
816                                  * XXX This check will need to be modified
817                                  * if/when localeconv() is fixed (PR172215).
818                                  */
819                                 if (**kwval == CHAR_MAX) {
820                                         if (*type == TYPE_NUM)
821                                                 *type = TYPE_UNQ;
822                                         *kwval = nastr;
823                                 }
824                                 break;
825                         case SRC_LTIME:
826                                 *kwval = kwval_ltime(kwinfo[i].value_ref);
827                                 *alloc = 1;
828                                 break;
829                         }
830                         break;
831                 }
832         }
833
834         return (rval);
835 }
836
837 /*
838  * Show details about requested keyword according to '-k' and/or '-c'
839  * command line options specified.
840  */
841 int
842 showdetails(const char *kw)
843 {
844         int     type, cat, tmpval, alloc;
845         char    *kwval;
846
847         if (kwval_lookup(kw, &kwval, &cat, &type, &alloc) == 0) {
848                 /* Invalid keyword specified */
849                 fprintf(stderr, "Unknown keyword: `%s'\n", kw);
850                 return (1);
851         }
852
853         if (prt_categories) {
854                 if (prt_keywords)
855                         printf("%-20s ", lookup_localecat(cat));
856                 else
857                         printf("%-20s\t%s\n", kw, lookup_localecat(cat));
858         }
859
860         if (prt_keywords) {
861                 switch (type) {
862                 case TYPE_NUM:
863                         tmpval = (char)*kwval;
864                         printf("%s=%d\n", kw, tmpval);
865                         break;
866                 case TYPE_STR:
867                         printf("%s=\"%s\"\n", kw, kwval);
868                         break;
869                 case TYPE_UNQ:
870                         printf("%s=%s\n", kw, kwval);
871                         break;
872                 }
873         }
874
875         if (!prt_categories && !prt_keywords) {
876                 switch (type) {
877                 case TYPE_NUM:
878                         tmpval = (char)*kwval;
879                         printf("%d\n", tmpval);
880                         break;
881                 case TYPE_STR:
882                 case TYPE_UNQ:
883                         printf("%s\n", kwval);
884                         break;
885                 }
886         }
887
888         if (alloc)
889                 free(kwval);
890
891         return (0);
892 }
893
894 /*
895  * Convert locale category id into string
896  */
897 const char *
898 lookup_localecat(int cat)
899 {
900         size_t  i;
901
902         for (i = 0; i < NLCINFO; i++)
903                 if (lcinfo[i].id == cat) {
904                         return (lcinfo[i].name);
905                 }
906         return ("UNKNOWN");
907 }
908
909 /*
910  * Show list of keywords
911  */
912 void
913 showkeywordslist(char *substring)
914 {
915         size_t  i;
916
917 #define FMT "%-20s %-12s %-7s %-20s\n"
918
919         if (substring == NULL)
920                 printf("List of available keywords\n\n");
921         else
922                 printf("List of available keywords starting with '%s'\n\n",
923                     substring);
924         printf(FMT, "Keyword", "Category", "Type", "Comment");
925         printf("-------------------- ------------ ------- --------------------\n");
926         for (i = 0; i < NKWINFO; i++) {
927                 if (substring != NULL) {
928                         if (strncmp(kwinfo[i].name, substring,
929                             strlen(substring)) != 0)
930                                 continue;
931                 }
932                 printf(FMT,
933                         kwinfo[i].name,
934                         lookup_localecat(kwinfo[i].catid),
935                         (kwinfo[i].type == TYPE_NUM) ? "number" : "string",
936                         kwinfo[i].comment);
937         }
938 }