1 /* Copyright (C) 1996 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper, <drepper@gnu.ai.mit.edu>.
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.
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.
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
17 not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA. */
42 #include "catgetsinfo.h"
46 (((w) << 24) | (((w) & 0xff00) << 8) | (((w) >> 8) & 0xff00) | ((w) >> 24))
57 struct message_list
*next
;
65 struct message_list
*messages
;
72 struct set_list
*next
;
78 struct set_list
*all_sets
;
79 struct set_list
*current_set
;
80 size_t total_messages
;
84 struct obstack mem_pool
;
88 /* If non-zero force creation of new file, not using existing one. */
92 static const struct option long_options
[] =
94 { "header", required_argument
, NULL
, 'H' },
95 { "help", no_argument
, NULL
, 'h' },
96 { "new", no_argument
, &force_new
, 1 },
97 { "output", required_argument
, NULL
, 'o' },
98 { "version", no_argument
, NULL
, 'V' },
102 /* Wrapper functions with error checking for standard functions. */
103 extern void *xmalloc (size_t n
);
105 /* Prototypes for local functions. */
106 static void usage (int status
) __attribute__ ((noreturn
));
107 static void error_print (void);
108 static struct catalog
*read_input_file (struct catalog
*current
,
110 static void write_out (struct catalog
*result
, const char *output_name
,
111 const char *header_name
);
112 static struct set_list
*find_set (struct catalog
*current
, int number
);
113 static void normalize_line (const char *fname
, size_t line
, char *string
,
115 static void read_old (struct catalog
*catalog
, const char *file_name
);
119 main (int argc
, char *argv
[])
121 struct catalog
*result
;
122 const char *output_name
;
123 const char *header_name
;
128 /* Set program name for messages. */
129 error_print_progname
= error_print
;
131 /* Set locale via LC_ALL. */
132 setlocale (LC_ALL
, "");
134 /* Set the text message domain. */
135 textdomain (PACKAGE
);
137 /* Initialize local variables. */
144 while ((opt
= getopt_long (argc
, argv
, "hH:o:V", long_options
, NULL
)) != EOF
)
147 case '\0': /* Long option. */
153 header_name
= optarg
;
156 output_name
= optarg
;
162 usage (EXIT_FAILURE
);
165 /* Version information is requested. */
168 fprintf (stderr
, "%s - GNU %s %s\n", program_invocation_name
,
173 /* Help is requested. */
175 usage (EXIT_SUCCESS
);
177 /* Determine output file. */
178 if (output_name
== NULL
)
179 output_name
= optind
< argc
? argv
[optind
++] : "-";
181 /* Process all input files. */
182 setlocale (LC_CTYPE
, "C");
185 result
= read_input_file (result
, argv
[optind
]);
186 while (++optind
< argc
);
188 result
= read_input_file (NULL
, "-");
190 /* Write out the result. */
192 write_out (result
, output_name
, header_name
);
201 if (status
!= EXIT_SUCCESS
)
202 fprintf (stderr
, gettext ("Try `%s --help' for more information.\n"),
203 program_invocation_name
);
207 Usage: %s [OPTION]... -o OUTPUT-FILE [INPUT-FILE]...\n\
208 %s [OPTION]... [OUTPUT-FILE [INPUT-FILE]...]\n\
209 Mandatory arguments to long options are mandatory for short options too.\n\
210 -H, --header create C header file containing symbol definitions\n\
211 -h, --help display this help and exit\n\
212 --new do not use existing catalog, force new output file\n\
213 -o, --output=NAME write output to file NAME\n\
214 -V, --version output version information and exit\n\
215 If INPUT-FILE is -, input is read from standard input. If OUTPUT-FILE\n\
216 is -, output is written to standard output.\n"),
217 program_invocation_name
, program_invocation_name
);
218 printf (gettext ("Report bugs to <bug-glibc@prep.ai.mit.edu>.\n"));
225 /* The address of this function will be assigned to the hook in the
230 /* We don't want the program name to be printed in messages. Emacs'
231 compile.el does not like this. */
235 static struct catalog
*
236 read_input_file (struct catalog
*current
, const char *fname
)
243 if (strcmp (fname
, "-") == 0 || strcmp (fname
, "/dev/stdin") == 0)
246 fname
= gettext ("*standard input*");
249 fp
= fopen (fname
, "r");
252 error (0, errno
, gettext ("cannot open input file `%s'"), fname
);
256 /* If we haven't seen anything yet, allocate result structure. */
259 current
= (struct catalog
*) xmalloc (sizeof (*current
));
261 current
->all_sets
= NULL
;
262 current
->total_messages
= 0;
263 current
->last_set
= 0;
264 current
->current_set
= find_set (current
, NL_SETD
);
266 #define obstack_chunk_alloc xmalloc
267 #define obstack_chunk_free free
268 obstack_init (¤t
->mem_pool
);
278 size_t start_line
= line_number
+ 1;
285 act_len
= getline (&buf
, &len
, fp
);
290 /* It the line continued? */
291 if (buf
[act_len
- 1] == '\n')
294 continued
= buf
[act_len
- 1] == '\\';
301 /* Append to currently selected line. */
302 obstack_grow (¤t
->mem_pool
, buf
, act_len
);
306 obstack_1grow (¤t
->mem_pool
, '\0');
307 this_line
= (char *) obstack_finish (¤t
->mem_pool
);
310 if (this_line
[0] == '$')
312 if (isspace (this_line
[1]))
313 /* This is a comment line. Do nothing. */;
314 else if (strncmp (&this_line
[1], "set", 3) == 0)
316 int cnt
= sizeof ("cnt");
318 const char *symbol
= NULL
;
319 while (isspace (this_line
[cnt
]))
322 if (isdigit (this_line
[cnt
]))
324 set_number
= atol (&this_line
[cnt
]);
326 /* If the given number for the character set is
327 higher than any we used for symbolic set names
328 avoid clashing by using only higher numbers for
329 the following symbolic definitions. */
330 if (set_number
> current
->last_set
)
331 current
->last_set
= set_number
;
335 /* See whether it is a reasonable identifier. */
337 while (isalnum (this_line
[cnt
]) || this_line
[cnt
] == '_')
342 /* No correct character found. */
343 error_at_line (0, 0, fname
, start_line
,
344 gettext ("illegal set number"));
349 /* We have found seomthing which looks like a
350 correct identifier. */
351 struct set_list
*runp
;
353 this_line
[cnt
] = '\0';
355 symbol
= &this_line
[start
];
357 /* Test whether the identifier was already used. */
358 runp
= current
->all_sets
;
360 if (runp
->symbol
!= NULL
361 && strcmp (runp
->symbol
, symbol
) == 0)
368 /* We cannot allow duplicate identifiers for
370 error_at_line (0, 0, fname
, start_line
,
371 gettext ("duplicate set definition"));
372 error_at_line (0, 0, runp
->fname
, runp
->line
,
374 this is the first definition"));
378 /* Allocate next free message set for identifier. */
379 set_number
= ++current
->last_set
;
385 /* We found a legal set number. */
386 current
->current_set
= find_set (current
, set_number
);
389 current
->current_set
->symbol
= symbol
;
390 current
->current_set
->fname
= fname
;
391 current
->current_set
->line
= start_line
;
394 else if (strncmp (&this_line
[1], "delset", 6) == 0)
396 int cnt
= sizeof ("delset");
398 while (isspace (this_line
[cnt
]))
401 if (isdigit (this_line
[cnt
]))
403 size_t set_number
= atol (&this_line
[cnt
]);
404 struct set_list
*set
;
406 /* Mark the message set with the given number as
408 set
= find_set (current
, set_number
);
413 /* See whether it is a reasonable identifier. */
415 while (isalnum (this_line
[cnt
]) || this_line
[cnt
] == '_')
420 error_at_line (0, 0, fname
, start_line
,
421 gettext ("illegal set number"));
427 struct set_list
*runp
;
429 this_line
[cnt
] = '\0';
431 symbol
= &this_line
[start
];
433 /* We have a symbolic set name. This name must
434 appear somewhere else in the catalogs read so
437 for (runp
= current
->all_sets
; runp
!= NULL
;
440 if (strcmp (runp
->symbol
, symbol
) == 0)
447 /* Name does not exist before. */
448 error_at_line (0, 0, fname
, start_line
,
449 gettext ("unknown set `%s'"), symbol
);
453 else if (strncmp (&this_line
[1], "quote", 5) == 0)
455 int cnt
= sizeof ("quote");
456 while (isspace (this_line
[cnt
]))
458 /* Yes, the quote char can be '\0'; this means no quote
460 current
->quote_char
= this_line
[cnt
];
466 while (this_line
[cnt
] != '\0' && !isspace (this_line
[cnt
]))
468 this_line
[cnt
] = '\0';
469 error_at_line (0, 0, fname
, start_line
,
470 gettext ("unknown directive `%s': line ignored"),
474 else if (isalnum (this_line
[0]) || this_line
[0] == '_')
476 const char *ident
= this_line
;
481 while (this_line
[0] != '\0' && !isspace (this_line
[0]));;
482 this_line
[0] = '\0'; /* Terminate the identifier. */
486 while (isspace (this_line
[0]));
487 /* Now we found the beginning of the message itself. */
489 if (isdigit (ident
[0]))
491 struct message_list
*runp
;
493 message_number
= atoi (ident
);
495 /* Find location to insert the new message. */
496 runp
= current
->current_set
->messages
;
498 if (runp
->number
== message_number
)
504 /* Oh, oh. There is already a message with this
505 number is the message set. */
506 error_at_line (0, 0, fname
, start_line
,
507 gettext ("duplicated message number"));
508 error_at_line (0, 0, runp
->fname
, runp
->line
,
509 gettext ("this is the first definition"));
512 ident
= NULL
; /* We don't have a symbol. */
514 if (message_number
!= 0
515 && message_number
> current
->current_set
->last_message
)
516 current
->current_set
->last_message
= message_number
;
518 else if (ident
[0] != '\0')
520 struct message_list
*runp
;
521 runp
= current
->current_set
->messages
;
523 /* Test whether the symbolic name was not used for
524 another message in this message set. */
526 if (runp
->symbol
!= NULL
&& strcmp (ident
, runp
->symbol
) == 0)
532 /* The name is already used. */
533 error_at_line (0, 0, fname
, start_line
,
534 gettext ("duplicated message identifier"));
535 error_at_line (0, 0, runp
->fname
, runp
->line
,
536 gettext ("this is the first definition"));
540 /* Give the message the next unused number. */
541 message_number
= ++current
->current_set
->last_message
;
546 if (message_number
!= 0)
548 struct message_list
*newp
;
550 used
= 1; /* Yes, we use the line. */
552 /* Strip quote characters, change escape sequences into
553 correct characters etc. */
554 normalize_line (fname
, start_line
, this_line
,
555 current
->quote_char
);
557 newp
= (struct message_list
*) xmalloc (sizeof (*newp
));
558 newp
->number
= message_number
;
559 newp
->message
= this_line
;
560 /* Remember symbolic name; is NULL if no is given. */
561 newp
->symbol
= ident
;
562 /* Remember where we found the character. */
564 newp
->line
= start_line
;
566 /* Find place to insert to message. We keep them in a
567 sorted single linked list. */
568 if (current
->current_set
->messages
== NULL
569 || current
->current_set
->messages
->number
> message_number
)
571 newp
->next
= current
->current_set
->messages
;
572 current
->current_set
->messages
= newp
;
576 struct message_list
*runp
;
577 runp
= current
->current_set
->messages
;
578 while (runp
->next
!= NULL
)
579 if (runp
->next
->number
> message_number
)
583 newp
->next
= runp
->next
;
587 ++current
->total_messages
;
594 /* See whether we have any non-white space character in this
596 while (this_line
[cnt
] != '\0' && isspace (this_line
[cnt
]))
599 if (this_line
[cnt
] != '\0')
600 /* Yes, some unknown characters found. */
601 error_at_line (0, 0, fname
, start_line
,
602 gettext ("malformed line ignored"));
605 /* We can save the memory for the line if it was not used. */
607 obstack_free (¤t
->mem_pool
, this_line
);
617 write_out (struct catalog
*catalog
, const char *output_name
,
618 const char *header_name
)
620 /* Computing the "optimal" size. */
621 struct set_list
*set_run
;
622 size_t best_total
, best_size
, best_depth
;
623 size_t act_size
, act_depth
;
624 struct catalog_obj obj
;
625 struct obstack string_pool
;
628 u_int32_t
*array1
, *array2
;
632 /* If not otherwise told try to read file with existing
635 read_old (catalog
, output_name
);
637 /* Initialize best_size with a very high value. */
638 best_total
= best_size
= best_depth
= UINT_MAX
;
640 /* We need some start size for testing. Let's start with
641 TOTAL_MESSAGES / 5, which theoretically provides a mean depth of
643 act_size
= 1 + catalog
->total_messages
/ 5;
645 /* We determine the size of a hash table here. Because the message
646 numbers can be chosen arbitrary by the programmer we cannot use
647 the simple method of accessing the array using the message
648 number. The algorithm is based on the trivial hash function
649 NUMBER % TABLE_SIZE, where collisions are stored in a second
650 dimension up to TABLE_DEPTH. We here compute TABLE_SIZE so that
651 the needed space (= TABLE_SIZE * TABLE_DEPTH) is minimal. */
652 while (act_size
<= best_total
)
654 size_t deep
[act_size
];
657 memset (deep
, '\0', act_size
* sizeof (size_t));
658 set_run
= catalog
->all_sets
;
659 while (set_run
!= NULL
)
661 struct message_list
*message_run
;
663 message_run
= set_run
->messages
;
664 while (message_run
!= NULL
)
666 size_t idx
= (message_run
->number
* set_run
->number
) % act_size
;
669 if (deep
[idx
] > act_depth
)
671 act_depth
= deep
[idx
];
672 if (act_depth
* act_size
> best_total
)
675 message_run
= message_run
->next
;
677 set_run
= set_run
->next
;
680 if (act_depth
* act_size
<= best_total
)
682 /* We have found a better solution. */
683 best_total
= act_depth
* act_size
;
684 best_size
= act_size
;
685 best_depth
= act_depth
;
691 /* let's be prepared for an empty message file. */
692 if (best_size
== UINT_MAX
)
698 /* OK, now we have the size we will use. Fill in the header, build
699 the table and the second one with swapped byte order. */
700 obj
.magic
= CATGETS_MAGIC
;
701 obj
.plane_size
= best_size
;
702 obj
.plane_depth
= best_depth
;
704 /* Allocate room for all needed arrays. */
706 (u_int32_t
*) alloca (best_size
* best_depth
* sizeof (u_int32_t
) * 3);
707 memset (array1
, '\0', best_size
* best_depth
* sizeof (u_int32_t
) * 3);
709 = (u_int32_t
*) alloca (best_size
* best_depth
* sizeof (u_int32_t
) * 3);
710 obstack_init (&string_pool
);
712 set_run
= catalog
->all_sets
;
713 while (set_run
!= NULL
)
715 struct message_list
*message_run
;
717 message_run
= set_run
->messages
;
718 while (message_run
!= NULL
)
720 size_t idx
= (((message_run
->number
* set_run
->number
) % best_size
)
722 /* Determine collision depth. */
723 while (array1
[idx
] != 0)
724 idx
+= best_size
* 3;
726 /* Store set number, message number and pointer into string
727 space, relative to the first string. */
728 array1
[idx
+ 0] = set_run
->number
;
729 array1
[idx
+ 1] = message_run
->number
;
730 array1
[idx
+ 2] = obstack_object_size (&string_pool
);
732 /* Add current string to the continuous space containing all
734 obstack_grow0 (&string_pool
, message_run
->message
,
735 strlen (message_run
->message
));
737 message_run
= message_run
->next
;
740 set_run
= set_run
->next
;
742 strings_size
= obstack_object_size (&string_pool
);
743 strings
= obstack_finish (&string_pool
);
745 /* Compute ARRAY2 by changing the byte order. */
746 for (cnt
= 0; cnt
< best_size
* best_depth
* 3; ++cnt
)
747 array2
[cnt
] = SWAPU32 (array1
[cnt
]);
749 /* Now we can write out the whole data. */
750 if (strcmp (output_name
, "-") == 0
751 || strcmp (output_name
, "/dev/stdout") == 0)
755 fd
= creat (output_name
, 0666);
757 error (EXIT_FAILURE
, errno
, gettext ("cannot open output file `%s'"),
761 /* Write out header. */
762 write (fd
, &obj
, sizeof (obj
));
764 /* We always write out the little endian version of the index
766 #if __BYTE_ORDER == __LITTLE_ENDIAN
767 write (fd
, array1
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
768 write (fd
, array2
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
769 #elif __BYTE_ORDER == __BIG_ENDIAN
770 write (fd
, array2
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
771 write (fd
, array1
, best_size
* best_depth
* sizeof (u_int32_t
) * 3);
773 # error Cannot handle __BYTE_ORDER byte order
776 /* Finally write the strings. */
777 write (fd
, strings
, strings_size
);
779 if (fd
!= STDOUT_FILENO
)
782 /* If requested now write out the header file. */
783 if (header_name
!= NULL
)
788 /* Open output file. "-" or "/dev/stdout" means write to
790 if (strcmp (header_name
, "-") == 0
791 || strcmp (header_name
, "/dev/stdout") == 0)
795 fp
= fopen (header_name
, "w");
797 error (EXIT_FAILURE
, errno
,
798 gettext ("cannot open output file `%s'"), header_name
);
801 /* Iterate over all sets and all messages. */
802 set_run
= catalog
->all_sets
;
803 while (set_run
!= NULL
)
805 struct message_list
*message_run
;
807 /* If the current message set has a symbolic name write this
809 if (set_run
->symbol
!= NULL
)
810 fprintf (fp
, "%s#define %sSet %#x\t/* %s:%Zu */\n",
811 first
? "" : "\n", set_run
->symbol
, set_run
->number
- 1,
812 set_run
->fname
, set_run
->line
);
815 message_run
= set_run
->messages
;
816 while (message_run
!= NULL
)
818 /* If the current message has a symbolic name write
819 #define out. But we have to take care for the set
820 not having a symbolic name. */
821 if (message_run
->symbol
!= NULL
)
822 if (set_run
->symbol
== NULL
)
823 fprintf (fp
, "#define AutomaticSet%d%s %#x\t/* %s:%Zu */\n",
824 set_run
->number
, message_run
->symbol
,
825 message_run
->number
, message_run
->fname
,
828 fprintf (fp
, "#define %s%s %#x\t/* %s:%Zu */\n",
829 set_run
->symbol
, message_run
->symbol
,
830 message_run
->number
, message_run
->fname
,
833 message_run
= message_run
->next
;
836 set_run
= set_run
->next
;
845 static struct set_list
*
846 find_set (struct catalog
*current
, int number
)
848 struct set_list
*result
= current
->all_sets
;
850 /* We must avoid set number 0 because a set of this number signals
851 in the tables that the entry is not occupied. */
854 while (result
!= NULL
)
855 if (result
->number
== number
)
858 result
= result
->next
;
860 /* Prepare new message set. */
861 result
= (struct set_list
*) xmalloc (sizeof (*result
));
862 result
->number
= number
;
864 result
->messages
= NULL
;
865 result
->next
= current
->all_sets
;
866 current
->all_sets
= result
;
872 /* Normalize given string *in*place* by processing escape sequences
873 and quote characters. */
875 normalize_line (const char *fname
, size_t line
, char *string
, char quote_char
)
881 if (quote_char
!= '\0' && *rp
== quote_char
)
890 if (*rp
== quote_char
)
891 /* We simply end the string when we find the first time an
892 not-escaped quote character. */
894 else if (*rp
== '\\')
897 if (quote_char
!= '\0' && *rp
== quote_char
)
898 /* This is an extension to XPG. */
901 /* Recognize escape sequences. */
934 int number
= *rp
++ - '0';
935 while (number
<= (255 / 8) && *rp
>= '0' && *rp
<= '7')
938 number
+= *rp
++ - '0';
940 *wp
++ = (char) number
;
944 /* Simply ignore the backslash character. */
951 /* If we saw a quote character at the beginning we expect another
953 if (is_quoted
&& *rp
!= quote_char
)
954 error (0, 0, fname
, line
, gettext ("unterminated message"));
956 /* Terminate string. */
963 read_old (struct catalog
*catalog
, const char *file_name
)
965 struct catalog_info old_cat_obj
;
966 struct set_list
*set
= NULL
;
970 old_cat_obj
.status
= closed
;
971 old_cat_obj
.cat_name
= file_name
;
973 /* Try to open catalog, but don't look through the NLSPATH. */
974 __open_catalog (&old_cat_obj
, 0);
976 if (old_cat_obj
.status
!= mmaped
&& old_cat_obj
.status
!= malloced
)
978 /* No problem, the catalog simply does not exist. */
981 error (EXIT_FAILURE
, errno
, gettext ("while opening old catalog file"));
983 /* OK, we have the catalog loaded. Now read all messages and merge
984 them. When set and message number clash for any message the new
986 for (cnt
= 0; cnt
< old_cat_obj
.plane_size
* old_cat_obj
.plane_depth
; ++cnt
)
988 struct message_list
*message
, *last
;
990 if (old_cat_obj
.name_ptr
[cnt
* 3 + 0] == 0)
991 /* No message in this slot. */
994 if (old_cat_obj
.name_ptr
[cnt
* 3 + 0] - 1 != (u_int32_t
) last_set
)
996 last_set
= old_cat_obj
.name_ptr
[cnt
* 3 + 0] - 1;
997 set
= find_set (catalog
, old_cat_obj
.name_ptr
[cnt
* 3 + 0] - 1);
1001 message
= set
->messages
;
1002 while (message
!= NULL
)
1004 if ((u_int32_t
) message
->number
>= old_cat_obj
.name_ptr
[cnt
* 3 + 1])
1007 message
= message
->next
;
1011 || (u_int32_t
) message
->number
> old_cat_obj
.name_ptr
[cnt
* 3 + 1])
1013 /* We have found a message which is not yet in the catalog.
1014 Insert it at the right position. */
1015 struct message_list
*newp
;
1017 newp
= (struct message_list
*) xmalloc (sizeof(*newp
));
1018 newp
->number
= old_cat_obj
.name_ptr
[cnt
* 3 + 1];
1020 &old_cat_obj
.strings
[old_cat_obj
.name_ptr
[cnt
* 3 + 2]];
1023 newp
->symbol
= NULL
;
1024 newp
->next
= message
;
1027 set
->messages
= newp
;
1031 ++catalog
->total_messages
;