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