update from main archive 970221
[kopensolaris-gnu/glibc.git] / locale / programs / locale.c
1 /* Implementation of the locale program according to POSIX 1003.2.
2    Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 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 Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    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    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the GNU C Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 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 <langinfo.h>
31 #include <libintl.h>
32 #include <limits.h>
33 #include <locale.h>
34 #include <search.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <sys/stat.h>
40
41 #include "localeinfo.h"
42
43
44 /* If set print the name of the category.  */
45 static int show_category_name;
46
47 /* If set print the name of the item.  */
48 static int show_keyword_name;
49
50 /* Print names of all available locales.  */
51 static int do_all;
52
53 /* Print names of all available character maps.  */
54 static int do_charmaps = 0;
55
56 /* Name and version of program.  */
57 static void print_version (FILE *stream, struct argp_state *state);
58 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
59
60 /* Definitions of arguments for argp functions.  */
61 static const struct argp_option options[] =
62 {
63   { NULL, 0, NULL, 0, N_("System information:") },
64   { "all-locales", 'a', NULL, OPTION_NO_USAGE,
65     N_("Write names of available locales") },
66   { "charmaps", 'm', NULL, OPTION_NO_USAGE,
67     N_("Write names of available charmaps") },
68   { NULL, 0, NULL, 0, N_("Modify output format:") },
69   { "category-name", 'c', NULL, 0, N_("Write names of selected categories") },
70   { "keyword-name", 'k', NULL, 0, N_("Write names of selected keywords") },
71   { NULL, 0, NULL, 0, NULL }
72 };
73
74 /* Short description of program.  */
75 static const char doc[] = N_("Get locale-specific information.");
76
77 /* Strings for arguments in help texts.  */
78 static const char args_doc[] = N_("NAME\n[-a|-m]");
79
80 /* Prototype for option handler.  */
81 static error_t parse_opt (int key, char *arg, struct argp_state *state);
82
83 /* Function to print some extra text in the help message.  */
84 static char *more_help (int key, const char *text, void *input);
85
86 /* Data structure to communicate with argp functions.  */
87 static struct argp argp =
88 {
89   options, parse_opt, args_doc, doc, NULL, more_help
90 };
91
92
93 /* We don't have these constants defined because we don't use them.  Give
94    default values.  */
95 #define CTYPE_MB_CUR_MIN 0
96 #define CTYPE_MB_CUR_MAX 0
97 #define CTYPE_HASH_SIZE 0
98 #define CTYPE_HASH_LAYERS 0
99 #define CTYPE_CLASS 0
100 #define CTYPE_TOUPPER_EB 0
101 #define CTYPE_TOLOWER_EB 0
102 #define CTYPE_TOUPPER_EL 0
103 #define CTYPE_TOLOWER_EL 0
104
105 /* Definition of the data structure which represents a category and its
106    items.  */
107 struct category
108 {
109   int cat_id;
110   const char *name;
111   size_t number;
112   struct cat_item
113   {
114     int item_id;
115     const char *name;
116     enum { std, opt } status;
117     enum value_type value_type;
118     int min;
119     int max;
120   } *item_desc;
121 };
122
123 /* Simple helper macro.  */
124 #define NELEMS(arr) ((sizeof (arr)) / (sizeof (arr[0])))
125
126 /* For some tricky stuff.  */
127 #define NO_PAREN(Item, More...) Item, ## More
128
129 /* We have all categories defined in `categories.def'.  Now construct
130    the description and data structure used for all categories.  */
131 #define DEFINE_ELEMENT(Item, More...) { Item, ## More },
132 #define DEFINE_CATEGORY(category, name, items, postload, in, check, out)      \
133     static struct cat_item category##_desc[] =                                \
134       {                                                                       \
135         NO_PAREN items                                                        \
136       };
137
138 #include "categories.def"
139 #undef DEFINE_CATEGORY
140
141 static struct category category[] =
142   {
143 #define DEFINE_CATEGORY(category, name, items, postload, in, check, out)      \
144     [category] = { _NL_NUM_##category, name, NELEMS (category##_desc),        \
145                    category##_desc },
146 #include "categories.def"
147 #undef DEFINE_CATEGORY
148   };
149 #define NCATEGORIES NELEMS (category)
150
151
152 /* Automatically set variable.  */
153 extern const char *__progname;
154
155 /* helper function for extended name handling.  */
156 extern void locale_special (const char *name, int show_category_name,
157                             int show_keyword_name);
158
159 /* Prototypes for local functions.  */
160 static void write_locales (void);
161 static void write_charmaps (void);
162 static void show_locale_vars (void);
163 static void show_info (const char *name);
164
165
166 int
167 main (int argc, char *argv[])
168 {
169   /* Set initial values for global variables.  */
170   show_category_name = 0;
171   show_keyword_name = 0;
172
173   /* Set locale.  Do not set LC_ALL because the other categories must
174      not be affected (according to POSIX.2).  */
175   setlocale (LC_CTYPE, "");
176   setlocale (LC_MESSAGES, "");
177
178   /* Initialize the message catalog.  */
179   textdomain (PACKAGE);
180
181   /* Parse and process arguments.  */
182   argp_parse (&argp, argc, argv, 0, 0, NULL);
183
184   /* `-a' requests the names of all available locales.  */
185   if (do_all != 0)
186     {
187       setlocale (LC_COLLATE, "");
188       write_locales ();
189       exit (EXIT_SUCCESS);
190     }
191
192   /* `m' requests the names of all available charmaps.  The names can be
193      used for the -f argument to localedef(3).  */
194   if (do_charmaps != 0)
195     {
196       write_charmaps ();
197       exit (EXIT_SUCCESS);
198     }
199
200   /* Specific information about the current locale are requested.
201      Change to this locale now.  */
202   setlocale (LC_ALL, "");
203
204   /* If no real argument is given we have to print the contents of the
205      current locale definition variables.  These are LANG and the LC_*.  */
206   if (optind == argc && show_keyword_name == 0 && show_category_name == 0)
207     {
208       show_locale_vars ();
209       exit (EXIT_SUCCESS);
210     }
211
212   /* Process all given names.  */
213   while (optind <  argc)
214     show_info (argv[optind++]);
215
216   exit (EXIT_SUCCESS);
217 }
218
219
220 /* Handle program arguments.  */
221 static error_t
222 parse_opt (int key, char *arg, struct argp_state *state)
223 {
224   switch (key)
225     {
226     case 'a':
227       do_all = 1;
228       break;
229     case 'c':
230       show_category_name = 1;
231       break;
232     case 'm':
233       do_charmaps = 1;
234       break;
235     case 'k':
236       show_keyword_name = 1;
237       break;
238     default:
239       return ARGP_ERR_UNKNOWN;
240     }
241   return 0;
242 }
243
244
245 static char *
246 more_help (int key, const char *text, void *input)
247 {
248   switch (key)
249     {
250     case ARGP_KEY_HELP_EXTRA:
251       /* We print some extra information.  */
252       return strdup (gettext ("\
253 Report bugs using the `glibcbug' script to <bugs@gnu.ai.mit.edu>.\n"));
254     default:
255       break;
256     }
257   return (char *) text;
258 }
259
260 /* Print the version information.  */
261 static void
262 print_version (FILE *stream, struct argp_state *state)
263 {
264   fprintf (stream, "locale (GNU %s) %s\n", PACKAGE, VERSION);
265   fprintf (stream, gettext ("\
266 Copyright (C) %s Free Software Foundation, Inc.\n\
267 This is free software; see the source for copying conditions.  There is NO\n\
268 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
269 "), "1995, 1996, 1997");
270   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
271 }
272
273
274 /* Simple action function which prints arguments as strings.  */
275 static void
276 print_names (const void *nodep, VISIT value, int level)
277 {
278   if (value == postorder || value == leaf)
279     puts (*(char **) nodep);
280 }
281
282
283 /* Write the names of all available locales to stdout.  We have some
284    sources of the information: the contents of the locale directory
285    and the locale.alias file.  To avoid duplicates and print the
286    result is a reasonable order we put all entries is a search tree
287    and print them afterwards.  */
288 static void
289 write_locales (void)
290 {
291   void *all_data = NULL;
292   DIR *dir;
293   struct dirent *dirent;
294   char *alias_path;
295   size_t alias_path_len;
296   char *entry;
297
298 #define PUT(name) tsearch ((name), &all_data, \
299                            (int (*) (const void *, const void *)) strcoll)
300
301   dir = opendir (LOCALEDIR);
302   if (dir == NULL)
303     {
304       error (1, errno, gettext ("cannot read locale directory `%s'"),
305              LOCALEDIR);
306       return;
307     }
308
309   /* `POSIX' locale is always available (POSIX.2 4.34.3).  */
310   PUT ("POSIX");
311   /* And so is the "C" locale.  */
312   PUT ("C");
313
314   /* Now we can look for all files in the directory.  */
315   while ((dirent = readdir (dir)) != NULL)
316     if (strcmp (dirent->d_name, ".") != 0
317         && strcmp (dirent->d_name, "..") != 0)
318       {
319         mode_t mode;
320 #ifdef _DIRENT_HAVE_D_TYPE
321         if (dirent->d_type != DT_UNKNOWN)
322           mode = DTTOIF (dirent->d_type);
323         else
324 #endif
325           {
326             struct stat st;
327             char buf[sizeof (LOCALEDIR) + strlen (dirent->d_name) + 1];
328
329             stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"), dirent->d_name);
330
331             if (stat (buf, &st) < 0)
332               continue;
333             mode = st.st_mode;
334           }
335
336         if (S_ISDIR (mode))
337           PUT (strdup (dirent->d_name));
338       }
339
340   closedir (dir);
341
342   /* Now read the locale.alias files.  */
343   if (argz_create_sep (LOCALE_ALIAS_PATH, ':', &alias_path, &alias_path_len))
344     error (1, errno, gettext ("while preparing output"));
345
346   entry = NULL;
347   while ((entry = argz_next (alias_path, alias_path_len, entry)))
348     {
349       static const char aliasfile[] = "/locale.alias";
350       FILE *fp;
351       char full_name[strlen (entry) + sizeof aliasfile];
352
353       stpcpy (stpcpy (full_name, entry), aliasfile);
354       fp = fopen (full_name, "r");
355       if (fp == NULL)
356         /* Ignore non-existing files.  */
357         continue;
358
359       while (! feof (fp))
360         {
361           /* It is a reasonable approach to use a fix buffer here
362              because
363              a) we are only interested in the first two fields
364              b) these fields must be usable as file names and so must
365                 not be that long  */
366           char buf[BUFSIZ];
367           char *alias;
368           char *value;
369           char *cp;
370
371           if (fgets (buf, BUFSIZ, fp) == NULL)
372             /* EOF reached.  */
373             break;
374
375           cp = buf;
376           /* Ignore leading white space.  */
377           while (isspace (cp[0]))
378             ++cp;
379
380           /* A leading '#' signals a comment line.  */
381           if (cp[0] != '\0' && cp[0] != '#')
382             {
383               alias = cp++;
384               while (cp[0] != '\0' && !isspace (cp[0]))
385                 ++cp;
386               /* Terminate alias name.  */
387               if (cp[0] != '\0')
388                 *cp++ = '\0';
389
390               /* Now look for the beginning of the value.  */
391               while (isspace (cp[0]))
392                 ++cp;
393
394               if (cp[0] != '\0')
395                 {
396                   value = cp++;
397                   while (cp[0] != '\0' && !isspace (cp[0]))
398                     ++cp;
399                   /* Terminate value.  */
400                   if (cp[0] == '\n')
401                     {
402                       /* This has to be done to make the following
403                          test for the end of line possible.  We are
404                          looking for the terminating '\n' which do not
405                          overwrite here.  */
406                       *cp++ = '\0';
407                       *cp = '\n';
408                     }
409                   else if (cp[0] != '\0')
410                     *cp++ = '\0';
411
412                   /* Add the alias.  */
413                   PUT (strdup (alias));
414                 }
415             }
416
417           /* Possibly not the whole line fits into the buffer.
418              Ignore the rest of the line.  */
419           while (strchr (cp, '\n') == NULL)
420             {
421               cp = buf;
422               if (fgets (buf, BUFSIZ, fp) == NULL)
423                 /* Make sure the inner loop will be left.  The outer
424                    loop will exit at the `feof' test.  */
425                 *cp = '\n';
426             }
427         }
428
429       fclose (fp);
430     }
431
432   twalk (all_data, print_names);
433 }
434
435
436 /* Write the names of all available character maps to stdout.  */
437 static void
438 write_charmaps (void)
439 {
440   void *all_data = NULL;
441   DIR *dir;
442   struct dirent *dirent;
443
444   dir = opendir (CHARMAP_PATH);
445   if (dir == NULL)
446     {
447       error (1, errno, gettext ("cannot read character map directory `%s'"),
448              CHARMAP_PATH);
449       return;
450     }
451
452   /* Now we can look for all files in the directory.  */
453   while ((dirent = readdir (dir)) != NULL)
454     if (strcmp (dirent->d_name, ".") != 0
455         && strcmp (dirent->d_name, "..") != 0)
456       {
457         mode_t mode;
458 #ifdef _DIRENT_HAVE_D_TYPE
459         if (dirent->d_type != DT_UNKNOWN)
460           mode = DTTOIF (dirent->d_type);
461         else
462 #endif
463           {
464             struct stat st;
465             char buf[sizeof (CHARMAP_PATH) + strlen (dirent->d_name) + 1];
466
467             stpcpy (stpcpy (stpcpy (buf, CHARMAP_PATH), "/"), dirent->d_name);
468
469             if (stat (buf, &st) < 0)
470               continue;
471             mode = st.st_mode;
472           }
473
474         if (S_ISREG (mode))
475           PUT (strdup (dirent->d_name));
476       }
477
478   closedir (dir);
479
480   twalk (all_data, print_names);
481 }
482
483
484 /* We have to show the contents of the environments determining the
485    locale.  */
486 static void
487 show_locale_vars (void)
488 {
489   size_t cat_no;
490   const char *lcall = getenv ("LC_ALL");
491   const char *lang = getenv ("LANG") ? : "POSIX";
492
493   void get_source (const char *name)
494     {
495       char *val = getenv (name);
496
497       if (lcall != NULL || val == NULL)
498         printf ("%s=\"%s\"\n", name, lcall ? : lang);
499       else
500         printf ("%s=%s\n", name, val);
501     }
502
503   /* LANG has to be the first value.  */
504   printf ("LANG=%s\n", lang);
505
506   /* Now all categories in an unspecified order.  */
507   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
508     get_source (category[cat_no].name);
509
510   /* The last is the LC_ALL value.  */
511   printf ("LC_ALL=%s\n", lcall ? : "");
512 }
513
514
515 /* Show the information request for NAME.  */
516 static void
517 show_info (const char *name)
518 {
519   size_t cat_no;
520
521   void print_item (struct cat_item *item)
522     {
523       if (show_keyword_name != 0)
524         printf ("%s=", item->name);
525
526       switch (item->value_type)
527         {
528         case string:
529           printf ("%s%s%s", show_keyword_name ? "\"" : "",
530                   nl_langinfo (item->item_id) ? : "",
531                   show_keyword_name ? "\"" : "");
532           break;
533         case stringarray:
534           {
535             int cnt;
536             const char *val;
537
538             if (show_keyword_name)
539               putchar ('"');
540
541             for (cnt = 0; cnt < item->max - 1; ++cnt)
542               {
543                 val = nl_langinfo (item->item_id + cnt);
544                 printf ("%s;", val ? : "");
545               }
546
547             val = nl_langinfo (item->item_id + cnt);
548             printf ("%s", val ? : "");
549
550             if (show_keyword_name)
551               putchar ('"');
552           }
553           break;
554         case byte:
555           {
556             const char *val = nl_langinfo (item->item_id);
557
558             if (val != NULL)
559               printf ("%d", *val == CHAR_MAX ? -1 : *val);
560           }
561           break;
562         case bytearray:
563           {
564             const char *val = nl_langinfo (item->item_id);
565             int cnt = val ? strlen (val) : 0;
566
567             while (cnt > 1)
568               {
569                 printf ("%d;", *val == CHAR_MAX ? -1 : *val);
570                 --cnt;
571                 ++val;
572               }
573
574             printf ("%d", cnt == 0 || *val == CHAR_MAX ? -1 : *val);
575           }
576           break;
577         case word:
578           {
579             unsigned int val = (unsigned int) nl_langinfo (item->item_id);
580             printf ("%d", val);
581           }
582           break;
583         default:
584         }
585       putchar ('\n');
586     }
587
588   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
589     {
590       size_t item_no;
591
592       if (strcmp (name, category[cat_no].name) == 0)
593         /* Print the whole category.  */
594         {
595           if (show_category_name != 0)
596             puts (category[cat_no].name);
597
598           for (item_no = 0; item_no < category[cat_no].number; ++item_no)
599             print_item (&category[cat_no].item_desc[item_no]);
600
601           return;
602         }
603
604       for (item_no = 0; item_no < category[cat_no].number; ++item_no)
605         if (strcmp (name, category[cat_no].item_desc[item_no].name) == 0)
606           {
607             if (show_category_name != 0)
608               puts (category[cat_no].name);
609
610             print_item (&category[cat_no].item_desc[item_no]);
611             return;
612           }
613     }
614
615   /* The name is not a standard one.
616      For testing and perhaps advanced use allow some more symbols.  */
617   locale_special (name, show_category_name, show_keyword_name);
618 }