49b63363bacce1b106604fa2c61c72421d5be29b
[platform/upstream/glibc.git] / catgets / gencat.c
1 /* Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by Ulrich Drepper <drepper@redhat.com>, 1996.
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with the GNU C Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18    Boston, MA 02111-1307, USA.  */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <argp.h>
25 #include <assert.h>
26 #include <ctype.h>
27 #include <endian.h>
28 #include <errno.h>
29 #include <error.h>
30 #include <fcntl.h>
31 #include <iconv.h>
32 #include <langinfo.h>
33 #include <locale.h>
34 #include <libintl.h>
35 #include <limits.h>
36 #include <nl_types.h>
37 #include <obstack.h>
38 #include <stdint.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <wchar.h>
44
45 #include "version.h"
46
47 #include "catgetsinfo.h"
48
49
50 #define SWAPU32(w) \
51   (((w) << 24) | (((w) & 0xff00) << 8) | (((w) >> 8) & 0xff00) | ((w) >> 24))
52
53 struct message_list
54 {
55   int number;
56   const char *message;
57
58   const char *fname;
59   size_t line;
60   const char *symbol;
61
62   struct message_list *next;
63 };
64
65
66 struct set_list
67 {
68   int number;
69   int deleted;
70   struct message_list *messages;
71   int last_message;
72
73   const char *fname;
74   size_t line;
75   const char *symbol;
76
77   struct set_list *next;
78 };
79
80
81 struct catalog
82 {
83   struct set_list *all_sets;
84   struct set_list *current_set;
85   size_t total_messages;
86   wint_t quote_char;
87   int last_set;
88
89   struct obstack mem_pool;
90 };
91
92
93 /* If non-zero force creation of new file, not using existing one.  */
94 static int force_new;
95
96 /* Name of output file.  */
97 static const char *output_name;
98
99 /* Name of generated C header file.  */
100 static const char *header_name;
101
102 /* Name and version of program.  */
103 static void print_version (FILE *stream, struct argp_state *state);
104 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
105
106 #define OPT_NEW 1
107
108 /* Definitions of arguments for argp functions.  */
109 static const struct argp_option options[] =
110 {
111   { "header", 'H', N_("NAME"), 0,
112     N_("Create C header file NAME containing symbol definitions") },
113   { "new", OPT_NEW, NULL, 0,
114     N_("Do not use existing catalog, force new output file") },
115   { "output", 'o', N_("NAME"), 0, N_("Write output to file NAME") },
116   { NULL, 0, NULL, 0, NULL }
117 };
118
119 /* Short description of program.  */
120 static const char doc[] = N_("Generate message catalog.\
121 \vIf INPUT-FILE is -, input is read from standard input.  If OUTPUT-FILE\n\
122 is -, output is written to standard output.\n");
123
124 /* Strings for arguments in help texts.  */
125 static const char args_doc[] = N_("\
126 -o OUTPUT-FILE [INPUT-FILE]...\n[OUTPUT-FILE [INPUT-FILE]...]");
127
128 /* Prototype for option handler.  */
129 static error_t parse_opt (int key, char *arg, struct argp_state *state);
130
131 /* Function to print some extra text in the help message.  */
132 static char *more_help (int key, const char *text, void *input);
133
134 /* Data structure to communicate with argp functions.  */
135 static struct argp argp =
136 {
137   options, parse_opt, args_doc, doc, NULL, more_help
138 };
139
140
141 /* Wrapper functions with error checking for standard functions.  */
142 extern void *xmalloc (size_t n);
143 extern void *xcalloc (size_t n, size_t s);
144 extern void *xrealloc (void *o, size_t n);
145 extern char *xstrdup (const char *);
146
147 /* Prototypes for local functions.  */
148 static void error_print (void);
149 static struct catalog *read_input_file (struct catalog *current,
150                                         const char *fname);
151 static void write_out (struct catalog *result, const char *output_name,
152                        const char *header_name);
153 static struct set_list *find_set (struct catalog *current, int number);
154 static void normalize_line (const char *fname, size_t line, iconv_t cd,
155                             wchar_t *string, wchar_t quote_char,
156                             wchar_t escape_char);
157 static void read_old (struct catalog *catalog, const char *file_name);
158 static int open_conversion (const char *codesetp, iconv_t *cd_towcp,
159                             iconv_t *cd_tombp, wchar_t *escape_charp);
160
161
162 int
163 main (int argc, char *argv[])
164 {
165   struct catalog *result;
166   int remaining;
167
168   /* Set program name for messages.  */
169   error_print_progname = error_print;
170
171   /* Set locale via LC_ALL.  */
172   setlocale (LC_ALL, "");
173
174   /* Set the text message domain.  */
175   textdomain (PACKAGE);
176
177   /* Initialize local variables.  */
178   result = NULL;
179
180   /* Parse and process arguments.  */
181   argp_parse (&argp, argc, argv, 0, &remaining, NULL);
182
183   /* Determine output file.  */
184   if (output_name == NULL)
185     output_name = remaining < argc ? argv[remaining++] : "-";
186
187   /* Process all input files.  */
188   setlocale (LC_CTYPE, "C");
189   if (remaining < argc)
190     do
191       result = read_input_file (result, argv[remaining]);
192     while (++remaining < argc);
193   else
194     result = read_input_file (NULL, "-");
195
196   /* Write out the result.  */
197   if (result != NULL)
198     write_out (result, output_name, header_name);
199
200   return EXIT_SUCCESS;
201 }
202
203
204 /* Handle program arguments.  */
205 static error_t
206 parse_opt (int key, char *arg, struct argp_state *state)
207 {
208   switch (key)
209     {
210     case 'H':
211       header_name = arg;
212       break;
213     case OPT_NEW:
214       force_new = 1;
215       break;
216     case 'o':
217       output_name = arg;
218       break;
219     default:
220       return ARGP_ERR_UNKNOWN;
221     }
222   return 0;
223 }
224
225
226 static char *
227 more_help (int key, const char *text, void *input)
228 {
229   switch (key)
230     {
231     case ARGP_KEY_HELP_EXTRA:
232       /* We print some extra information.  */
233       return strdup (gettext ("\
234 Report bugs using the `glibcbug' script to <bugs@gnu.org>.\n"));
235     default:
236       break;
237     }
238   return (char *) text;
239 }
240
241 /* Print the version information.  */
242 static void
243 print_version (FILE *stream, struct argp_state *state)
244 {
245   fprintf (stream, "gencat (GNU %s) %s\n", PACKAGE, VERSION);
246   fprintf (stream, gettext ("\
247 Copyright (C) %s Free Software Foundation, Inc.\n\
248 This is free software; see the source for copying conditions.  There is NO\n\
249 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
250 "), "2000");
251   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
252 }
253
254
255 /* The address of this function will be assigned to the hook in the
256    error functions.  */
257 static void
258 error_print ()
259 {
260   /* We don't want the program name to be printed in messages.  Emacs'
261      compile.el does not like this.  */
262 }
263
264
265 static struct catalog *
266 read_input_file (struct catalog *current, const char *fname)
267 {
268   FILE *fp;
269   char *buf;
270   size_t len;
271   size_t line_number;
272   wchar_t *wbuf;
273   size_t wbufsize;
274   iconv_t cd_towc = (iconv_t) -1;
275   iconv_t cd_tomb = (iconv_t) -1;
276   wchar_t escape_char = L'\\';
277   char *codeset = NULL;
278
279   if (strcmp (fname, "-") == 0 || strcmp (fname, "/dev/stdin") == 0)
280     {
281       fp = stdin;
282       fname = gettext ("*standard input*");
283     }
284   else
285     fp = fopen (fname, "r");
286   if (fp == NULL)
287     {
288       error (0, errno, gettext ("cannot open input file `%s'"), fname);
289       return current;
290     }
291
292   /* If we haven't seen anything yet, allocate result structure.  */
293   if (current == NULL)
294     {
295       current = (struct catalog *) xcalloc (1, sizeof (*current));
296
297 #define obstack_chunk_alloc malloc
298 #define obstack_chunk_free free
299       obstack_init (&current->mem_pool);
300
301       current->current_set = find_set (current, NL_SETD);
302     }
303
304   buf = NULL;
305   len = 0;
306   line_number = 0;
307
308   wbufsize = 1024;
309   wbuf = (wchar_t *) xmalloc (wbufsize);
310
311   while (!feof (fp))
312     {
313       int continued;
314       int used;
315       size_t start_line = line_number + 1;
316       char *this_line;
317
318       do
319         {
320           int act_len;
321
322           act_len = getline (&buf, &len, fp);
323           if (act_len <= 0)
324             break;
325           ++line_number;
326
327           /* It the line continued?  */
328           if (buf[act_len - 1] == '\n')
329             {
330               --act_len;
331               continued = buf[act_len - 1] == '\\';
332               if (continued)
333                 --act_len;
334             }
335           else
336             continued = 0;
337
338           /* Append to currently selected line.  */
339           obstack_grow (&current->mem_pool, buf, act_len);
340         }
341       while (continued);
342
343       obstack_1grow (&current->mem_pool, '\0');
344       this_line = (char *) obstack_finish (&current->mem_pool);
345
346       used = 0;
347       if (this_line[0] == '$')
348         {
349           if (isblank (this_line[1]))
350             {
351               int cnt = 1;
352               while (isblank (this_line[cnt]))
353                 ++cnt;
354               if (strncmp (&this_line[cnt], "codeset=", 8) != 0)
355                 /* This is a comment line. Do nothing.  */;
356               else if (codeset != NULL)
357                 /* Ignore multiple codeset. */;
358               else
359                 {
360                   int start = cnt + 8;
361                   cnt = start;
362                   while (this_line[cnt] != '\0' && !isspace (this_line[cnt]))
363                     ++cnt;
364                   if (cnt != start)
365                     {
366                       int len = cnt - start;
367                       codeset = xmalloc (len + 1);
368                       *((char *) mempcpy (codeset, &this_line[start], len))
369                         = '\0';
370                     }
371                 }
372             }
373           else if (strncmp (&this_line[1], "set", 3) == 0)
374             {
375               int cnt = sizeof ("set");
376               int set_number;
377               const char *symbol = NULL;
378               while (isspace (this_line[cnt]))
379                 ++cnt;
380
381               if (isdigit (this_line[cnt]))
382                 {
383                   set_number = atol (&this_line[cnt]);
384
385                   /* If the given number for the character set is
386                      higher than any we used for symbolic set names
387                      avoid clashing by using only higher numbers for
388                      the following symbolic definitions.  */
389                   if (set_number > current->last_set)
390                     current->last_set = set_number;
391                 }
392               else
393                 {
394                   /* See whether it is a reasonable identifier.  */
395                   int start = cnt;
396                   while (isalnum (this_line[cnt]) || this_line[cnt] == '_')
397                     ++cnt;
398
399                   if (cnt == start)
400                     {
401                       /* No correct character found.  */
402                       error_at_line (0, 0, fname, start_line,
403                                      gettext ("illegal set number"));
404                       set_number = 0;
405                     }
406                   else
407                     {
408                       /* We have found seomthing that looks like a
409                          correct identifier.  */
410                       struct set_list *runp;
411
412                       this_line[cnt] = '\0';
413                       used = 1;
414                       symbol = &this_line[start];
415
416                       /* Test whether the identifier was already used.  */
417                       runp = current->all_sets;
418                       while (runp != 0)
419                         if (runp->symbol != NULL
420                             && strcmp (runp->symbol, symbol) == 0)
421                           break;
422                         else
423                           runp = runp->next;
424
425                       if (runp != NULL)
426                         {
427                           /* We cannot allow duplicate identifiers for
428                              message sets.  */
429                           error_at_line (0, 0, fname, start_line,
430                                          gettext ("duplicate set definition"));
431                           error_at_line (0, 0, runp->fname, runp->line,
432                                          gettext ("\
433 this is the first definition"));
434                           set_number = 0;
435                         }
436                       else
437                         /* Allocate next free message set for identifier.  */
438                         set_number = ++current->last_set;
439                     }
440                 }
441
442               if (set_number != 0)
443                 {
444                   /* We found a legal set number.  */
445                   current->current_set = find_set (current, set_number);
446                   if (symbol != NULL)
447                       used = 1;
448                   current->current_set->symbol = symbol;
449                   current->current_set->fname = fname;
450                   current->current_set->line = start_line;
451                 }
452             }
453           else if (strncmp (&this_line[1], "delset", 6) == 0)
454             {
455               int cnt = sizeof ("delset");
456               size_t set_number;
457               while (isspace (this_line[cnt]))
458                 ++cnt;
459
460               if (isdigit (this_line[cnt]))
461                 {
462                   size_t set_number = atol (&this_line[cnt]);
463                   struct set_list *set;
464
465                   /* Mark the message set with the given number as
466                      deleted.  */
467                   set = find_set (current, set_number);
468                   set->deleted = 1;
469                 }
470               else
471                 {
472                   /* See whether it is a reasonable identifier.  */
473                   int start = cnt;
474                   while (isalnum (this_line[cnt]) || this_line[cnt] == '_')
475                     ++cnt;
476
477                   if (cnt == start)
478                     {
479                       error_at_line (0, 0, fname, start_line,
480                                      gettext ("illegal set number"));
481                       set_number = 0;
482                     }
483                   else
484                     {
485                       const char *symbol;
486                       struct set_list *runp;
487
488                       this_line[cnt] = '\0';
489                       used = 1;
490                       symbol = &this_line[start];
491
492                       /* We have a symbolic set name.  This name must
493                          appear somewhere else in the catalogs read so
494                          far.  */
495                       set_number = 0;
496                       for (runp = current->all_sets; runp != NULL;
497                            runp = runp->next)
498                         {
499                           if (strcmp (runp->symbol, symbol) == 0)
500                             {
501                               runp->deleted = 1;
502                               break;
503                             }
504                         }
505                       if (runp == NULL)
506                         /* Name does not exist before.  */
507                         error_at_line (0, 0, fname, start_line,
508                                        gettext ("unknown set `%s'"), symbol);
509                     }
510                 }
511             }
512           else if (strncmp (&this_line[1], "quote", 5) == 0)
513             {
514               char buf[2];
515               char *bufptr;
516               size_t buflen;
517               char *wbufptr;
518               size_t wbuflen;
519               int cnt;
520
521               cnt = sizeof ("quote");
522               while (isspace (this_line[cnt]))
523                 ++cnt;
524
525               /* We need the conversion.  */
526               if (cd_towc == (iconv_t) -1
527                   && open_conversion (codeset, &cd_towc, &cd_tomb,
528                                       &escape_char) != 0)
529                 /* Something is wrong.  */
530                 goto out;
531
532               /* Yes, the quote char can be '\0'; this means no quote
533                  char.  The function using the information works on
534                  wide characters so we have to convert it here.  */
535               buf[0] = this_line[cnt];
536               buf[1] = '\0';
537               bufptr = buf;
538               buflen = 2;
539
540               wbufptr = (char *) wbuf;
541               wbuflen = wbufsize;
542
543               /* Flush the state.  */
544               iconv (cd_towc, NULL, NULL, NULL, NULL);
545
546               iconv (cd_towc, &bufptr, &buflen, &wbufptr, &wbuflen);
547               if (buflen != 0 || (wchar_t *) wbufptr != &wbuf[2])
548                 error_at_line (0, 0, fname, start_line,
549                                gettext ("invalid quote character"));
550               else
551                 /* Use the converted wide character.  */
552                 current->quote_char = wbuf[0];
553             }
554           else
555             {
556               int cnt;
557               cnt = 2;
558               while (this_line[cnt] != '\0' && !isspace (this_line[cnt]))
559                 ++cnt;
560               this_line[cnt] = '\0';
561               error_at_line (0, 0, fname, start_line,
562                              gettext ("unknown directive `%s': line ignored"),
563                              &this_line[1]);
564             }
565         }
566       else if (isalnum (this_line[0]) || this_line[0] == '_')
567         {
568           const char *ident = this_line;
569           int message_number;
570
571           do
572             ++this_line;
573           while (this_line[0] != '\0' && !isspace (this_line[0]));
574           if (this_line[0] != '\0')
575             *this_line++ = '\0';        /* Terminate the identifier.  */
576
577           /* Now we found the beginning of the message itself.  */
578
579           if (isdigit (ident[0]))
580             {
581               struct message_list *runp;
582               struct message_list *lastp;
583
584               message_number = atoi (ident);
585
586               /* Find location to insert the new message.  */
587               runp = current->current_set->messages;
588               lastp = NULL;
589               while (runp != NULL)
590                 if (runp->number == message_number)
591                   break;
592                 else
593                   {
594                     lastp = runp;
595                     runp = runp->next;
596                   }
597               if (runp != NULL)
598                 {
599                   /* Oh, oh.  There is already a message with this
600                      number in the message set.  */
601                   error_at_line (0, 0, fname, start_line,
602                                  gettext ("duplicated message number"));
603                   error_at_line (0, 0, runp->fname, runp->line,
604                                  gettext ("this is the first definition"));
605                   message_number = 0;
606                 }
607               ident = NULL;     /* We don't have a symbol.  */
608
609               if (message_number != 0
610                   && message_number > current->current_set->last_message)
611                 current->current_set->last_message = message_number;
612             }
613           else if (ident[0] != '\0')
614             {
615               struct message_list *runp;
616               struct message_list *lastp;
617
618               /* Test whether the symbolic name was not used for
619                  another message in this message set.  */
620               runp = current->current_set->messages;
621               lastp = NULL;
622               while (runp != NULL)
623                 if (runp->symbol != NULL && strcmp (ident, runp->symbol) == 0)
624                   break;
625                 else
626                   runp = runp->next;
627               if (runp != NULL)
628                 {
629                   /* The name is already used.  */
630                   error_at_line (0, 0, fname, start_line, gettext ("\
631 duplicated message identifier"));
632                   error_at_line (0, 0, runp->fname, runp->line,
633                                  gettext ("this is the first definition"));
634                   message_number = 0;
635                 }
636               else
637                 /* Give the message the next unused number.  */
638                 message_number = ++current->current_set->last_message;
639             }
640           else
641             message_number = 0;
642
643           if (message_number != 0)
644             {
645               char *inbuf;
646               size_t inlen;
647               char *outbuf;
648               size_t outlen;
649               struct message_list *newp;
650               size_t this_line_len = strlen (this_line) + 1;
651
652               /* We need the conversion.  */
653               if (cd_towc == (iconv_t) -1
654                   && open_conversion (codeset, &cd_towc, &cd_tomb,
655                                       &escape_char) != 0)
656                 /* Something is wrong.  */
657                 goto out;
658
659               /* Convert to a wide character string.  We have to
660                  interpret escape sequences which will be impossible
661                  without doing the conversion if the codeset of the
662                  message is stateful.  */
663               while (1)
664                 {
665                   inbuf = this_line;
666                   inlen = this_line_len;
667                   outbuf = (char *) wbuf;
668                   outlen = wbufsize;
669
670                   /* Flush the state.  */
671                   iconv (cd_towc, NULL, NULL, NULL, NULL);
672
673                   iconv (cd_towc, &inbuf, &inlen, &outbuf, &outlen);
674                   if (inlen == 0)
675                     {
676                       /* The string is converted.  */
677                       assert (outlen < wbufsize);
678                       assert (wbuf[(wbufsize - outlen) / sizeof (wchar_t) - 1]
679                               == L'\0');
680                       break;
681                     }
682
683                   if (outlen != 0)
684                     {
685                       /* Something is wrong with this string, we ignore it.  */
686                       error_at_line (0, 0, fname, start_line, gettext ("\
687 invalid character: message ignored"));
688                       goto ignore;
689                     }
690
691                   /* The output buffer is too small.  */
692                   wbufsize *= 2;
693                   wbuf = (wchar_t *) xrealloc (wbuf, wbufsize);
694                 }
695
696               used = 1; /* Yes, we use the line.  */
697
698               /* Strip quote characters, change escape sequences into
699                  correct characters etc.  */
700               normalize_line (fname, start_line, cd_towc, wbuf,
701                               current->quote_char, escape_char);
702
703               /* Now the string is free of escape sequences.  Convert it
704                  back into a multibyte character string.  First free the
705                  memory allocated for the original string.  */
706               obstack_free (&current->mem_pool, this_line);
707
708               /* Now fill in the new string.  It should never happen that
709                  the replaced string is longer than the original.  */
710               inbuf = (char *) wbuf;
711               inlen = (wcslen (wbuf) + 1) * sizeof (wchar_t);
712
713               outlen = obstack_room (&current->mem_pool);
714               start_line = (char *) obstack_alloc (&current->mem_pool, outlen);
715               outbuf = start_line;
716
717               /* Flush the state.  */
718               iconv (cd_tomb, NULL, NULL, NULL, NULL);
719
720               iconv (cd_tomb, &inbuf, &inlen, &outbuf, &outlen);
721               if (inlen != 0)
722                 {
723                   error_at_line (0, 0, fname, start_line,
724                                  gettext ("invalid line"));
725                   goto ignore;
726                 }
727               assert (outbuf[-1] == '\0');
728
729               /* Free the memory in the obstack we don't use.  */
730               obstack_free (&current->mem_pool, outbuf);
731
732               newp = (struct message_list *) xmalloc (sizeof (*newp));
733               newp->number = message_number;
734               newp->message = this_line;
735               /* Remember symbolic name; is NULL if no is given.  */
736               newp->symbol = ident;
737               /* Remember where we found the character.  */
738               newp->fname = fname;
739               newp->line = start_line;
740
741               /* Find place to insert to message.  We keep them in a
742                  sorted single linked list.  */
743               if (current->current_set->messages == NULL
744                   || current->current_set->messages->number > message_number)
745                 {
746                   newp->next = current->current_set->messages;
747                   current->current_set->messages = newp;
748                 }
749               else
750                 {
751                   struct message_list *runp;
752                   runp = current->current_set->messages;
753                   while (runp->next != NULL)
754                     if (runp->next->number > message_number)
755                       break;
756                     else
757                       runp = runp->next;
758                   newp->next = runp->next;
759                   runp->next = newp;
760                 }
761             }
762           ++current->total_messages;
763         }
764       else
765         {
766           size_t cnt;
767
768           cnt = 0;
769           /* See whether we have any non-white space character in this
770              line.  */
771           while (this_line[cnt] != '\0' && isspace (this_line[cnt]))
772             ++cnt;
773
774           if (this_line[cnt] != '\0')
775             /* Yes, some unknown characters found.  */
776             error_at_line (0, 0, fname, start_line,
777                            gettext ("malformed line ignored"));
778         }
779
780     ignore:
781       /* We can save the memory for the line if it was not used.  */
782       if (!used)
783         obstack_free (&current->mem_pool, this_line);
784     }
785
786   /* Close the conversion modules.  */
787   iconv_close (cd_towc);
788   iconv_close (cd_tomb);
789   free (codeset);
790
791  out:
792   free (wbuf);
793
794   if (fp != stdin)
795     fclose (fp);
796   return current;
797 }
798
799
800 static void
801 write_out (struct catalog *catalog, const char *output_name,
802            const char *header_name)
803 {
804   /* Computing the "optimal" size.  */
805   struct set_list *set_run;
806   size_t best_total, best_size, best_depth;
807   size_t act_size, act_depth;
808   struct catalog_obj obj;
809   struct obstack string_pool;
810   const char *strings;
811   size_t strings_size;
812   uint32_t *array1, *array2;
813   size_t cnt;
814   int fd;
815
816   /* If not otherwise told try to read file with existing
817      translations.  */
818   if (!force_new)
819     read_old (catalog, output_name);
820
821   /* Initialize best_size with a very high value.  */
822   best_total = best_size = best_depth = UINT_MAX;
823
824   /* We need some start size for testing.  Let's start with
825      TOTAL_MESSAGES / 5, which theoretically provides a mean depth of
826      5.  */
827   act_size = 1 + catalog->total_messages / 5;
828
829   /* We determine the size of a hash table here.  Because the message
830      numbers can be chosen arbitrary by the programmer we cannot use
831      the simple method of accessing the array using the message
832      number.  The algorithm is based on the trivial hash function
833      NUMBER % TABLE_SIZE, where collisions are stored in a second
834      dimension up to TABLE_DEPTH.  We here compute TABLE_SIZE so that
835      the needed space (= TABLE_SIZE * TABLE_DEPTH) is minimal.  */
836   while (act_size <= best_total)
837     {
838       size_t deep[act_size];
839
840       act_depth = 1;
841       memset (deep, '\0', act_size * sizeof (size_t));
842       set_run = catalog->all_sets;
843       while (set_run != NULL)
844         {
845           struct message_list *message_run;
846
847           message_run = set_run->messages;
848           while (message_run != NULL)
849             {
850               size_t idx = (message_run->number * set_run->number) % act_size;
851
852               ++deep[idx];
853               if (deep[idx] > act_depth)
854                 {
855                   act_depth = deep[idx];
856                   if (act_depth * act_size > best_total)
857                     break;
858                 }
859               message_run = message_run->next;
860             }
861           set_run = set_run->next;
862         }
863
864       if (act_depth * act_size <= best_total)
865         {
866           /* We have found a better solution.  */
867           best_total = act_depth * act_size;
868           best_size = act_size;
869           best_depth = act_depth;
870         }
871
872       ++act_size;
873     }
874
875   /* let's be prepared for an empty message file.  */
876   if (best_size == UINT_MAX)
877     {
878       best_size = 1;
879       best_depth = 1;
880     }
881
882   /* OK, now we have the size we will use.  Fill in the header, build
883      the table and the second one with swapped byte order.  */
884   obj.magic = CATGETS_MAGIC;
885   obj.plane_size = best_size;
886   obj.plane_depth = best_depth;
887
888   /* Allocate room for all needed arrays.  */
889   array1 =
890     (uint32_t *) alloca (best_size * best_depth * sizeof (uint32_t) * 3);
891   memset (array1, '\0', best_size * best_depth * sizeof (uint32_t) * 3);
892   array2
893     = (uint32_t *) alloca (best_size * best_depth * sizeof (uint32_t) * 3);
894   obstack_init (&string_pool);
895
896   set_run = catalog->all_sets;
897   while (set_run != NULL)
898     {
899       struct message_list *message_run;
900
901       message_run = set_run->messages;
902       while (message_run != NULL)
903         {
904           size_t idx = (((message_run->number * set_run->number) % best_size)
905                         * 3);
906           /* Determine collision depth.  */
907           while (array1[idx] != 0)
908             idx += best_size * 3;
909
910           /* Store set number, message number and pointer into string
911              space, relative to the first string.  */
912           array1[idx + 0] = set_run->number;
913           array1[idx + 1] = message_run->number;
914           array1[idx + 2] = obstack_object_size (&string_pool);
915
916           /* Add current string to the continuous space containing all
917              strings.  */
918           obstack_grow0 (&string_pool, message_run->message,
919                          strlen (message_run->message));
920
921           message_run = message_run->next;
922         }
923
924       set_run = set_run->next;
925     }
926   strings_size = obstack_object_size (&string_pool);
927   strings = obstack_finish (&string_pool);
928
929   /* Compute ARRAY2 by changing the byte order.  */
930   for (cnt = 0; cnt < best_size * best_depth * 3; ++cnt)
931     array2[cnt] = SWAPU32 (array1[cnt]);
932
933   /* Now we can write out the whole data.  */
934   if (strcmp (output_name, "-") == 0
935       || strcmp (output_name, "/dev/stdout") == 0)
936     fd = STDOUT_FILENO;
937   else
938     {
939       fd = creat (output_name, 0666);
940       if (fd < 0)
941         error (EXIT_FAILURE, errno, gettext ("cannot open output file `%s'"),
942                output_name);
943     }
944
945   /* Write out header.  */
946   write (fd, &obj, sizeof (obj));
947
948   /* We always write out the little endian version of the index
949      arrays.  */
950 #if __BYTE_ORDER == __LITTLE_ENDIAN
951   write (fd, array1, best_size * best_depth * sizeof (uint32_t) * 3);
952   write (fd, array2, best_size * best_depth * sizeof (uint32_t) * 3);
953 #elif __BYTE_ORDER == __BIG_ENDIAN
954   write (fd, array2, best_size * best_depth * sizeof (uint32_t) * 3);
955   write (fd, array1, best_size * best_depth * sizeof (uint32_t) * 3);
956 #else
957 # error Cannot handle __BYTE_ORDER byte order
958 #endif
959
960   /* Finally write the strings.  */
961   write (fd, strings, strings_size);
962
963   if (fd != STDOUT_FILENO)
964     close (fd);
965
966   /* If requested now write out the header file.  */
967   if (header_name != NULL)
968     {
969       int first = 1;
970       FILE *fp;
971
972       /* Open output file.  "-" or "/dev/stdout" means write to
973          standard output.  */
974       if (strcmp (header_name, "-") == 0
975           || strcmp (header_name, "/dev/stdout") == 0)
976         fp = stdout;
977       else
978         {
979           fp = fopen (header_name, "w");
980           if (fp == NULL)
981             error (EXIT_FAILURE, errno,
982                    gettext ("cannot open output file `%s'"), header_name);
983         }
984
985       /* Iterate over all sets and all messages.  */
986       set_run = catalog->all_sets;
987       while (set_run != NULL)
988         {
989           struct message_list *message_run;
990
991           /* If the current message set has a symbolic name write this
992              out first.  */
993           if (set_run->symbol != NULL)
994             fprintf (fp, "%s#define %sSet %#x\t/* %s:%Zu */\n",
995                      first ? "" : "\n", set_run->symbol, set_run->number - 1,
996                      set_run->fname, set_run->line);
997           first = 0;
998
999           message_run = set_run->messages;
1000           while (message_run != NULL)
1001             {
1002               /* If the current message has a symbolic name write
1003                  #define out.  But we have to take care for the set
1004                  not having a symbolic name.  */
1005               if (message_run->symbol != NULL)
1006                 {
1007                   if (set_run->symbol == NULL)
1008                     fprintf (fp, "#define AutomaticSet%d%s %#x\t/* %s:%Zu */\n",
1009                              set_run->number, message_run->symbol,
1010                              message_run->number, message_run->fname,
1011                              message_run->line);
1012                   else
1013                     fprintf (fp, "#define %s%s %#x\t/* %s:%Zu */\n",
1014                              set_run->symbol, message_run->symbol,
1015                              message_run->number, message_run->fname,
1016                              message_run->line);
1017                 }
1018
1019               message_run = message_run->next;
1020             }
1021
1022           set_run = set_run->next;
1023         }
1024
1025       if (fp != stdout)
1026         fclose (fp);
1027     }
1028 }
1029
1030
1031 static struct set_list *
1032 find_set (struct catalog *current, int number)
1033 {
1034   struct set_list *result = current->all_sets;
1035
1036   /* We must avoid set number 0 because a set of this number signals
1037      in the tables that the entry is not occupied.  */
1038   ++number;
1039
1040   while (result != NULL)
1041     if (result->number == number)
1042       return result;
1043     else
1044       result = result->next;
1045
1046   /* Prepare new message set.  */
1047   result = (struct set_list *) xcalloc (1, sizeof (*result));
1048   result->number = number;
1049   result->next = current->all_sets;
1050   current->all_sets = result;
1051
1052   return result;
1053 }
1054
1055
1056 /* Normalize given string *in*place* by processing escape sequences
1057    and quote characters.  */
1058 static void
1059 normalize_line (const char *fname, size_t line, iconv_t cd, wchar_t *string,
1060                 wchar_t quote_char, wchar_t escape_char)
1061 {
1062   int is_quoted;
1063   wchar_t *rp = string;
1064   wchar_t *wp = string;
1065
1066   if (quote_char != L'\0' && *rp == quote_char)
1067     {
1068       is_quoted = 1;
1069       ++rp;
1070     }
1071   else
1072     is_quoted = 0;
1073
1074   while (*rp != L'\0')
1075     if (*rp == quote_char)
1076       /* We simply end the string when we find the first time an
1077          not-escaped quote character.  */
1078         break;
1079     else if (*rp == escape_char)
1080       {
1081         ++rp;
1082         if (quote_char != L'\0' && *rp == quote_char)
1083           /* This is an extension to XPG.  */
1084           *wp++ = *rp++;
1085         else
1086           /* Recognize escape sequences.  */
1087           switch (*rp)
1088             {
1089             case L'n':
1090               *wp++ = L'\n';
1091               ++rp;
1092               break;
1093             case L't':
1094               *wp++ = L'\t';
1095               ++rp;
1096               break;
1097             case L'v':
1098               *wp++ = L'\v';
1099               ++rp;
1100               break;
1101             case L'b':
1102               *wp++ = L'\b';
1103               ++rp;
1104               break;
1105             case L'r':
1106               *wp++ = L'\r';
1107               ++rp;
1108               break;
1109             case L'f':
1110               *wp++ = L'\f';
1111               ++rp;
1112               break;
1113             case L'0' ... L'7':
1114               {
1115                 int number;
1116                 char cbuf[2];
1117                 char *cbufptr;
1118                 size_t cbufin;
1119                 wchar_t wcbuf[2];
1120                 char *wcbufptr;
1121                 size_t wcbufin;
1122
1123                 number = *rp++ - L'0';
1124                 while (number <= (255 / 8) && *rp >= L'0' && *rp <= L'7')
1125                   {
1126                     number *= 8;
1127                     number += *rp++ - L'0';
1128                   }
1129
1130                 cbuf[0] = (char) number;
1131                 cbuf[1] = '\0';
1132                 cbufptr = cbuf;
1133                 cbufin = 2;
1134
1135                 wcbufptr = (char *) wcbuf;
1136                 wcbufin = sizeof (wcbuf);
1137
1138                 /* Flush the state.  */
1139                 iconv (cd, NULL, NULL, NULL, NULL);
1140
1141                 iconv (cd, &cbufptr, &cbufin, &wcbufptr, &wcbufin);
1142                 if (cbufptr != &cbuf[2] || (wchar_t *) wcbufptr != &wcbuf[2])
1143                   error_at_line (0, 0, fname, line,
1144                                  gettext ("invalid escape sequence"));
1145                 else
1146                   *wp++ = wcbuf[0];
1147               }
1148               break;
1149             default:
1150               if (*rp == escape_char)
1151                 {
1152                   *wp++ = escape_char;
1153                   ++rp;
1154                 }
1155               else
1156                 /* Simply ignore the backslash character.  */;
1157               break;
1158             }
1159       }
1160     else
1161       *wp++ = *rp++;
1162
1163   /* If we saw a quote character at the beginning we expect another
1164      one at the end.  */
1165   if (is_quoted && *rp != quote_char)
1166     error_at_line (0, 0, fname, line, gettext ("unterminated message"));
1167
1168   /* Terminate string.  */
1169   *wp = L'\0';
1170   return;
1171 }
1172
1173
1174 static void
1175 read_old (struct catalog *catalog, const char *file_name)
1176 {
1177   struct catalog_info old_cat_obj;
1178   struct set_list *set = NULL;
1179   int last_set = -1;
1180   size_t cnt;
1181
1182   old_cat_obj.status = closed;
1183   old_cat_obj.cat_name = file_name;
1184   old_cat_obj.nlspath = NULL;
1185   __libc_lock_init (old_cat_obj.lock);
1186
1187   /* Try to open catalog, but don't look through the NLSPATH.  */
1188   __open_catalog (&old_cat_obj);
1189
1190   if (old_cat_obj.status != mmapped && old_cat_obj.status != malloced)
1191     {
1192       if (errno == ENOENT)
1193         /* No problem, the catalog simply does not exist.  */
1194         return;
1195       else
1196         error (EXIT_FAILURE, errno, gettext ("while opening old catalog file"));
1197     }
1198
1199   /* OK, we have the catalog loaded.  Now read all messages and merge
1200      them.  When set and message number clash for any message the new
1201      one is used.  If the new one is empty it indicates that the
1202      message should be deleted.  */
1203   for (cnt = 0; cnt < old_cat_obj.plane_size * old_cat_obj.plane_depth; ++cnt)
1204     {
1205       struct message_list *message, *last;
1206
1207       if (old_cat_obj.name_ptr[cnt * 3 + 0] == 0)
1208         /* No message in this slot.  */
1209         continue;
1210
1211       if (old_cat_obj.name_ptr[cnt * 3 + 0] - 1 != (uint32_t) last_set)
1212         {
1213           last_set = old_cat_obj.name_ptr[cnt * 3 + 0] - 1;
1214           set = find_set (catalog, old_cat_obj.name_ptr[cnt * 3 + 0] - 1);
1215         }
1216
1217       last = NULL;
1218       message = set->messages;
1219       while (message != NULL)
1220         {
1221           if ((uint32_t) message->number >= old_cat_obj.name_ptr[cnt * 3 + 1])
1222             break;
1223           last = message;
1224           message = message->next;
1225         }
1226
1227       if (message == NULL
1228           || (uint32_t) message->number > old_cat_obj.name_ptr[cnt * 3 + 1])
1229         {
1230           /* We have found a message which is not yet in the catalog.
1231              Insert it at the right position.  */
1232           struct message_list *newp;
1233
1234           newp = (struct message_list *) xmalloc (sizeof(*newp));
1235           newp->number = old_cat_obj.name_ptr[cnt * 3 + 1];
1236           newp->message =
1237             &old_cat_obj.strings[old_cat_obj.name_ptr[cnt * 3 + 2]];
1238           newp->fname = NULL;
1239           newp->line = 0;
1240           newp->symbol = NULL;
1241           newp->next = message;
1242
1243           if (last == NULL)
1244             set->messages = newp;
1245           else
1246             last->next = newp;
1247
1248           ++catalog->total_messages;
1249         }
1250       else if (*message->message == '\0')
1251         {
1252           /* The new empty message has overridden the old one thus
1253              "deleting" it as required.  Now remove the empty remains. */
1254           if (last == NULL)
1255             set->messages = message->next;
1256           else
1257             last->next = message->next;
1258         }
1259     }
1260 }
1261
1262
1263 static int
1264 open_conversion (const char *codeset, iconv_t *cd_towcp, iconv_t *cd_tombp,
1265                  wchar_t *escape_charp)
1266 {
1267   char buf[2];
1268   char *bufptr;
1269   size_t bufsize;
1270   wchar_t wbuf[2];
1271   char *wbufptr;
1272   size_t wbufsize;
1273
1274   /* If the input file does not specify the codeset use the locale's.  */
1275   if (codeset == NULL)
1276     {
1277       setlocale (LC_ALL, "");
1278       codeset = nl_langinfo (CODESET);
1279       setlocale (LC_ALL, "C");
1280     }
1281
1282   /* Get the conversion modules.  */
1283   *cd_towcp = iconv_open ("WCHAR_T", codeset);
1284   *cd_tombp = iconv_open (codeset, "WCHAR_T");
1285   if (*cd_towcp == (iconv_t) -1 || *cd_tombp == (iconv_t) -1)
1286     {
1287       error (0, 0, gettext ("conversion modules not available"));
1288       if (*cd_towcp != (iconv_t) -1)
1289         iconv_close (*cd_towcp);
1290
1291       return 1;
1292     }
1293
1294   /* One special case for historical reasons is the backslash
1295      character.  In some codesets the byte value 0x5c is not mapped to
1296      U005c in Unicode.  These charsets then don't have a backslash
1297      character at all.  Therefore we have to live with whatever the
1298      codeset provides and recognize, instead of the U005c, the character
1299      the byte value 0x5c is mapped to.  */
1300   buf[0] = '\\';
1301   buf[1] = '\0';
1302   bufptr = buf;
1303   bufsize = 2;
1304
1305   wbufptr = (char *) wbuf;
1306   wbufsize = sizeof (wbuf);
1307
1308   iconv (*cd_towcp, &bufptr, &bufsize, &wbufptr, &wbufsize);
1309   if (bufsize != 0 || wbufsize != 0)
1310     {
1311       /* Something went wrong, we couldn't convert the byte 0x5c.  Go
1312          on with using U005c.  */
1313       error (0, 0, gettext ("cannot determine escape character"));
1314       *escape_charp = L'\\';
1315     }
1316   else
1317     *escape_charp = wbuf[0];
1318
1319   return 0;
1320 }