OSDN Git Service

* loadlib.h: New header implementing safe LoadLibrary calls.
[pf3gnuchains/pf3gnuchains4x.git] / winsup / utils / locale.cc
1 /*
2  * Copyright (c) 2010, Corinna Vinschen
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 #include <stdio.h>
27 #include <ctype.h>
28 #include <getopt.h>
29 #include <string.h>
30 #include <wchar.h>
31 #include <locale.h>
32 #include <langinfo.h>
33 #include <limits.h>
34 #include <sys/cygwin.h>
35 #define WINVER 0x0601
36 #include <windows.h>
37
38 #define LOCALE_ALIAS            "/usr/share/locale/locale.alias"
39 #define LOCALE_ALIAS_LINE_LEN   255
40
41 extern char *__progname;
42
43 void usage (FILE *, int) __attribute__ ((noreturn));
44
45 void
46 usage (FILE * stream, int status)
47 {
48   fprintf (stream,
49            "Usage: %s [-amsuUvh]\n"
50            "   or: %s [-ck] NAME\n"
51            "Get locale-specific information.\n"
52            "\n"
53            "Options:\n"
54            "\n"
55            "  -a, --all-locales    List all available supported locales\n"
56            "  -c, --category-name  List information about given category NAME\n"
57            "  -k, --keyword-name   Print information about given keyword NAME\n"
58            "  -m, --charmaps       List all available character maps\n"
59            "  -s, --system         Print system default locale\n"
60            "  -u, --user           Print user's default locale\n"
61            "  -U, --utf            Attach \".UTF-8\" to the result\n"
62            "  -v, --verbose        More verbose output\n"
63            "  -h, --help           This text\n",
64            __progname, __progname);
65   exit (status);
66 }
67
68 struct option longopts[] = {
69   {"all-locales", no_argument, NULL, 'a'},
70   {"category-name", no_argument, NULL, 'c'},
71   {"keyword-name", no_argument, NULL, 'k'},
72   {"charmaps", no_argument, NULL, 'm'},
73   {"system", no_argument, NULL, 's'},
74   {"user", no_argument, NULL, 'u'},
75   {"utf", no_argument, NULL, 'U'},
76   {"verbose", no_argument, NULL, 'v'},
77   {"help", no_argument, NULL, 'h'},
78   {0, no_argument, NULL, 0}
79 };
80 const char *opts = "achkmsuUv";
81
82 int
83 getlocale (LCID lcid, char *name)
84 {
85   char iso639[10];
86   char iso3166[10];
87
88   iso3166[0] = '\0';
89   if (!GetLocaleInfo (lcid, LOCALE_SISO639LANGNAME, iso639, 10))
90     return 0;
91   GetLocaleInfo (lcid, LOCALE_SISO3166CTRYNAME, iso3166, 10);
92   sprintf (name, "%s%s%s", iso639, lcid > 0x3ff ? "_" : "",
93                            lcid > 0x3ff ? iso3166 : "");
94   return 1;
95 }
96
97 typedef struct {
98   const char *name;
99   const wchar_t *language;
100   const wchar_t *territory;
101   const char *codeset;
102   bool alias;
103 } loc_t;
104 loc_t *locale;
105 size_t loc_max;
106 size_t loc_num;
107
108 void
109 print_codeset (const char *codeset)
110 {
111   for (; *codeset; ++codeset)
112     if (*codeset != '-')
113       putc (tolower ((int)(unsigned char) *codeset), stdout);
114 }
115
116 void
117 print_locale_with_codeset (int verbose, loc_t *locale, bool utf8,
118                            const char *modifier)
119 {
120   static const char *sysroot;
121   char locname[32];
122
123   if (verbose
124       && (!strcmp (locale->name, "C") || !strcmp (locale->name, "POSIX")))
125     return;
126   if (!sysroot)
127     {
128       char sysbuf[PATH_MAX];
129       HMODULE k32 = GetModuleHandleW (L"kernel32.dll");
130       if (GetModuleFileName (k32, sysbuf, PATH_MAX))
131         sysroot = (const char *) cygwin_create_path (CCP_WIN_A_TO_POSIX,
132                                                      sysbuf);
133       if (!sysroot)
134         sysroot = "kernel32.dll";
135     }
136   snprintf (locname, 32, "%s%s%s%s", locale->name, utf8 ? ".utf8" : "",
137                                      modifier ? "@" : "", modifier ?: "");
138   if (verbose)
139     fputs ("locale: ", stdout);
140   printf ("%-15s ", locname);
141   if (verbose)
142     {
143       printf ("archive: %s\n",
144       locale->alias ? LOCALE_ALIAS : sysroot);
145       puts ("-------------------------------------------------------------------------------");
146       printf (" language | %ls\n", locale->language);
147       printf ("territory | %ls\n", locale->territory);
148       printf ("  codeset | %s\n", utf8 ? "UTF-8" : locale->codeset);
149     }
150   putc ('\n', stdout);
151 }
152
153 void
154 print_locale (int verbose, loc_t *locale)
155 {
156   print_locale_with_codeset (verbose, locale, false, NULL);
157   char *modifier = strchr (locale->name, '@');
158   if (!locale->alias)
159     {
160       if (!modifier)
161         print_locale_with_codeset (verbose, locale, true, NULL);
162       else if (!strcmp (modifier, "@cjknarrow"))
163         {
164           *modifier++ = '\0';
165           print_locale_with_codeset (verbose, locale, true, modifier);
166         }
167     }
168 }
169
170 int
171 compare_locales (const void *a, const void *b)
172 {
173   const loc_t *la = (const loc_t *) a;
174   const loc_t *lb = (const loc_t *) b;
175   return strcmp (la->name, lb->name);
176 }
177
178 void
179 add_locale (const char *name, const wchar_t *language, const wchar_t *territory,
180             bool alias = false)
181 {
182   char orig_locale[32];
183
184   if (loc_num >= loc_max)
185     {
186       loc_t *tmp = (loc_t *) realloc (locale, (loc_max + 32) * sizeof (loc_t));
187       if (!tmp)
188         {
189           fprintf (stderr, "Out of memory!\n");
190           exit (1);
191         }
192       locale = tmp;
193       loc_max += 32;
194     }
195   locale[loc_num].name = strdup (name);
196   locale[loc_num].language = wcsdup (language);
197   locale[loc_num].territory = wcsdup (territory);
198   strcpy (orig_locale, setlocale (LC_CTYPE, NULL));
199   setlocale (LC_CTYPE, name);
200   locale[loc_num].codeset = strdup (nl_langinfo (CODESET));
201   setlocale (LC_CTYPE, orig_locale);
202   locale[loc_num].alias = alias;
203   ++loc_num;
204 }
205
206 void
207 add_locale_alias_locales ()
208 {
209   char alias_buf[LOCALE_ALIAS_LINE_LEN + 1], *c;
210   const char *alias, *replace;
211   char orig_locale[32];
212   loc_t search, *loc;
213   size_t orig_loc_num = loc_num;
214
215   FILE *fp = fopen (LOCALE_ALIAS, "rt");
216   if (!fp)
217     return;
218   strcpy (orig_locale, setlocale (LC_CTYPE, NULL));
219   while (fgets (alias_buf, LOCALE_ALIAS_LINE_LEN + 1, fp))
220     {
221       alias_buf[LOCALE_ALIAS_LINE_LEN] = '\0';
222       c = strrchr (alias_buf, '\n');
223       if (c)
224         *c = '\0';
225       c = alias_buf;
226       c += strspn (c, " \t");
227       if (!*c || *c == '#')
228         continue;
229       alias = c;
230       c += strcspn (c, " \t");
231       *c++ = '\0';
232       c += strspn (c, " \t");
233       if (*c == '#')
234         continue;
235       replace = c;
236       c += strcspn (c, " \t");
237       *c++ = '\0';
238       c = strchr (replace, '.');
239       if (c)
240         *c = '\0';
241       search.name = replace;
242       loc = (loc_t *) bsearch (&search, locale, orig_loc_num, sizeof (loc_t),
243                                compare_locales);
244       add_locale (alias, loc ? loc->language : L"", loc ? loc->territory : L"",
245                   true);
246     }
247   fclose (fp);
248 }
249
250 void
251 print_all_locales (int verbose)
252 {
253   LCID lcid = 0;
254   char name[32];
255   DWORD cp;
256
257   unsigned lang, sublang;
258
259   add_locale ("C", L"C", L"POSIX");
260   add_locale ("POSIX", L"C", L"POSIX", true);
261   for (lang = 1; lang <= 0xff; ++lang)
262     {
263       struct {
264         wchar_t language[256];
265         wchar_t country[256];
266         char loc[32];
267       } loc_list[32];
268       int lcnt = 0;
269
270       for (sublang = 1; sublang <= 0x3f; ++sublang)
271         {
272           lcid = (sublang << 10) | lang;
273           if (getlocale (lcid, name))
274             {
275               wchar_t language[256];
276               wchar_t country[256];
277               int i;
278               char *c, loc[32];
279               wchar_t wbuf[9];
280
281               /* Go figure.  Even the English name of a language or
282                  locale might contain native characters. */
283               GetLocaleInfoW (lcid, LOCALE_SENGLANGUAGE, language, 256);
284               GetLocaleInfoW (lcid, LOCALE_SENGCOUNTRY, country, 256);
285               /* Avoid dups */
286               for (i = 0; i < lcnt; ++ i)
287                 if (!wcscmp (loc_list[i].language, language)
288                     && !wcscmp (loc_list[i].country, country))
289                   break;
290               if (i < lcnt)
291                 continue;
292               if (lcnt < 32)
293                 {
294                   wcscpy (loc_list[lcnt].language, language);
295                   wcscpy (loc_list[lcnt].country, country);
296                 }
297               c = stpcpy (loc, name);
298               /* Convert old sr_SP silently to sr_CS on old systems.
299                  Make sure sr_CS country is in recent shape. */
300               if (lang == LANG_SERBIAN
301                   && (sublang == SUBLANG_SERBIAN_LATIN
302                       || sublang == SUBLANG_SERBIAN_CYRILLIC))
303                 {
304                   c = stpcpy (loc, "sr_CS");
305                   wcscpy (country, L"Serbia and Montenegro (Former)");
306                 }
307               /* Now check certain conditions to figure out if that
308                  locale requires a modifier. */
309               if (lang == LANG_SERBIAN && !strncmp (loc, "sr_", 3)
310                   && wcsstr (language, L"(Latin)"))
311                 stpcpy (c, "@latin");
312               else if (lang == LANG_UZBEK
313                        && sublang == SUBLANG_UZBEK_CYRILLIC)
314                 stpcpy (c, "@cyrillic");
315               /* Avoid more dups */
316               for (i = 0; i < lcnt; ++ i)
317                 if (!strcmp (loc_list[i].loc, loc))
318                   {
319                     lcnt++;
320                     break;
321                   }
322               if (i < lcnt)
323                 continue;
324               if (lcnt < 32)
325                 strcpy (loc_list[lcnt++].loc, loc);
326               /* Print */
327               add_locale (loc, language, country);
328               /* Check for locales which sport a modifier for
329                  changing the codeset and other stuff. */
330               if (lang == LANG_BELARUSIAN
331                   && sublang == SUBLANG_BELARUSIAN_BELARUS)
332                 stpcpy (c, "@latin");
333               else if (lang == LANG_TATAR
334                        && sublang == SUBLANG_TATAR_RUSSIA)
335                 stpcpy (c, "@iqtelif");
336               else if (GetLocaleInfoW (lcid,
337                                        LOCALE_IDEFAULTANSICODEPAGE
338                                        | LOCALE_RETURN_NUMBER,
339                                        (PWCHAR) &cp, sizeof cp)
340                        && cp == 1252 /* Latin1*/
341                        && GetLocaleInfoW (lcid, LOCALE_SINTLSYMBOL, wbuf, 9)
342                        && !wcsncmp (wbuf, L"EUR", 3))
343                 stpcpy (c, "@euro");
344               else if (lang == LANG_JAPANESE
345                        || lang == LANG_KOREAN
346                        || lang == LANG_CHINESE)
347                 stpcpy (c, "@cjknarrow");
348               else
349                 continue;
350               add_locale (loc, language, country);
351             }
352         }
353       /* Check Serbian language for the available territories.  Up to
354          Server 2003 we only had sr_SP (silently converted to sr_CS
355          above), in Vista we had only sr_CS.  First starting with W7 we
356          have the actual sr_RS and sr_ME.  However, all of them are
357          supported on all systems in Cygwin.  So we fake them here, if
358          they are missing. */
359       if (lang == LANG_SERBIAN)
360         {
361           int sr_CS_idx = -1;
362           int sr_RS_idx = -1;
363           int i;
364
365           for (i = 0; i < lcnt; ++ i)
366             if (!strcmp (loc_list[i].loc, "sr_CS"))
367               sr_CS_idx = i;
368             else if (!strcmp (loc_list[i].loc, "sr_RS"))
369               sr_RS_idx = i;
370           if (sr_CS_idx > 0 && sr_RS_idx == -1)
371             {
372               add_locale ("sr_RS@latin", L"Serbian (Latin)", L"Serbia");
373               add_locale ("sr_RS", L"Serbian (Cyrillic)", L"Serbia");
374               add_locale ("sr_ME@latin", L"Serbian (Latin)", L"Montenegro");
375               add_locale ("sr_ME", L"Serbian (Cyrillic)", L"Montenegro");
376             }
377         }
378     }
379   /* First sort allows add_locale_alias_locales to bsearch in locales. */
380   qsort (locale, loc_num, sizeof (loc_t), compare_locales);
381   add_locale_alias_locales ();
382   qsort (locale, loc_num, sizeof (loc_t), compare_locales);
383   for (size_t i = 0; i < loc_num; ++i)
384     print_locale (verbose, &locale[i]);
385 }
386
387 void
388 print_charmaps ()
389 {
390   /* FIXME: We need a method to fetch the available charsets from Cygwin, */
391   const char *charmaps[] =
392   {
393     "ASCII",
394     "BIG5",
395     "CP1125",
396     "CP1250",
397     "CP1251",
398     "CP1252",
399     "CP1253",
400     "CP1254",
401     "CP1255",
402     "CP1256",
403     "CP1257",
404     "CP1258",
405     "CP437",
406     "CP720",
407     "CP737",
408     "CP775",
409     "CP850",
410     "CP852",
411     "CP855",
412     "CP857",
413     "CP858",
414     "CP862",
415     "CP866",
416     "CP874",
417     "CP932",
418     "EUC-CN",
419     "EUC-JP",
420     "EUC-KR",
421     "GB2312",
422     "GBK",
423     "GEORGIAN-PS",
424     "ISO-8859-1",
425     "ISO-8859-10",
426     "ISO-8859-11",
427     "ISO-8859-13",
428     "ISO-8859-14",
429     "ISO-8859-15",
430     "ISO-8859-16",
431     "ISO-8859-2",
432     "ISO-8859-3",
433     "ISO-8859-4",
434     "ISO-8859-5",
435     "ISO-8859-6",
436     "ISO-8859-7",
437     "ISO-8859-8",
438     "ISO-8859-9",
439     "KOI8-R",
440     "KOI8-U",
441     "PT154",
442     "SJIS",
443     "TIS-620",
444     "UTF-8",
445     NULL
446   };
447   const char **charmap = charmaps;
448   while (*charmap)
449     printf ("%s\n", *charmap++);
450 }
451
452 void
453 print_lc_ivalue (int key, const char *name, int value)
454 {
455   if (key)
456     printf ("%s=", name);
457   printf ("%d", value == CHAR_MAX ? -1 : value);
458   fputc ('\n', stdout);
459 }
460
461 void
462 print_lc_svalue (int key, const char *name, const char *value)
463 {
464   if (key)
465     printf ("%s=\"", name);
466   fputs (value, stdout);
467   if (key)
468     fputc ('"', stdout);
469   fputc ('\n', stdout);
470 }
471
472 void
473 print_lc_sepstrings (int key, const char *name, const char *value)
474 {
475   char *c;
476
477   if (key)
478     printf ("%s=", name);
479   while (value && *value)
480     {
481       if (key)
482         fputc ('"', stdout);
483       c = strchr (value, ';');
484       if (!c)
485         {
486           fputs (value, stdout);
487           value = NULL;
488         }
489       else
490         {
491           printf ("%.*s", c - value, value);
492           value = c + 1;
493         }
494       if (key)
495         fputc ('"', stdout);
496       if (value && *value)
497         fputc (';', stdout);
498     }
499   fputc ('\n', stdout);
500 }
501
502 void
503 print_lc_strings (int key, const char *name, int from, int to)
504 {
505   if (key)
506     printf ("%s=\"", name);
507   for (int i = from; i <= to; ++i)
508     printf ("%s%s", i > from ? ";" : "", nl_langinfo (i));
509   if (key)
510     fputc ('"', stdout);
511   fputc ('\n', stdout);
512 }
513
514 void
515 print_lc_grouping (int key, const char *name, const char *grouping)
516 {
517   if (key)
518     printf ("%s=", name);
519   for (const char *g = grouping; *g; ++g)
520     printf ("%s%d", g > grouping ? ";" : "", *g == CHAR_MAX ? -1 : *g);
521   fputc ('\n', stdout);
522 }
523
524 enum type_t
525 {
526   is_string_fake,
527   is_grouping,
528   is_string,
529   is_mstrings,
530   is_sepstrings,
531   is_int,
532   is_wchar,
533   is_end
534 };
535
536 struct lc_names_t
537 {
538   const char *name;
539   type_t      type;
540   size_t      fromval;
541   size_t      toval;
542 };
543
544 const char *fake_string[] = {
545  "upper;lower;alpha;digit;xdigit;space;print;graph;blank;cntrl;punct;alnum",
546  "upper\";\"lower\";\"alpha\";\"digit\";\"xdigit\";\"space\";\"print\";\"graph\";\"blank\";\"cntrl\";\"punct\";\"alnum",
547  "toupper;tolower",
548  "toupper\";\"tolower"
549 };
550
551 lc_names_t lc_ctype_names[] =
552 {
553   { "ctype-class-names",         is_string_fake, 0,                      0 },
554   { "ctype-map-names",           is_string_fake, 2,                      0 },
555   { "ctype-outdigit0_mb",        is_string,     _NL_CTYPE_OUTDIGITS0_MB, 0 },
556   { "ctype-outdigit1_mb",        is_string,     _NL_CTYPE_OUTDIGITS1_MB, 0 },
557   { "ctype-outdigit2_mb",        is_string,     _NL_CTYPE_OUTDIGITS2_MB, 0 },
558   { "ctype-outdigit3_mb",        is_string,     _NL_CTYPE_OUTDIGITS3_MB, 0 },
559   { "ctype-outdigit4_mb",        is_string,     _NL_CTYPE_OUTDIGITS4_MB, 0 },
560   { "ctype-outdigit5_mb",        is_string,     _NL_CTYPE_OUTDIGITS5_MB, 0 },
561   { "ctype-outdigit6_mb",        is_string,     _NL_CTYPE_OUTDIGITS6_MB, 0 },
562   { "ctype-outdigit7_mb",        is_string,     _NL_CTYPE_OUTDIGITS7_MB, 0 },
563   { "ctype-outdigit8_mb",        is_string,     _NL_CTYPE_OUTDIGITS8_MB, 0 },
564   { "ctype-outdigit9_mb",        is_string,     _NL_CTYPE_OUTDIGITS9_MB, 0 },
565   { "ctype-outdigit0_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS0_WC, 0 },
566   { "ctype-outdigit1_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS1_WC, 0 },
567   { "ctype-outdigit2_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS2_WC, 0 },
568   { "ctype-outdigit3_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS3_WC, 0 },
569   { "ctype-outdigit4_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS4_WC, 0 },
570   { "ctype-outdigit5_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS5_WC, 0 },
571   { "ctype-outdigit6_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS6_WC, 0 },
572   { "ctype-outdigit7_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS7_WC, 0 },
573   { "ctype-outdigit8_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS8_WC, 0 },
574   { "ctype-outdigit9_wc",        is_wchar,      _NL_CTYPE_OUTDIGITS9_WC, 0 },
575   { "charmap",                   is_string,     CODESET,                 0 },
576   { "ctype-mb-cur-max",          is_int,        _NL_CTYPE_MB_CUR_MAX,    0 },
577   { NULL,                        is_end,        0,                       0 }
578 };
579
580 lc_names_t lc_numeric_names[] =
581 {
582   { "decimal_point",             is_string,     RADIXCHAR,               0 },
583   { "thousands_sep",             is_string,     THOUSEP,                 0 },
584   { "grouping",                  is_grouping,   _NL_NUMERIC_GROUPING,    0 },
585   { "numeric-decimal-point-wc",  is_wchar,      _NL_NUMERIC_DECIMAL_POINT_WC, 0 },
586   { "numeric-thousands-sep-wc",  is_wchar,      _NL_NUMERIC_THOUSANDS_SEP_WC, 0 },
587   { "numeric-codeset",           is_string,     _NL_NUMERIC_CODESET,     0 },
588   { NULL,                        is_end,        0,                       0 }
589 };
590
591 lc_names_t lc_time_names[] =
592 {
593   { "abday",                     is_mstrings,   ABDAY_1,         ABDAY_7  },
594   { "day",                       is_mstrings,   DAY_1,           DAY_7    },
595   { "abmon",                     is_mstrings,   ABMON_1,         ABMON_12 },
596   { "mon",                       is_mstrings,   MON_1,           MON_12   },
597   { "am_pm",                     is_mstrings,   AM_STR,          PM_STR   },
598   { "d_t_fmt",                   is_string,     D_T_FMT,                0 },
599   { "d_fmt",                     is_string,     D_FMT,                  0 },
600   { "t_fmt",                     is_string,     T_FMT,                  0 },
601   { "t_fmt_ampm",                is_string,     T_FMT_AMPM,             0 },
602   { "era",                       is_sepstrings, ERA,                    0 },
603   { "era_d_fmt",                 is_string,     ERA_D_FMT,              0 },
604   { "alt_digits",                is_sepstrings,ALT_DIGITS,              0 },
605   { "era_d_t_fmt",               is_string,     ERA_D_T_FMT,            0 },
606   { "era_t_fmt",                 is_string,     ERA_T_FMT,              0 },
607   { "date_fmt",                  is_string,     _DATE_FMT,              0 },
608   { "time-codeset",              is_string,     _NL_TIME_CODESET,       0 },
609   { NULL,                        is_end,        0,                      0 }
610 };
611
612 lc_names_t lc_collate_names[] =
613 {
614   { "collate-codeset",           is_string,     _NL_COLLATE_CODESET,    0 },
615   { NULL,                        is_end,        0,                      0 }
616 };
617
618 lc_names_t lc_monetary_names[] =
619 {
620   { "int_curr_symbol",           is_string,     _NL_MONETARY_INT_CURR_SYMBOL, 0 },
621   { "currency_symbol",           is_string,     _NL_MONETARY_CURRENCY_SYMBOL, 0 },
622   { "mon_decimal_point",         is_string,     _NL_MONETARY_MON_DECIMAL_POINT, 0 },
623   { "mon_thousands_sep",         is_string,     _NL_MONETARY_MON_THOUSANDS_SEP, 0 },
624   { "mon_grouping",              is_grouping,   _NL_MONETARY_MON_GROUPING, 0 },
625   { "positive_sign",             is_string,     _NL_MONETARY_POSITIVE_SIGN, 0 },
626   { "negative_sign",             is_string,     _NL_MONETARY_NEGATIVE_SIGN, 0 },
627   { "int_frac_digits",           is_int,        _NL_MONETARY_INT_FRAC_DIGITS, 0 },
628   { "frac_digits",               is_int,        _NL_MONETARY_FRAC_DIGITS,   0 },
629   { "p_cs_precedes",             is_int,        _NL_MONETARY_P_CS_PRECEDES, 0 },
630   { "p_sep_by_space",            is_int,        _NL_MONETARY_P_SEP_BY_SPACE, 0 },
631   { "n_cs_precedes",             is_int,        _NL_MONETARY_N_CS_PRECEDES, 0 },
632   { "n_sep_by_space",            is_int,        _NL_MONETARY_N_SEP_BY_SPACE, 0 },
633   { "p_sign_posn",               is_int,        _NL_MONETARY_P_SIGN_POSN,   0 },
634   { "n_sign_posn",               is_int,        _NL_MONETARY_N_SIGN_POSN,   0 },
635   { "int_p_cs_precedes",         is_int,        _NL_MONETARY_INT_P_CS_PRECEDES, 0 },
636   { "int_p_sep_by_space",        is_int,        _NL_MONETARY_INT_P_SEP_BY_SPACE,0 },
637   { "int_n_cs_precedes",         is_int,        _NL_MONETARY_INT_N_CS_PRECEDES, 0 },
638   { "int_n_sep_by_space",        is_int,        _NL_MONETARY_INT_N_SEP_BY_SPACE,0 },
639   { "int_p_sign_posn",           is_int,        _NL_MONETARY_INT_P_SIGN_POSN, 0 },
640   { "int_n_sign_posn",           is_int,        _NL_MONETARY_INT_N_SIGN_POSN, 0 },
641   { "monetary-decimal-point-wc", is_wchar,      _NL_MONETARY_WMON_DECIMAL_POINT, 0 },
642   { "monetary-thousands-sep-wc", is_wchar,      _NL_MONETARY_WMON_THOUSANDS_SEP, 0 },
643   { "monetary-codeset",          is_string,     _NL_MONETARY_CODESET,      0 },
644   { NULL,                        is_end,        0,                         0 }
645 };
646
647 lc_names_t lc_messages_names[] =
648 {
649   { "yesexpr",                   is_string,     YESEXPR,                0 },
650   { "noexpr",                    is_string,     NOEXPR,                 0 },
651   { "yesstr",                    is_string,     YESSTR,                 0 },
652   { "nostr",                     is_string,     NOSTR,                  0 },
653   { "messages-codeset",          is_string,     _NL_MESSAGES_CODESET,   0 },
654   { NULL,                        is_end,        0,                      0 }
655 };
656
657 void
658 print_lc (int cat, int key, const char *category, const char *name,
659           lc_names_t *lc_name)
660 {
661   if (cat)
662     printf ("%s\n", category);
663   for (lc_names_t *lc = lc_name; lc->type != is_end; ++lc)
664     if (!name || !strcmp (name, lc->name))
665       switch (lc->type)
666         {
667         case is_string_fake:
668           print_lc_svalue (key, lc->name, fake_string[lc->fromval + key]);
669           break;
670         case is_grouping:
671           print_lc_grouping (key, lc->name, nl_langinfo (lc->fromval));
672           break;
673         case is_string:
674           print_lc_svalue (key, lc->name, nl_langinfo (lc->fromval));
675           break;
676         case is_sepstrings:
677           print_lc_sepstrings (key, lc->name, nl_langinfo (lc->fromval));
678           break;
679         case is_mstrings:
680           print_lc_strings (key, lc->name, lc->fromval, lc->toval);
681           break;
682         case is_int:
683           print_lc_ivalue (key, lc->name, (int) *nl_langinfo (lc->fromval));
684           break;
685         case is_wchar:
686           print_lc_ivalue (key, lc->name,
687                            *(wchar_t *) nl_langinfo (lc->fromval));
688           break;
689         default:
690           break;
691         }
692 }
693
694 struct cat_t
695 {
696   const char *category;
697   int lc_cat;
698   lc_names_t *lc_names;
699 } categories[] =
700 {
701   { "LC_CTYPE",    LC_CTYPE,    lc_ctype_names    },
702   { "LC_NUMERIC",  LC_NUMERIC,  lc_numeric_names  },
703   { "LC_TIME",     LC_TIME,     lc_time_names     },
704   { "LC_COLLATE",  LC_COLLATE,  lc_collate_names  },
705   { "LC_MONETARY", LC_MONETARY, lc_monetary_names },
706   { "LC_MESSAGES", LC_MESSAGES, lc_messages_names },
707   { NULL,          0,           NULL              }
708 };
709
710 void
711 print_names (int cat, int key, const char *name)
712 {
713   struct cat_t *c;
714   lc_names_t *lc;
715
716   for (c = categories; c->category; ++c)
717     if (!strcmp (name, c->category))
718       {
719         print_lc (cat, key, c->category, NULL, c->lc_names);
720         return;
721       }
722   for (c = categories; c->category; ++c)
723     for (lc = c->lc_names; lc->type != is_end; ++lc)
724       if (!strcmp (name, lc->name))
725       {
726         print_lc (cat, key, c->category, lc->name, lc);
727         return;
728       }
729 }
730
731 void
732 print_lc ()
733 {
734   printf ("LANG=%s\n", getenv ("LANG") ?: "");
735   printf ("LC_CTYPE=\"%s\"\n", setlocale (LC_CTYPE, NULL));
736   printf ("LC_NUMERIC=\"%s\"\n", setlocale (LC_NUMERIC, NULL));
737   printf ("LC_TIME=\"%s\"\n", setlocale (LC_TIME, NULL));
738   printf ("LC_COLLATE=\"%s\"\n", setlocale (LC_COLLATE, NULL));
739   printf ("LC_MONETARY=\"%s\"\n", setlocale (LC_MONETARY, NULL));
740   printf ("LC_MESSAGES=\"%s\"\n", setlocale (LC_MESSAGES, NULL));
741   printf ("LC_ALL=%s\n", getenv ("LC_ALL") ?: "");
742 }
743
744 int
745 main (int argc, char **argv)
746 {
747   int opt;
748   LCID lcid = 0;
749   int all = 0;
750   int cat = 0;
751   int key = 0;
752   int maps = 0;
753   int verbose = 0;
754   const char *utf = "";
755   char name[32];
756
757   setlocale (LC_ALL, "");
758   while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF)
759     switch (opt)
760       {
761       case 'a':
762         all = 1;
763         break;
764       case 'c':
765         cat = 1;
766         break;
767       case 'k':
768         key = 1;
769         break;
770       case 'm':
771         maps = 1;
772         break;
773       case 's':
774         lcid = LOCALE_SYSTEM_DEFAULT;
775         break;
776       case 'u':
777         lcid = LOCALE_USER_DEFAULT;
778         break;
779       case 'U':
780         utf = ".UTF-8";
781         break;
782       case 'v':
783         verbose = 1;
784         break;
785       case 'h':
786         usage (stdout, 0);
787         break;
788       default:
789         usage (stderr, 1);
790         break;
791       }
792   if (all)
793     print_all_locales (verbose);
794   else if (maps)
795     print_charmaps ();
796   else if (lcid)
797     {
798       if (getlocale (lcid, name))
799         printf ("%s%s\n", name, utf);
800     }
801   else if (optind < argc)
802     while (optind < argc)
803       print_names (cat, key, argv[optind++]);
804   else
805     print_lc ();
806   return 0;
807 }