]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/locale/locale.c
MFV r354582: file 5.37.
[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 void    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                                 showdetails(*argv);
418                                 argv++;
419                                 argc--;
420                         }
421                 } else {
422                         uint i;
423                         for (i = 0; i < nitems(kwinfo); i++)
424                                 showdetails(kwinfo[i].name);
425                 }
426                 exit(0);
427         }
428
429         /* no arguments, show current locale state */
430         showlocale();
431
432         return (0);
433 }
434
435 void
436 usage(void)
437 {
438         printf("Usage: locale [ -a | -m ]\n"
439                "       locale -k list [prefix]\n"
440                "       locale [ -ck ] [keyword ...]\n");
441         exit(1);
442 }
443
444 /*
445  * Output information about all available locales
446  *
447  * XXX actually output of this function does not guarantee that locale
448  *     is really available to application, since it can be broken or
449  *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
450  *     also validate these locales?
451  */
452 void
453 list_locales(void)
454 {
455         size_t i;
456
457         init_locales_list();
458         for (i = 0; i < locales->sl_cur; i++) {
459                 printf("%s\n", locales->sl_str[i]);
460         }
461 }
462
463 /*
464  * qsort() helper function
465  */
466 static int
467 scmp(const void *s1, const void *s2)
468 {
469         return strcmp(*(const char * const *)s1, *(const char * const *)s2);
470 }
471
472 /*
473  * Output information about all available charmaps
474  *
475  * XXX this function is doing a task in hackish way, i.e. by scaning
476  *     list of locales, spliting their codeset part and building list of
477  *     them.
478  */
479 void
480 list_charmaps(void)
481 {
482         size_t i;
483         char *s, *cs;
484         StringList *charmaps;
485
486         /* initialize StringList */
487         charmaps = sl_init();
488         if (charmaps == NULL)
489                 err(1, "could not allocate memory");
490
491         /* fetch locales list */
492         init_locales_list();
493
494         /* split codesets and build their list */
495         for (i = 0; i < locales->sl_cur; i++) {
496                 s = locales->sl_str[i];
497                 if ((cs = strchr(s, '.')) != NULL) {
498                         cs++;
499                         if (sl_find(charmaps, cs) == NULL)
500                                 sl_add(charmaps, cs);
501                 }
502         }
503
504         /* add US-ASCII, if not yet added */
505         if (sl_find(charmaps, "US-ASCII") == NULL)
506                 sl_add(charmaps, strdup("US-ASCII"));
507
508         /* sort the list */
509         qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
510
511         /* print results */
512         for (i = 0; i < charmaps->sl_cur; i++) {
513                 printf("%s\n", charmaps->sl_str[i]);
514         }
515 }
516
517 /*
518  * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
519  * environment variable is set)
520  */
521 void
522 init_locales_list(void)
523 {
524         DIR *dirp;
525         struct dirent *dp;
526         size_t i;
527         int bogus;
528
529         /* why call this function twice ? */
530         if (locales != NULL)
531                 return;
532
533         /* initialize StringList */
534         locales = sl_init();
535         if (locales == NULL)
536                 err(1, "could not allocate memory");
537
538         /* get actual locales directory name */
539         if (__detect_path_locale() != 0)
540                 err(1, "unable to find locales storage");
541
542         /* open locales directory */
543         dirp = opendir(_PathLocale);
544         if (dirp == NULL)
545                 err(1, "could not open directory '%s'", _PathLocale);
546
547         /* scan directory and store its contents except "." and ".." */
548         while ((dp = readdir(dirp)) != NULL) {
549                 if (*(dp->d_name) == '.')
550                         continue;               /* exclude "." and ".." */
551                 for (bogus = i = 0; i < NBOGUS; i++)
552                         if (strncmp(dp->d_name, boguslocales[i],
553                             strlen(boguslocales[i])) == 0)
554                                 bogus = 1;
555                 if (!bogus)
556                         sl_add(locales, strdup(dp->d_name));
557         }
558         closedir(dirp);
559
560         /* make sure that 'POSIX' and 'C' locales are present in the list.
561          * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
562          * we also list 'C' for constistency
563          */
564         if (sl_find(locales, "POSIX") == NULL)
565                 sl_add(locales, strdup("POSIX"));
566
567         if (sl_find(locales, "C") == NULL)
568                 sl_add(locales, strdup("C"));
569
570         /* make output nicer, sort the list */
571         qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
572 }
573
574 /*
575  * Show current locale status, depending on environment variables
576  */
577 void
578 showlocale(void)
579 {
580         size_t  i;
581         const char *lang, *vval, *eval;
582
583         setlocale(LC_ALL, "");
584
585         lang = getenv("LANG");
586         if (lang == NULL) {
587                 lang = "";
588         }
589         printf("LANG=%s\n", lang);
590         /* XXX: if LANG is null, then set it to "C" to get implied values? */
591
592         for (i = 0; i < NLCINFO; i++) {
593                 vval = setlocale(lcinfo[i].id, NULL);
594                 eval = getenv(lcinfo[i].name);
595                 if (eval != NULL && !strcmp(eval, vval)
596                                 && strcmp(lang, vval)) {
597                         /*
598                          * Appropriate environment variable set, its value
599                          * is valid and not overridden by LC_ALL
600                          *
601                          * XXX: possible side effect: if both LANG and
602                          * overridden environment variable are set into same
603                          * value, then it'll be assumed as 'implied'
604                          */
605                         printf("%s=%s\n", lcinfo[i].name, vval);
606                 } else {
607                         printf("%s=\"%s\"\n", lcinfo[i].name, vval);
608                 }
609         }
610
611         vval = getenv("LC_ALL");
612         if (vval == NULL) {
613                 vval = "";
614         }
615         printf("LC_ALL=%s\n", vval);
616 }
617
618 char *
619 format_grouping(char *binary)
620 {
621         static char rval[64];
622         const char *cp;
623         size_t roff;
624         int len;
625
626         /*
627          * XXX This check will need to be modified if/when localeconv() is
628          * fixed (PR172215).
629          */
630         if (*binary == CHAR_MAX)
631                 return (binary);
632
633         rval[0] = '\0';
634         roff = 0;
635         for (cp = binary; *cp != '\0'; ++cp) {
636 #if CHAR_MIN != 0
637                 if (*cp < 0)
638                         break;          /* garbage input */
639 #endif
640                 len = snprintf(&rval[roff], sizeof(rval) - roff, "%u;", *cp);
641                 if (len < 0 || (unsigned)len >= sizeof(rval) - roff)
642                         break;          /* insufficient space for output */
643                 roff += len;
644                 if (*cp == CHAR_MAX)
645                         break;          /* special termination */
646         }
647
648         /* Truncate at the last successfully snprintf()ed semicolon. */
649         if (roff != 0)
650                 rval[roff - 1] = '\0';
651
652         return (&rval[0]);
653 }
654
655 /*
656  * keyword value lookup helper for values accessible via localeconv()
657  */
658 char *
659 kwval_lconv(int id)
660 {
661         struct lconv *lc;
662         char *rval;
663
664         rval = NULL;
665         lc = localeconv();
666         switch (id) {
667                 case KW_GROUPING:
668                         rval = format_grouping(lc->grouping);
669                         break;
670                 case KW_INT_CURR_SYMBOL:
671                         rval = lc->int_curr_symbol;
672                         break;
673                 case KW_CURRENCY_SYMBOL:
674                         rval = lc->currency_symbol;
675                         break;
676                 case KW_MON_DECIMAL_POINT:
677                         rval = lc->mon_decimal_point;
678                         break;
679                 case KW_MON_THOUSANDS_SEP:
680                         rval = lc->mon_thousands_sep;
681                         break;
682                 case KW_MON_GROUPING:
683                         rval = format_grouping(lc->mon_grouping);
684                         break;
685                 case KW_POSITIVE_SIGN:
686                         rval = lc->positive_sign;
687                         break;
688                 case KW_NEGATIVE_SIGN:
689                         rval = lc->negative_sign;
690                         break;
691                 case KW_INT_FRAC_DIGITS:
692                         rval = &(lc->int_frac_digits);
693                         break;
694                 case KW_FRAC_DIGITS:
695                         rval = &(lc->frac_digits);
696                         break;
697                 case KW_P_CS_PRECEDES:
698                         rval = &(lc->p_cs_precedes);
699                         break;
700                 case KW_P_SEP_BY_SPACE:
701                         rval = &(lc->p_sep_by_space);
702                         break;
703                 case KW_N_CS_PRECEDES:
704                         rval = &(lc->n_cs_precedes);
705                         break;
706                 case KW_N_SEP_BY_SPACE:
707                         rval = &(lc->n_sep_by_space);
708                         break;
709                 case KW_P_SIGN_POSN:
710                         rval = &(lc->p_sign_posn);
711                         break;
712                 case KW_N_SIGN_POSN:
713                         rval = &(lc->n_sign_posn);
714                         break;
715                 case KW_INT_P_CS_PRECEDES:
716                         rval = &(lc->int_p_cs_precedes);
717                         break;
718                 case KW_INT_P_SEP_BY_SPACE:
719                         rval = &(lc->int_p_sep_by_space);
720                         break;
721                 case KW_INT_N_CS_PRECEDES:
722                         rval = &(lc->int_n_cs_precedes);
723                         break;
724                 case KW_INT_N_SEP_BY_SPACE:
725                         rval = &(lc->int_n_sep_by_space);
726                         break;
727                 case KW_INT_P_SIGN_POSN:
728                         rval = &(lc->int_p_sign_posn);
729                         break;
730                 case KW_INT_N_SIGN_POSN:
731                         rval = &(lc->int_n_sign_posn);
732                         break;
733                 default:
734                         break;
735         }
736         return (rval);
737 }
738
739 /*
740  * keyword value lookup helper for LC_TIME keywords not accessible
741  * via nl_langinfo() or localeconv()
742  */
743 static char *
744 kwval_ltime(int id)
745 {
746         char *rval;
747         struct sbuf *kwsbuf;
748         nl_item i, s_item = 0, e_item = 0;
749
750         switch (id) {
751         case KW_TIME_DAY:
752                 s_item = DAY_1;
753                 e_item = DAY_7;
754                 break;
755         case KW_TIME_ABDAY:
756                 s_item = ABDAY_1;
757                 e_item = ABDAY_7;
758                 break;
759         case KW_TIME_MON:
760                 s_item = MON_1;
761                 e_item = MON_12;
762                 break;
763         case KW_TIME_ABMON:
764                 s_item = ABMON_1;
765                 e_item = ABMON_12;
766                 break;
767         case KW_TIME_AM_PM:
768                 if (asprintf(&rval, "%s;%s",
769                     nl_langinfo(AM_STR),
770                     nl_langinfo(PM_STR)) == -1)
771                         err(1, "asprintf");
772                 return (rval);
773         }
774
775         kwsbuf = sbuf_new_auto();
776         if (kwsbuf == NULL)
777                 err(1, "sbuf");
778         for (i = s_item; i <= e_item; i++) {
779                 (void) sbuf_cat(kwsbuf, nl_langinfo(i));
780                 if (i != e_item)
781                         (void) sbuf_cat(kwsbuf, ";");
782         }
783         (void) sbuf_finish(kwsbuf);
784         rval = strdup(sbuf_data(kwsbuf));
785         if (rval == NULL)
786                 err(1, "strdup");
787         sbuf_delete(kwsbuf);
788         return (rval);
789 }
790
791 /*
792  * keyword value and properties lookup
793  */
794 int
795 kwval_lookup(const char *kwname, char **kwval, int *cat, int *type, int *alloc)
796 {
797         int     rval;
798         size_t  i;
799         static char nastr[3] = "-1";
800
801         rval = 0;
802         *alloc = 0;
803         for (i = 0; i < NKWINFO; i++) {
804                 if (strcasecmp(kwname, kwinfo[i].name) == 0) {
805                         rval = 1;
806                         *cat = kwinfo[i].catid;
807                         *type = kwinfo[i].type;
808                         switch (kwinfo[i].source) {
809                         case SRC_LINFO:
810                                 *kwval = nl_langinfo(kwinfo[i].value_ref);
811                                 break;
812                         case SRC_LCONV:
813                                 *kwval = kwval_lconv(kwinfo[i].value_ref);
814                                 /*
815                                  * XXX This check will need to be modified
816                                  * if/when localeconv() is fixed (PR172215).
817                                  */
818                                 if (**kwval == CHAR_MAX) {
819                                         if (*type == TYPE_NUM)
820                                                 *type = TYPE_UNQ;
821                                         *kwval = nastr;
822                                 }
823                                 break;
824                         case SRC_LTIME:
825                                 *kwval = kwval_ltime(kwinfo[i].value_ref);
826                                 *alloc = 1;
827                                 break;
828                         }
829                         break;
830                 }
831         }
832
833         return (rval);
834 }
835
836 /*
837  * Show details about requested keyword according to '-k' and/or '-c'
838  * command line options specified.
839  */
840 void
841 showdetails(const char *kw)
842 {
843         int     type, cat, tmpval, alloc;
844         char    *kwval;
845
846         if (kwval_lookup(kw, &kwval, &cat, &type, &alloc) == 0) {
847                 /*
848                  * invalid keyword specified.
849                  * XXX: any actions?
850                  */
851                 fprintf(stderr, "Unknown keyword: `%s'\n", kw);
852                 return;
853         }
854
855         if (prt_categories) {
856                 if (prt_keywords)
857                         printf("%-20s ", lookup_localecat(cat));
858                 else
859                         printf("%-20s\t%s\n", kw, lookup_localecat(cat));
860         }
861
862         if (prt_keywords) {
863                 switch (type) {
864                 case TYPE_NUM:
865                         tmpval = (char)*kwval;
866                         printf("%s=%d\n", kw, tmpval);
867                         break;
868                 case TYPE_STR:
869                         printf("%s=\"%s\"\n", kw, kwval);
870                         break;
871                 case TYPE_UNQ:
872                         printf("%s=%s\n", kw, kwval);
873                         break;
874                 }
875         }
876
877         if (!prt_categories && !prt_keywords) {
878                 switch (type) {
879                 case TYPE_NUM:
880                         tmpval = (char)*kwval;
881                         printf("%d\n", tmpval);
882                         break;
883                 case TYPE_STR:
884                 case TYPE_UNQ:
885                         printf("%s\n", kwval);
886                         break;
887                 }
888         }
889
890         if (alloc)
891                 free(kwval);
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 }