(write_locales): Disable implicit locking for the stream.
[kopensolaris-gnu/glibc.git] / locale / programs / locale.c
1 /* Implementation of the locale program according to POSIX 9945-2.
2    Copyright (C) 1995-1997, 1999, 2000, 2001 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
47 extern void *xmalloc (size_t __n);
48 extern char *xstrdup (const char *__str);
49
50
51 /* If set print the name of the category.  */
52 static int show_category_name;
53
54 /* If set print the name of the item.  */
55 static int show_keyword_name;
56
57 /* Print names of all available locales.  */
58 static int do_all;
59
60 /* Print names of all available character maps.  */
61 static int do_charmaps = 0;
62
63 /* Nonzero if verbose output is wanted.  */
64 static int verbose;
65
66 /* Name and version of program.  */
67 static void print_version (FILE *stream, struct argp_state *state);
68 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
69
70 /* Definitions of arguments for argp functions.  */
71 static const struct argp_option options[] =
72 {
73   { NULL, 0, NULL, 0, N_("System information:") },
74   { "all-locales", 'a', NULL, OPTION_NO_USAGE,
75     N_("Write names of available locales") },
76   { "charmaps", 'm', NULL, OPTION_NO_USAGE,
77     N_("Write names of available charmaps") },
78   { NULL, 0, NULL, 0, N_("Modify output format:") },
79   { "category-name", 'c', NULL, 0, N_("Write names of selected categories") },
80   { "keyword-name", 'k', NULL, 0, N_("Write names of selected keywords") },
81   { "verbose", 'v', NULL, 0, N_("Print more information") },
82   { NULL, 0, NULL, 0, NULL }
83 };
84
85 /* Short description of program.  */
86 static const char doc[] = N_("Get locale-specific information.");
87
88 /* Strings for arguments in help texts.  */
89 static const char args_doc[] = N_("NAME\n[-a|-m]");
90
91 /* Prototype for option handler.  */
92 static error_t parse_opt (int key, char *arg, struct argp_state *state);
93
94 /* Function to print some extra text in the help message.  */
95 static char *more_help (int key, const char *text, void *input);
96
97 /* Data structure to communicate with argp functions.  */
98 static struct argp argp =
99 {
100   options, parse_opt, args_doc, doc, NULL, more_help
101 };
102
103
104 /* We don't have these constants defined because we don't use them.  Give
105    default values.  */
106 #define CTYPE_MB_CUR_MIN 0
107 #define CTYPE_MB_CUR_MAX 0
108 #define CTYPE_HASH_SIZE 0
109 #define CTYPE_HASH_LAYERS 0
110 #define CTYPE_CLASS 0
111 #define CTYPE_TOUPPER_EB 0
112 #define CTYPE_TOLOWER_EB 0
113 #define CTYPE_TOUPPER_EL 0
114 #define CTYPE_TOLOWER_EL 0
115
116 /* Definition of the data structure which represents a category and its
117    items.  */
118 struct category
119 {
120   int cat_id;
121   const char *name;
122   size_t number;
123   struct cat_item
124   {
125     int item_id;
126     const char *name;
127     enum { std, opt } status;
128     enum value_type value_type;
129     int min;
130     int max;
131   } *item_desc;
132 };
133
134 /* Simple helper macro.  */
135 #define NELEMS(arr) ((sizeof (arr)) / (sizeof (arr[0])))
136
137 /* For some tricky stuff.  */
138 #define NO_PAREN(Item, More...) Item, ## More
139
140 /* We have all categories defined in `categories.def'.  Now construct
141    the description and data structure used for all categories.  */
142 #define DEFINE_ELEMENT(Item, More...) { Item, ## More },
143 #define DEFINE_CATEGORY(category, name, items, postload) \
144     static struct cat_item category##_desc[] =                                \
145       {                                                                       \
146         NO_PAREN items                                                        \
147       };
148
149 #include "categories.def"
150 #undef DEFINE_CATEGORY
151
152 static struct category category[] =
153   {
154 #define DEFINE_CATEGORY(category, name, items, postload) \
155     [category] = { _NL_NUM_##category, name, NELEMS (category##_desc),        \
156                    category##_desc },
157 #include "categories.def"
158 #undef DEFINE_CATEGORY
159   };
160 #define NCATEGORIES NELEMS (category)
161
162
163 /* Automatically set variable.  */
164 extern const char *__progname;
165
166 /* helper function for extended name handling.  */
167 extern void locale_special (const char *name, int show_category_name,
168                             int show_keyword_name);
169
170 /* Prototypes for local functions.  */
171 static void write_locales (void);
172 static void write_charmaps (void);
173 static void show_locale_vars (void);
174 static void show_info (const char *name);
175
176
177 int
178 main (int argc, char *argv[])
179 {
180   int remaining;
181
182   /* Set initial values for global variables.  */
183   show_category_name = 0;
184   show_keyword_name = 0;
185
186   /* Set locale.  Do not set LC_ALL because the other categories must
187      not be affected (according to POSIX.2).  */
188   setlocale (LC_CTYPE, "");
189   setlocale (LC_MESSAGES, "");
190
191   /* Initialize the message catalog.  */
192   textdomain (PACKAGE);
193
194   /* Parse and process arguments.  */
195   argp_parse (&argp, argc, argv, 0, &remaining, NULL);
196
197   /* `-a' requests the names of all available locales.  */
198   if (do_all != 0)
199     {
200       setlocale (LC_COLLATE, "");
201       write_locales ();
202       exit (EXIT_SUCCESS);
203     }
204
205   /* `m' requests the names of all available charmaps.  The names can be
206      used for the -f argument to localedef(1).  */
207   if (do_charmaps != 0)
208     {
209       write_charmaps ();
210       exit (EXIT_SUCCESS);
211     }
212
213   /* Specific information about the current locale are requested.
214      Change to this locale now.  */
215   setlocale (LC_ALL, "");
216
217   /* If no real argument is given we have to print the contents of the
218      current locale definition variables.  These are LANG and the LC_*.  */
219   if (remaining == argc && show_keyword_name == 0 && show_category_name == 0)
220     {
221       show_locale_vars ();
222       exit (EXIT_SUCCESS);
223     }
224
225   /* Process all given names.  */
226   while (remaining <  argc)
227     show_info (argv[remaining++]);
228
229   exit (EXIT_SUCCESS);
230 }
231
232
233 /* Handle program arguments.  */
234 static error_t
235 parse_opt (int key, char *arg, struct argp_state *state)
236 {
237   switch (key)
238     {
239     case 'a':
240       do_all = 1;
241       break;
242     case 'c':
243       show_category_name = 1;
244       break;
245     case 'm':
246       do_charmaps = 1;
247       break;
248     case 'k':
249       show_keyword_name = 1;
250       break;
251     case 'v':
252       verbose = 1;
253       break;
254     default:
255       return ARGP_ERR_UNKNOWN;
256     }
257   return 0;
258 }
259
260
261 static char *
262 more_help (int key, const char *text, void *input)
263 {
264   switch (key)
265     {
266     case ARGP_KEY_HELP_EXTRA:
267       /* We print some extra information.  */
268       return xstrdup (gettext ("\
269 Report bugs using the `glibcbug' script to <bugs@gnu.org>.\n"));
270     default:
271       break;
272     }
273   return (char *) text;
274 }
275
276 /* Print the version information.  */
277 static void
278 print_version (FILE *stream, struct argp_state *state)
279 {
280   fprintf (stream, "locale (GNU %s) %s\n", PACKAGE, VERSION);
281   fprintf (stream, gettext ("\
282 Copyright (C) %s Free Software Foundation, Inc.\n\
283 This is free software; see the source for copying conditions.  There is NO\n\
284 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
285 "), "2001");
286   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
287 }
288
289
290 /* Simple action function which prints arguments as strings.  */
291 static void
292 print_names (const void *nodep, VISIT value, int level)
293 {
294   if (value == postorder || value == leaf)
295     puts (*(char **) nodep);
296 }
297
298
299 static int
300 select_dirs (const struct dirent *dirent)
301 {
302   int result = 0;
303
304   if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0)
305     {
306       mode_t mode = 0;
307
308 #ifdef _DIRENT_HAVE_D_TYPE
309       if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK)
310         mode = DTTOIF (dirent->d_type);
311       else
312 #endif
313         {
314           struct stat64 st;
315           char buf[sizeof (LOCALEDIR) + strlen (dirent->d_name) + 1];
316
317           stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"), dirent->d_name);
318
319           if (stat64 (buf, &st) == 0)
320             mode = st.st_mode;
321         }
322
323       result = S_ISDIR (mode);
324     }
325
326   return result;
327 }
328
329
330 /* Write the names of all available locales to stdout.  We have some
331    sources of the information: the contents of the locale directory
332    and the locale.alias file.  To avoid duplicates and print the
333    result is a reasonable order we put all entries is a search tree
334    and print them afterwards.  */
335 static void
336 write_locales (void)
337 {
338   char linebuf[80];
339   void *all_data = NULL;
340   struct dirent **dirents;
341   int ndirents;
342   int cnt;
343   char *alias_path;
344   size_t alias_path_len;
345   char *entry;
346   int first_locale = 1;
347
348 #define PUT(name) tsearch (name, &all_data, \
349                            (int (*) (const void *, const void *)) strcoll)
350
351   /* Now read the locale.alias files.  */
352   if (argz_create_sep (LOCALE_ALIAS_PATH, ':', &alias_path, &alias_path_len))
353     error (1, errno, gettext ("while preparing output"));
354
355   entry = NULL;
356   while ((entry = argz_next (alias_path, alias_path_len, entry)))
357     {
358       static const char aliasfile[] = "/locale.alias";
359       FILE *fp;
360       char full_name[strlen (entry) + sizeof aliasfile];
361
362       stpcpy (stpcpy (full_name, entry), aliasfile);
363       fp = fopen (full_name, "r");
364       if (fp == NULL)
365         /* Ignore non-existing files.  */
366         continue;
367
368       /* No threads present.  */
369       __fsetlocking (fp, FSETLOCKING_BYCALLER);
370
371       while (! feof_unlocked (fp))
372         {
373           /* It is a reasonable approach to use a fix buffer here
374              because
375              a) we are only interested in the first two fields
376              b) these fields must be usable as file names and so must
377                 not be that long  */
378           char buf[BUFSIZ];
379           char *alias;
380           char *value;
381           char *cp;
382
383           if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
384             /* EOF reached.  */
385             break;
386
387           cp = buf;
388           /* Ignore leading white space.  */
389           while (isspace (cp[0]) && cp[0] != '\n')
390             ++cp;
391
392           /* A leading '#' signals a comment line.  */
393           if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
394             {
395               alias = cp++;
396               while (cp[0] != '\0' && !isspace (cp[0]))
397                 ++cp;
398               /* Terminate alias name.  */
399               if (cp[0] != '\0')
400                 *cp++ = '\0';
401
402               /* Now look for the beginning of the value.  */
403               while (isspace (cp[0]))
404                 ++cp;
405
406               if (cp[0] != '\0')
407                 {
408                   value = cp++;
409                   while (cp[0] != '\0' && !isspace (cp[0]))
410                     ++cp;
411                   /* Terminate value.  */
412                   if (cp[0] == '\n')
413                     {
414                       /* This has to be done to make the following
415                          test for the end of line possible.  We are
416                          looking for the terminating '\n' which do not
417                          overwrite here.  */
418                       *cp++ = '\0';
419                       *cp = '\n';
420                     }
421                   else if (cp[0] != '\0')
422                     *cp++ = '\0';
423
424                   /* Add the alias.  */
425                   if (! verbose)
426                     PUT (xstrdup (alias));
427                 }
428             }
429
430           /* Possibly not the whole line fits into the buffer.
431              Ignore the rest of the line.  */
432           while (strchr (cp, '\n') == NULL)
433             {
434               cp = buf;
435               if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
436                 /* Make sure the inner loop will be left.  The outer
437                    loop will exit at the `feof' test.  */
438                 *cp = '\n';
439             }
440         }
441
442       fclose (fp);
443     }
444
445   memset (linebuf, '-', sizeof (linebuf) - 1);
446   linebuf[sizeof (linebuf) - 1] = '\0';
447
448   /* Now we can look for all files in the directory.  */
449   ndirents = scandir (LOCALEDIR, &dirents, select_dirs, alphasort);
450   for (cnt = 0; cnt < ndirents; ++cnt)
451     {
452       /* Test whether at least the LC_CTYPE data is there.  Some
453          directories only contain translations.  */
454       char buf[sizeof (LOCALEDIR) + strlen (dirents[cnt]->d_name)
455               + sizeof "/LC_IDENTIFICATION"];
456       char *enddir;
457       struct stat64 st;
458
459       stpcpy (enddir = stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"),
460                                dirents[cnt]->d_name),
461               "/LC_IDENTIFICATION");
462
463       if (stat64 (buf, &st) == 0 && S_ISREG (st.st_mode))
464         {
465           if (verbose)
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                       /* Read the information from the file.  */
487                       struct
488                       {
489                         unsigned int magic;
490                         unsigned int nstrings;
491                         unsigned int strindex[0];
492                       } *filedata = mapped;
493
494                       if (filedata->magic == LIMAGIC (LC_IDENTIFICATION)
495                           && (sizeof *filedata
496                               + (filedata->nstrings
497                                  * sizeof (unsigned int))
498                               <= (size_t) st.st_size))
499                         {
500                           const char *str;
501
502 #define HANDLE(idx, name) \
503   str = ((char *) mapped                                                      \
504          + filedata->strindex[_NL_ITEM_INDEX (_NL_IDENTIFICATION_##idx)]);    \
505   if (*str != '\0')                                                           \
506     printf ("%9s | %s\n", name, str)
507                           HANDLE (TITLE, "title");
508                           HANDLE (SOURCE, "source");
509                           HANDLE (ADDRESS, "address");
510                           HANDLE (CONTACT, "contact");
511                           HANDLE (EMAIL, "email");
512                           HANDLE (TEL, "telephone");
513                           HANDLE (FAX, "fax");
514                           HANDLE (LANGUAGE, "language");
515                           HANDLE (TERRITORY, "territory");
516                           HANDLE (AUDIENCE, "audience");
517                           HANDLE (APPLICATION, "application");
518                           HANDLE (ABBREVIATION, "abbreviation");
519                           HANDLE (REVISION, "revision");
520                           HANDLE (DATE, "date");
521                         }
522
523                       munmap (mapped, st.st_size);
524                     }
525
526                   close (fd);
527
528                   /* Now try to get the charset information.  */
529                   strcpy (enddir, "/LC_CTYPE");
530                   fd = open64 (buf, O_RDONLY);
531                   if (fd != -1 && fstat64 (fd, &st) >= 0
532                       && ((mapped = mmap64 (NULL, st.st_size, PROT_READ,
533                                             MAP_SHARED, fd, 0))
534                           != MAP_FAILED))
535                     {
536                       struct
537                       {
538                         unsigned int magic;
539                         unsigned int nstrings;
540                         unsigned int strindex[0];
541                       } *filedata = mapped;
542
543                       if (filedata->magic == LIMAGIC (LC_CTYPE)
544                           && (sizeof *filedata
545                               + (filedata->nstrings
546                                  * sizeof (unsigned int))
547                               <= (size_t) st.st_size))
548                         {
549                           const char *str;
550
551                           str = ((char *) mapped
552                                  + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)]);
553                           if (*str != '\0')
554                             printf ("  codeset | %s\n", str);
555                         }
556
557                       munmap (mapped, st.st_size);
558                     }
559
560                   if (fd != -1)
561                     close (fd);
562                 }
563             }
564           else
565             /* If the verbose format is not selected we simply
566                collect the names.  */
567             PUT (xstrdup (dirents[cnt]->d_name));
568         }
569     }
570   if (ndirents > 0)
571     free (dirents);
572
573   if (! verbose)
574     {
575       /* `POSIX' locale is always available (POSIX.2 4.34.3).  */
576       PUT ("POSIX");
577       /* And so is the "C" locale.  */
578       PUT ("C");
579
580       twalk (all_data, print_names);
581     }
582 }
583
584
585 /* Write the names of all available character maps to stdout.  */
586 static void
587 write_charmaps (void)
588 {
589   void *all_data = NULL;
590   CHARMAP_DIR *dir;
591   const char *dirent;
592
593   /* Look for all files in the charmap directory.  */
594   dir = charmap_opendir (CHARMAP_PATH);
595   if (dir == NULL)
596     return;
597
598   while ((dirent = charmap_readdir (dir)) != NULL)
599     {
600       char **aliases;
601       char **p;
602
603       PUT (xstrdup (dirent));
604
605       aliases = charmap_aliases (CHARMAP_PATH, dirent);
606
607 #if 0
608       /* Add the code_set_name and the aliases.  */
609       for (p = aliases; *p; p++)
610         PUT (xstrdup (*p));
611 #else
612       /* Add the code_set_name only.  Most aliases are obsolete.  */
613       p = aliases;
614       if (*p)
615         PUT (xstrdup (*p));
616 #endif
617
618       charmap_free_aliases (aliases);
619     }
620
621   charmap_closedir (dir);
622
623   twalk (all_data, print_names);
624 }
625
626
627 /* We have to show the contents of the environments determining the
628    locale.  */
629 static void
630 show_locale_vars (void)
631 {
632   size_t cat_no;
633   const char *lcall = getenv ("LC_ALL");
634   const char *lang = getenv ("LANG") ? : "POSIX";
635
636   auto void get_source (const char *name);
637
638   void get_source (const char *name)
639     {
640       char *val = getenv (name);
641
642       if ((lcall ?: "")[0] != '\0' || val == NULL)
643         printf ("%s=\"%s\"\n", name, (lcall ?: "")[0] ? lcall : lang);
644       else
645         printf ("%s=%s\n", name, val);
646     }
647
648   /* LANG has to be the first value.  */
649   printf ("LANG=%s\n", lang);
650
651   /* Now all categories in an unspecified order.  */
652   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
653     if (cat_no != LC_ALL)
654       get_source (category[cat_no].name);
655
656   /* The last is the LC_ALL value.  */
657   printf ("LC_ALL=%s\n", lcall ? : "");
658 }
659
660
661 /* Show the information request for NAME.  */
662 static void
663 show_info (const char *name)
664 {
665   size_t cat_no;
666
667   auto void print_item (struct cat_item *item);
668
669   void print_item (struct cat_item *item)
670     {
671       switch (item->value_type)
672         {
673         case string:
674           if (show_keyword_name)
675             printf ("%s=\"", item->name);
676           fputs (nl_langinfo (item->item_id) ? : "", stdout);
677           if (show_keyword_name)
678             putchar ('"');
679           putchar ('\n');
680           break;
681         case stringarray:
682           {
683             int cnt;
684             const char *val;
685
686             if (show_keyword_name)
687               printf ("%s=\"", item->name);
688
689             for (cnt = 0; cnt < item->max - 1; ++cnt)
690               {
691                 val = nl_langinfo (item->item_id + cnt);
692                 if (val != NULL)
693                   fputs (val, stdout);
694                 putchar (';');
695               }
696
697             val = nl_langinfo (item->item_id + cnt);
698             if (val != NULL)
699               fputs (val, stdout);
700
701             if (show_keyword_name)
702               putchar ('"');
703             putchar ('\n');
704           }
705           break;
706         case stringlist:
707           {
708             int first = 1;
709             const char *val = nl_langinfo (item->item_id) ? : "";
710             int cnt;
711
712             if (show_keyword_name)
713               printf ("%s=", item->name);
714
715             for (cnt = 0; cnt < item->max && *val != '\0'; ++cnt)
716               {
717                 printf ("%s%s%s%s", first ? "" : ";",
718                         show_keyword_name ? "\"" : "", val,
719                         show_keyword_name ? "\"" : "");
720                 val = strchr (val, '\0') + 1;
721                 first = 0;
722               }
723             putchar ('\n');
724           }
725           break;
726         case byte:
727           {
728             const char *val = nl_langinfo (item->item_id);
729
730             if (show_keyword_name)
731               printf ("%s=", item->name);
732
733             if (val != NULL)
734               printf ("%d", *val == CHAR_MAX ? -1 : *val);
735             putchar ('\n');
736           }
737           break;
738         case bytearray:
739           {
740             const char *val = nl_langinfo (item->item_id);
741             int cnt = val ? strlen (val) : 0;
742
743             if (show_keyword_name)
744               printf ("%s=", item->name);
745
746             while (cnt > 1)
747               {
748                 printf ("%d;", *val == CHAR_MAX ? -1 : *val);
749                 --cnt;
750                 ++val;
751               }
752
753             printf ("%d\n", cnt == 0 || *val == CHAR_MAX ? -1 : *val);
754           }
755           break;
756         case word:
757           {
758             unsigned int val =
759               (unsigned int) (unsigned long int) nl_langinfo (item->item_id);
760             if (show_keyword_name)
761               printf ("%s=", item->name);
762
763             printf ("%d\n", val);
764           }
765           break;
766         case wstring:
767         case wstringarray:
768         case wstringlist:
769           /* We don't print wide character information since the same
770              information is available in a multibyte string.  */
771         default:
772           break;
773
774         }
775     }
776
777   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
778     if (cat_no != LC_ALL)
779       {
780         size_t item_no;
781
782         if (strcmp (name, category[cat_no].name) == 0)
783           /* Print the whole category.  */
784           {
785             if (show_category_name != 0)
786               puts (category[cat_no].name);
787
788             for (item_no = 0; item_no < category[cat_no].number; ++item_no)
789               print_item (&category[cat_no].item_desc[item_no]);
790
791             return;
792           }
793
794         for (item_no = 0; item_no < category[cat_no].number; ++item_no)
795           if (strcmp (name, category[cat_no].item_desc[item_no].name) == 0)
796             {
797               if (show_category_name != 0)
798                 puts (category[cat_no].name);
799
800               print_item (&category[cat_no].item_desc[item_no]);
801               return;
802             }
803       }
804
805   /* The name is not a standard one.
806      For testing and perhaps advanced use allow some more symbols.  */
807   locale_special (name, show_category_name, show_keyword_name);
808 }