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