[BZ #40]
[platform/upstream/glibc.git] / locale / programs / locale.c
1 /* Implementation of the locale program according to POSIX 9945-2.
2    Copyright (C) 1995-1997, 1999-2003, 2004 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1995.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    The GNU C Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the GNU C Library; if not, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    02111-1307 USA.  */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <argp.h>
26 #include <argz.h>
27 #include <dirent.h>
28 #include <errno.h>
29 #include <error.h>
30 #include <fcntl.h>
31 #include <langinfo.h>
32 #include <libintl.h>
33 #include <limits.h>
34 #include <locale.h>
35 #include <search.h>
36 #include <stdio.h>
37 #include <stdio_ext.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <sys/mman.h>
42 #include <sys/stat.h>
43
44 #include "localeinfo.h"
45 #include "charmap-dir.h"
46 #include "../locarchive.h"
47
48 extern void *xmalloc (size_t __n);
49 extern char *xstrdup (const char *__str);
50
51 #define ARCHIVE_NAME LOCALEDIR "/locale-archive"
52
53 /* If set print the name of the category.  */
54 static int show_category_name;
55
56 /* If set print the name of the item.  */
57 static int show_keyword_name;
58
59 /* Print names of all available locales.  */
60 static int do_all;
61
62 /* Print names of all available character maps.  */
63 static int do_charmaps = 0;
64
65 /* Nonzero if verbose output is wanted.  */
66 static int verbose;
67
68 /* Name and version of program.  */
69 static void print_version (FILE *stream, struct argp_state *state);
70 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
71
72 /* Definitions of arguments for argp functions.  */
73 static const struct argp_option options[] =
74 {
75   { NULL, 0, NULL, 0, N_("System information:") },
76   { "all-locales", 'a', NULL, OPTION_NO_USAGE,
77     N_("Write names of available locales") },
78   { "charmaps", 'm', NULL, OPTION_NO_USAGE,
79     N_("Write names of available charmaps") },
80   { NULL, 0, NULL, 0, N_("Modify output format:") },
81   { "category-name", 'c', NULL, 0, N_("Write names of selected categories") },
82   { "keyword-name", 'k', NULL, 0, N_("Write names of selected keywords") },
83   { "verbose", 'v', NULL, 0, N_("Print more information") },
84   { NULL, 0, NULL, 0, NULL }
85 };
86
87 /* Short description of program.  */
88 static const char doc[] = N_("Get locale-specific information.");
89
90 /* Strings for arguments in help texts.  */
91 static const char args_doc[] = N_("NAME\n[-a|-m]");
92
93 /* Prototype for option handler.  */
94 static error_t parse_opt (int key, char *arg, struct argp_state *state);
95
96 /* Function to print some extra text in the help message.  */
97 static char *more_help (int key, const char *text, void *input);
98
99 /* Data structure to communicate with argp functions.  */
100 static struct argp argp =
101 {
102   options, parse_opt, args_doc, doc, NULL, more_help
103 };
104
105
106 /* We don't have these constants defined because we don't use them.  Give
107    default values.  */
108 #define CTYPE_MB_CUR_MIN 0
109 #define CTYPE_MB_CUR_MAX 0
110 #define CTYPE_HASH_SIZE 0
111 #define CTYPE_HASH_LAYERS 0
112 #define CTYPE_CLASS 0
113 #define CTYPE_TOUPPER_EB 0
114 #define CTYPE_TOLOWER_EB 0
115 #define CTYPE_TOUPPER_EL 0
116 #define CTYPE_TOLOWER_EL 0
117
118 /* Definition of the data structure which represents a category and its
119    items.  */
120 struct category
121 {
122   int cat_id;
123   const char *name;
124   size_t number;
125   struct cat_item
126   {
127     int item_id;
128     const char *name;
129     enum { std, opt } status;
130     enum value_type value_type;
131     int min;
132     int max;
133   } *item_desc;
134 };
135
136 /* Simple helper macro.  */
137 #define NELEMS(arr) ((sizeof (arr)) / (sizeof (arr[0])))
138
139 /* For some tricky stuff.  */
140 #define NO_PAREN(Item, More...) Item, ## More
141
142 /* We have all categories defined in `categories.def'.  Now construct
143    the description and data structure used for all categories.  */
144 #define DEFINE_ELEMENT(Item, More...) { Item, ## More },
145 #define DEFINE_CATEGORY(category, name, items, postload) \
146     static struct cat_item category##_desc[] =                                \
147       {                                                                       \
148         NO_PAREN items                                                        \
149       };
150
151 #include "categories.def"
152 #undef DEFINE_CATEGORY
153
154 static struct category category[] =
155   {
156 #define DEFINE_CATEGORY(category, name, items, postload) \
157     [category] = { _NL_NUM_##category, name, NELEMS (category##_desc),        \
158                    category##_desc },
159 #include "categories.def"
160 #undef DEFINE_CATEGORY
161   };
162 #define NCATEGORIES NELEMS (category)
163
164
165 /* Automatically set variable.  */
166 extern const char *__progname;
167
168 /* helper function for extended name handling.  */
169 extern void locale_special (const char *name, int show_category_name,
170                             int show_keyword_name);
171
172 /* Prototypes for local functions.  */
173 static void print_LC_IDENTIFICATION (void *mapped, size_t size);
174 static void print_LC_CTYPE (void *mapped, size_t size);
175 static void write_locales (void);
176 static int nameentcmp (const void *a, const void *b);
177 static int write_archive_locales (void **all_datap, char *linebuf);
178 static void write_charmaps (void);
179 static void show_locale_vars (void);
180 static void show_info (const char *name);
181
182
183 int
184 main (int argc, char *argv[])
185 {
186   int remaining;
187
188   /* Set initial values for global variables.  */
189   show_category_name = 0;
190   show_keyword_name = 0;
191
192   /* Set locale.  Do not set LC_ALL because the other categories must
193      not be affected (according to POSIX.2).  */
194   if (setlocale (LC_CTYPE, "") == NULL)
195     error (0, errno, gettext ("Cannot set LC_CTYPE to default locale"));
196   if (setlocale (LC_MESSAGES, "") == NULL)
197     error (0, errno, gettext ("Cannot set LC_MESSAGES to default locale"));
198
199   /* Initialize the message catalog.  */
200   textdomain (PACKAGE);
201
202   /* Parse and process arguments.  */
203   argp_parse (&argp, argc, argv, 0, &remaining, NULL);
204
205   /* `-a' requests the names of all available locales.  */
206   if (do_all != 0)
207     {
208       if (setlocale (LC_COLLATE, "") == NULL)
209         error (0, errno,
210                gettext ("Cannot set LC_COLLATE to default locale"));
211       write_locales ();
212       exit (EXIT_SUCCESS);
213     }
214
215   /* `m' requests the names of all available charmaps.  The names can be
216      used for the -f argument to localedef(1).  */
217   if (do_charmaps != 0)
218     {
219       write_charmaps ();
220       exit (EXIT_SUCCESS);
221     }
222
223   /* Specific information about the current locale are requested.
224      Change to this locale now.  */
225   if (setlocale (LC_ALL, "") == NULL)
226     error (0, errno, gettext ("Cannot set LC_ALL to default locale"));
227
228   /* If no real argument is given we have to print the contents of the
229      current locale definition variables.  These are LANG and the LC_*.  */
230   if (remaining == argc && show_keyword_name == 0 && show_category_name == 0)
231     {
232       show_locale_vars ();
233       exit (EXIT_SUCCESS);
234     }
235
236   /* Process all given names.  */
237   while (remaining <  argc)
238     show_info (argv[remaining++]);
239
240   exit (EXIT_SUCCESS);
241 }
242
243
244 /* Handle program arguments.  */
245 static error_t
246 parse_opt (int key, char *arg, struct argp_state *state)
247 {
248   switch (key)
249     {
250     case 'a':
251       do_all = 1;
252       break;
253     case 'c':
254       show_category_name = 1;
255       break;
256     case 'm':
257       do_charmaps = 1;
258       break;
259     case 'k':
260       show_keyword_name = 1;
261       break;
262     case 'v':
263       verbose = 1;
264       break;
265     default:
266       return ARGP_ERR_UNKNOWN;
267     }
268   return 0;
269 }
270
271
272 static char *
273 more_help (int key, const char *text, void *input)
274 {
275   switch (key)
276     {
277     case ARGP_KEY_HELP_EXTRA:
278       /* We print some extra information.  */
279       return xstrdup (gettext ("\
280 For bug reporting instructions, please see:\n\
281 <http://www.gnu.org/software/libc/bugs.html>.\n"));
282     default:
283       break;
284     }
285   return (char *) text;
286 }
287
288 /* Print the version information.  */
289 static void
290 print_version (FILE *stream, struct argp_state *state)
291 {
292   fprintf (stream, "locale (GNU %s) %s\n", PACKAGE, VERSION);
293   fprintf (stream, gettext ("\
294 Copyright (C) %s Free Software Foundation, Inc.\n\
295 This is free software; see the source for copying conditions.  There is NO\n\
296 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
297 "), "2004");
298   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
299 }
300
301
302 /* Simple action function which prints arguments as strings.  */
303 static void
304 print_names (const void *nodep, VISIT value, int level)
305 {
306   if (value == postorder || value == leaf)
307     puts (*(char **) nodep);
308 }
309
310
311 static int
312 select_dirs (const struct dirent *dirent)
313 {
314   int result = 0;
315
316   if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0)
317     {
318       mode_t mode = 0;
319
320 #ifdef _DIRENT_HAVE_D_TYPE
321       if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK)
322         mode = DTTOIF (dirent->d_type);
323       else
324 #endif
325         {
326           struct stat64 st;
327           char buf[sizeof (LOCALEDIR) + strlen (dirent->d_name) + 1];
328
329           stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"), dirent->d_name);
330
331           if (stat64 (buf, &st) == 0)
332             mode = st.st_mode;
333         }
334
335       result = S_ISDIR (mode);
336     }
337
338   return result;
339 }
340
341
342 static void
343 print_LC_IDENTIFICATION (void *mapped, size_t size)
344 {
345   /* Read the information from the file.  */
346   struct
347     {
348       unsigned int magic;
349       unsigned int nstrings;
350       unsigned int strindex[0];
351     } *filedata = mapped;
352
353   if (filedata->magic == LIMAGIC (LC_IDENTIFICATION)
354       && (sizeof *filedata
355           + (filedata->nstrings
356              * sizeof (unsigned int))
357           <= size))
358     {
359       const char *str;
360
361 #define HANDLE(idx, name) \
362   str = ((char *) mapped                                                      \
363          + filedata->strindex[_NL_ITEM_INDEX (_NL_IDENTIFICATION_##idx)]);    \
364   if (*str != '\0')                                                           \
365     printf ("%9s | %s\n", name, str)
366       HANDLE (TITLE, "title");
367       HANDLE (SOURCE, "source");
368       HANDLE (ADDRESS, "address");
369       HANDLE (CONTACT, "contact");
370       HANDLE (EMAIL, "email");
371       HANDLE (TEL, "telephone");
372       HANDLE (FAX, "fax");
373       HANDLE (LANGUAGE, "language");
374       HANDLE (TERRITORY, "territory");
375       HANDLE (AUDIENCE, "audience");
376       HANDLE (APPLICATION, "application");
377       HANDLE (ABBREVIATION, "abbreviation");
378       HANDLE (REVISION, "revision");
379       HANDLE (DATE, "date");
380     }
381 }
382
383
384 static void
385 print_LC_CTYPE (void *mapped, size_t size)
386 {
387   struct
388     {
389       unsigned int magic;
390       unsigned int nstrings;
391       unsigned int strindex[0];
392     } *filedata = mapped;
393
394   if (filedata->magic == LIMAGIC (LC_CTYPE)
395       && (sizeof *filedata
396           + (filedata->nstrings
397              * sizeof (unsigned int))
398           <= size))
399     {
400       const char *str;
401
402       str = ((char *) mapped
403              + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)]);
404       if (*str != '\0')
405         printf ("  codeset | %s\n", str);
406     }
407 }
408
409
410 /* Write the names of all available locales to stdout.  We have some
411    sources of the information: the contents of the locale directory
412    and the locale.alias file.  To avoid duplicates and print the
413    result is a reasonable order we put all entries is a search tree
414    and print them afterwards.  */
415 static void
416 write_locales (void)
417 {
418   char linebuf[80];
419   void *all_data = NULL;
420   struct dirent **dirents;
421   int ndirents;
422   int cnt;
423   char *alias_path;
424   size_t alias_path_len;
425   char *entry;
426   int first_locale = 1;
427
428 #define PUT(name) tsearch (name, &all_data, \
429                            (int (*) (const void *, const void *)) strcoll)
430 #define GET(name) tfind (name, &all_data, \
431                            (int (*) (const void *, const void *)) strcoll)
432
433   /* `POSIX' locale is always available (POSIX.2 4.34.3).  */
434   PUT ("POSIX");
435   /* And so is the "C" locale.  */
436   PUT ("C");
437
438   memset (linebuf, '-', sizeof (linebuf) - 1);
439   linebuf[sizeof (linebuf) - 1] = '\0';
440
441   /* First scan the locale archive.  */
442   if (write_archive_locales (&all_data, linebuf))
443     first_locale = 0;
444
445   /* Now we can look for all files in the directory.  */
446   ndirents = scandir (LOCALEDIR, &dirents, select_dirs, alphasort);
447   for (cnt = 0; cnt < ndirents; ++cnt)
448     {
449       /* Test whether at least the LC_CTYPE data is there.  Some
450          directories only contain translations.  */
451       char buf[sizeof (LOCALEDIR) + strlen (dirents[cnt]->d_name)
452               + sizeof "/LC_IDENTIFICATION"];
453       char *enddir;
454       struct stat64 st;
455
456       stpcpy (enddir = stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"),
457                                dirents[cnt]->d_name),
458               "/LC_IDENTIFICATION");
459
460       if (stat64 (buf, &st) == 0 && S_ISREG (st.st_mode))
461         {
462           if (verbose && GET (dirents[cnt]->d_name) == NULL)
463             {
464               /* Provide some nice output of all kinds of
465                  information.  */
466               int fd;
467
468               if (! first_locale)
469                 putchar_unlocked ('\n');
470               first_locale = 0;
471
472               printf ("locale: %-15.15s directory: %.*s\n%s\n",
473                       dirents[cnt]->d_name, (int) (enddir - buf), buf,
474                       linebuf);
475
476               fd = open64 (buf, O_RDONLY);
477               if (fd != -1)
478                 {
479                   void *mapped = mmap64 (NULL, st.st_size, PROT_READ,
480                                          MAP_SHARED, fd, 0);
481                   if (mapped != MAP_FAILED)
482                     {
483                       print_LC_IDENTIFICATION (mapped, st.st_size);
484
485                       munmap (mapped, st.st_size);
486                     }
487
488                   close (fd);
489
490                   /* Now try to get the charset information.  */
491                   strcpy (enddir, "/LC_CTYPE");
492                   fd = open64 (buf, O_RDONLY);
493                   if (fd != -1 && fstat64 (fd, &st) >= 0
494                       && ((mapped = mmap64 (NULL, st.st_size, PROT_READ,
495                                             MAP_SHARED, fd, 0))
496                           != MAP_FAILED))
497                     {
498                       print_LC_CTYPE (mapped, st.st_size);
499
500                       munmap (mapped, st.st_size);
501                     }
502
503                   if (fd != -1)
504                     close (fd);
505                 }
506             }
507
508           /* If the verbose format is not selected we simply
509              collect the names.  */
510           PUT (xstrdup (dirents[cnt]->d_name));
511         }
512     }
513   if (ndirents > 0)
514     free (dirents);
515
516   /* Now read the locale.alias files.  */
517   if (argz_create_sep (LOCALE_ALIAS_PATH, ':', &alias_path, &alias_path_len))
518     error (1, errno, gettext ("while preparing output"));
519
520   entry = NULL;
521   while ((entry = argz_next (alias_path, alias_path_len, entry)))
522     {
523       static const char aliasfile[] = "/locale.alias";
524       FILE *fp;
525       char full_name[strlen (entry) + sizeof aliasfile];
526
527       stpcpy (stpcpy (full_name, entry), aliasfile);
528       fp = fopen (full_name, "rm");
529       if (fp == NULL)
530         /* Ignore non-existing files.  */
531         continue;
532
533       /* No threads present.  */
534       __fsetlocking (fp, FSETLOCKING_BYCALLER);
535
536       while (! feof_unlocked (fp))
537         {
538           /* It is a reasonable approach to use a fix buffer here
539              because
540              a) we are only interested in the first two fields
541              b) these fields must be usable as file names and so must
542                 not be that long  */
543           char buf[BUFSIZ];
544           char *alias;
545           char *value;
546           char *cp;
547
548           if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
549             /* EOF reached.  */
550             break;
551
552           cp = buf;
553           /* Ignore leading white space.  */
554           while (isspace (cp[0]) && cp[0] != '\n')
555             ++cp;
556
557           /* A leading '#' signals a comment line.  */
558           if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
559             {
560               alias = cp++;
561               while (cp[0] != '\0' && !isspace (cp[0]))
562                 ++cp;
563               /* Terminate alias name.  */
564               if (cp[0] != '\0')
565                 *cp++ = '\0';
566
567               /* Now look for the beginning of the value.  */
568               while (isspace (cp[0]))
569                 ++cp;
570
571               if (cp[0] != '\0')
572                 {
573                   value = cp++;
574                   while (cp[0] != '\0' && !isspace (cp[0]))
575                     ++cp;
576                   /* Terminate value.  */
577                   if (cp[0] == '\n')
578                     {
579                       /* This has to be done to make the following
580                          test for the end of line possible.  We are
581                          looking for the terminating '\n' which do not
582                          overwrite here.  */
583                       *cp++ = '\0';
584                       *cp = '\n';
585                     }
586                   else if (cp[0] != '\0')
587                     *cp++ = '\0';
588
589                   /* Add the alias.  */
590                   if (! verbose && GET (value) != NULL)
591                     PUT (xstrdup (alias));
592                 }
593             }
594
595           /* Possibly not the whole line fits into the buffer.
596              Ignore the rest of the line.  */
597           while (strchr (cp, '\n') == NULL)
598             {
599               cp = buf;
600               if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
601                 /* Make sure the inner loop will be left.  The outer
602                    loop will exit at the `feof' test.  */
603                 *cp = '\n';
604             }
605         }
606
607       fclose (fp);
608     }
609
610   if (! verbose)
611     {
612       twalk (all_data, print_names);
613     }
614 }
615
616
617 struct nameent
618 {
619   char *name;
620   uint32_t locrec_offset;
621 };
622
623
624 static int
625 nameentcmp (const void *a, const void *b)
626 {
627   return strcoll (((const struct nameent *) a)->name,
628                   ((const struct nameent *) b)->name);
629 }
630
631
632 static int
633 write_archive_locales (void **all_datap, char *linebuf)
634 {
635   struct stat64 st;
636   void *all_data = *all_datap;
637   size_t len = 0;
638   struct locarhead *head;
639   struct namehashent *namehashtab;
640   char *addr = MAP_FAILED;
641   int fd, ret = 0;
642   uint32_t cnt;
643
644   fd = open64 (ARCHIVE_NAME, O_RDONLY);
645   if (fd < 0)
646     return 0;
647
648   if (fstat64 (fd, &st) < 0 || st.st_size < sizeof (*head))
649     goto error_out;
650
651   len = st.st_size;
652   addr = mmap64 (NULL, len, PROT_READ, MAP_SHARED, fd, 0);
653   if (addr == MAP_FAILED)
654     goto error_out;
655
656   head = (struct locarhead *) addr;
657   if (head->namehash_offset + head->namehash_size > len
658       || head->string_offset + head->string_size > len
659       || head->locrectab_offset + head->locrectab_size > len
660       || head->sumhash_offset + head->sumhash_size > len)
661     goto error_out;
662
663   namehashtab = (struct namehashent *) (addr + head->namehash_offset);
664   if (! verbose)
665     {
666       for (cnt = 0; cnt < head->namehash_size; ++cnt)
667         if (namehashtab[cnt].locrec_offset != 0)
668           {
669             PUT (xstrdup (addr + namehashtab[cnt].name_offset));
670             ++ret;
671           }
672     }
673   else
674     {
675       struct nameent *names;
676       uint32_t used;
677
678       names = (struct nameent *) xmalloc (head->namehash_used
679                                           * sizeof (struct nameent));
680       for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
681         if (namehashtab[cnt].locrec_offset != 0)
682           {
683             names[used].name = addr + namehashtab[cnt].name_offset;
684             names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
685           }
686
687       /* Sort the names.  */
688       qsort (names, used, sizeof (struct nameent), nameentcmp);
689
690       for (cnt = 0; cnt < used; ++cnt)
691         {
692           struct locrecent *locrec;
693
694           PUT (xstrdup (names[cnt].name));
695
696           if (cnt)
697             putchar_unlocked ('\n');
698
699           printf ("locale: %-15.15s archive: " ARCHIVE_NAME "\n%s\n",
700                   names[cnt].name, linebuf);
701
702           locrec = (struct locrecent *) (addr + names[cnt].locrec_offset);
703
704           print_LC_IDENTIFICATION (addr
705                                    + locrec->record[LC_IDENTIFICATION].offset,
706                                    locrec->record[LC_IDENTIFICATION].len);
707
708           print_LC_CTYPE (addr + locrec->record[LC_CTYPE].offset,
709                           locrec->record[LC_CTYPE].len);
710         }
711
712       ret = used;
713     }
714
715 error_out:
716   if (addr != MAP_FAILED)
717     munmap (addr, len);
718   close (fd);
719   *all_datap = all_data;
720   return ret;
721 }
722
723
724 /* Write the names of all available character maps to stdout.  */
725 static void
726 write_charmaps (void)
727 {
728   void *all_data = NULL;
729   CHARMAP_DIR *dir;
730   const char *dirent;
731
732   /* Look for all files in the charmap directory.  */
733   dir = charmap_opendir (CHARMAP_PATH);
734   if (dir == NULL)
735     return;
736
737   while ((dirent = charmap_readdir (dir)) != NULL)
738     {
739       char **aliases;
740       char **p;
741
742       PUT (xstrdup (dirent));
743
744       aliases = charmap_aliases (CHARMAP_PATH, dirent);
745
746 #if 0
747       /* Add the code_set_name and the aliases.  */
748       for (p = aliases; *p; p++)
749         PUT (xstrdup (*p));
750 #else
751       /* Add the code_set_name only.  Most aliases are obsolete.  */
752       p = aliases;
753       if (*p)
754         PUT (xstrdup (*p));
755 #endif
756
757       charmap_free_aliases (aliases);
758     }
759
760   charmap_closedir (dir);
761
762   twalk (all_data, print_names);
763 }
764
765
766 /* We have to show the contents of the environments determining the
767    locale.  */
768 static void
769 show_locale_vars (void)
770 {
771   size_t cat_no;
772   const char *lcall = getenv ("LC_ALL");
773   const char *lang = getenv ("LANG") ? : "";
774
775   auto void get_source (const char *name);
776
777   void get_source (const char *name)
778     {
779       char *val = getenv (name);
780
781       if ((lcall ?: "")[0] != '\0' || val == NULL)
782         printf ("%s=\"%s\"\n", name,
783                 (lcall ?: "")[0] ? lcall : (lang ?: "")[0] ? lang : "POSIX");
784       else
785         printf ("%s=%s\n", name, val);
786     }
787
788   /* LANG has to be the first value.  */
789   printf ("LANG=%s\n", lang);
790
791   /* Now all categories in an unspecified order.  */
792   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
793     if (cat_no != LC_ALL)
794       get_source (category[cat_no].name);
795
796   /* The last is the LC_ALL value.  */
797   printf ("LC_ALL=%s\n", lcall ? : "");
798 }
799
800
801 /* Show the information request for NAME.  */
802 static void
803 show_info (const char *name)
804 {
805   size_t cat_no;
806
807   auto void print_item (struct cat_item *item);
808
809   void print_item (struct cat_item *item)
810     {
811       switch (item->value_type)
812         {
813         case string:
814           if (show_keyword_name)
815             printf ("%s=\"", item->name);
816           fputs (nl_langinfo (item->item_id) ? : "", stdout);
817           if (show_keyword_name)
818             putchar ('"');
819           putchar ('\n');
820           break;
821         case stringarray:
822           {
823             int cnt;
824             const char *val;
825
826             if (show_keyword_name)
827               printf ("%s=\"", item->name);
828
829             for (cnt = 0; cnt < item->max - 1; ++cnt)
830               {
831                 val = nl_langinfo (item->item_id + cnt);
832                 if (val != NULL)
833                   fputs (val, stdout);
834                 putchar (';');
835               }
836
837             val = nl_langinfo (item->item_id + cnt);
838             if (val != NULL)
839               fputs (val, stdout);
840
841             if (show_keyword_name)
842               putchar ('"');
843             putchar ('\n');
844           }
845           break;
846         case stringlist:
847           {
848             int first = 1;
849             const char *val = nl_langinfo (item->item_id) ? : "";
850             int cnt;
851
852             if (show_keyword_name)
853               printf ("%s=", item->name);
854
855             for (cnt = 0; cnt < item->max && *val != '\0'; ++cnt)
856               {
857                 printf ("%s%s%s%s", first ? "" : ";",
858                         show_keyword_name ? "\"" : "", val,
859                         show_keyword_name ? "\"" : "");
860                 val = strchr (val, '\0') + 1;
861                 first = 0;
862               }
863             putchar ('\n');
864           }
865           break;
866         case byte:
867           {
868             const char *val = nl_langinfo (item->item_id);
869
870             if (show_keyword_name)
871               printf ("%s=", item->name);
872
873             if (val != NULL)
874               printf ("%d", *val == '\177' ? -1 : *val);
875             putchar ('\n');
876           }
877           break;
878         case bytearray:
879           {
880             const char *val = nl_langinfo (item->item_id);
881             int cnt = val ? strlen (val) : 0;
882
883             if (show_keyword_name)
884               printf ("%s=", item->name);
885
886             while (cnt > 1)
887               {
888                 printf ("%d;", *val == '\177' ? -1 : *val);
889                 --cnt;
890                 ++val;
891               }
892
893             printf ("%d\n", cnt == 0 || *val == '\177' ? -1 : *val);
894           }
895           break;
896         case word:
897           {
898             union { unsigned int word; char *string; } val;
899             val.string = nl_langinfo (item->item_id);
900             if (show_keyword_name)
901               printf ("%s=", item->name);
902
903             printf ("%d\n", val.word);
904           }
905           break;
906         case wstring:
907         case wstringarray:
908         case wstringlist:
909           /* We don't print wide character information since the same
910              information is available in a multibyte string.  */
911         default:
912           break;
913
914         }
915     }
916
917   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
918     if (cat_no != LC_ALL)
919       {
920         size_t item_no;
921
922         if (strcmp (name, category[cat_no].name) == 0)
923           /* Print the whole category.  */
924           {
925             if (show_category_name != 0)
926               puts (category[cat_no].name);
927
928             for (item_no = 0; item_no < category[cat_no].number; ++item_no)
929               print_item (&category[cat_no].item_desc[item_no]);
930
931             return;
932           }
933
934         for (item_no = 0; item_no < category[cat_no].number; ++item_no)
935           if (strcmp (name, category[cat_no].item_desc[item_no].name) == 0)
936             {
937               if (show_category_name != 0)
938                 puts (category[cat_no].name);
939
940               print_item (&category[cat_no].item_desc[item_no]);
941               return;
942             }
943       }
944
945   /* The name is not a standard one.
946      For testing and perhaps advanced use allow some more symbols.  */
947   locale_special (name, show_category_name, show_keyword_name);
948 }