1 /* Generate fastloading iconv module configuration files.
2    Copyright (C) 2000-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published
7    by the Free Software Foundation; version 2 of the License, or
8    (at your option) any later version.
9 
10    This program 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
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, see <https://www.gnu.org/licenses/>.  */
17 
18 #include <argp.h>
19 #include <assert.h>
20 #include <error.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <libintl.h>
24 #include <locale.h>
25 #include <mcheck.h>
26 #include <search.h>
27 #include <stdint.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdio_ext.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <sys/cdefs.h>
35 #include <sys/uio.h>
36 
37 #include "iconvconfig.h"
38 #include <gconv_parseconfdir.h>
39 
40 /* Get libc version number.  */
41 #include "../version.h"
42 
43 #define PACKAGE _libc_intl_domainname
44 
45 
46 /* The hashing function we use.  */
47 #include "../intl/hash-string.h"
48 
49 
50 /* Types used.  */
51 struct module
52 {
53   char *fromname;
54   struct Strent *fromname_strent;
55   char *filename;
56   struct Strent *filename_strent;
57   const char *directory;
58   struct Strent *directory_strent;
59   struct module *next;
60   int cost;
61   struct Strent *toname_strent;
62   char toname[0];
63 };
64 
65 struct alias
66 {
67   char *fromname;
68   struct Strent *froment;
69   struct module *module;
70   struct Strent *toent;
71   char toname[0];
72 };
73 
74 struct name
75 {
76   const char *name;
77   struct Strent *strent;
78   int module_idx;
79   uint32_t hashval;
80 };
81 
82 struct name_info
83 {
84   const char *canonical_name;
85   struct Strent *canonical_strent;
86 
87   struct module *from_internal;
88   struct module *to_internal;
89 
90   struct other_conv_list
91   {
92     int dest_idx;
93     struct other_conv
94     {
95       gidx_t module_idx;
96       struct module *module;
97       struct other_conv *next;
98     } other_conv;
99     struct other_conv_list *next;
100   } *other_conv_list;
101 };
102 
103 
104 /* Name and version of program.  */
105 static void print_version (FILE *stream, struct argp_state *state);
106 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
107 
108 /* Short description of program.  */
109 static const char doc[] = N_("\
110 Create fastloading iconv module configuration file.");
111 
112 /* Strings for arguments in help texts.  */
113 static const char args_doc[] = N_("[DIR...]");
114 
115 /* Prototype for option handler.  */
116 static error_t parse_opt (int key, char *arg, struct argp_state *state);
117 
118 /* Function to print some extra text in the help message.  */
119 static char *more_help (int key, const char *text, void *input);
120 
121 /* Definitions of arguments for argp functions.  */
122 #define OPT_PREFIX 300
123 #define OPT_NOSTDLIB 301
124 static const struct argp_option options[] =
125 {
126   { "prefix", OPT_PREFIX, N_("PATH"), 0,
127     N_("Prefix used for all file accesses") },
128   { "output", 'o', N_("FILE"), 0, N_("\
129 Put output in FILE instead of installed location\
130  (--prefix does not apply to FILE)") },
131   { "nostdlib", OPT_NOSTDLIB, NULL, 0,
132     N_("Do not search standard directories, only those on the command line") },
133   { NULL, 0, NULL, 0, NULL }
134 };
135 
136 /* Data structure to communicate with argp functions.  */
137 static struct argp argp =
138 {
139   options, parse_opt, args_doc, doc, NULL, more_help
140 };
141 
142 
143 /* The function doing the actual work.  */
144 static int handle_dir (const char *dir);
145 
146 /* Add all known builtin conversions and aliases.  */
147 static void add_builtins (void);
148 
149 /* Create list of all aliases without circular aliases.  */
150 static void get_aliases (void);
151 
152 /* Create list of all modules.  */
153 static void get_modules (void);
154 
155 /* Get list of all the names and thereby indexing them.  */
156 static void generate_name_list (void);
157 
158 /* Collect information about all the names.  */
159 static void generate_name_info (void);
160 
161 /* Write the output file.  */
162 static int write_output (void);
163 
164 
165 /* Prefix to be used for all file accesses.  */
166 static const char *prefix = "";
167 /* Its length.  */
168 static size_t prefix_len;
169 
170 /* Directory to place output file in.  */
171 static const char *output_file;
172 /* Its length.  */
173 static size_t output_file_len;
174 
175 /* If true, omit the GCONV_PATH directories and require some arguments.  */
176 static bool nostdlib;
177 
178 /* Search tree of the modules we know.  */
179 static void *modules;
180 
181 /* Search tree of the aliases we know.  */
182 static void *aliases;
183 
184 /* Search tree for name to index mapping.  */
185 static void *names;
186 
187 /* Number of names we know about.  */
188 static int nnames;
189 
190 /* List of all aliases.  */
191 static struct alias **alias_list;
192 static size_t nalias_list;
193 static size_t nalias_list_max;
194 
195 /* List of all modules.  */
196 static struct module **module_list;
197 static size_t nmodule_list;
198 static size_t nmodule_list_max;
199 
200 /* Names and information about them.  */
201 static struct name_info *name_info;
202 static size_t nname_info;
203 
204 /* Number of translations not from or to INTERNAL.  */
205 static size_t nextra_modules;
206 
207 
208 /* Names and aliases for the builtin transformations.  */
209 static struct
210 {
211   const char *from;
212   const char *to;
213 } builtin_alias[] =
214   {
215 #define BUILTIN_ALIAS(alias, real) \
216     { .from = alias, .to = real },
217 #define BUILTIN_TRANSFORMATION(From, To, Cost, Name, Fct, BtowcFct, \
218 			       MinF, MaxF, MinT, MaxT)
219 #include <gconv_builtin.h>
220   };
221 #undef BUILTIN_ALIAS
222 #undef BUILTIN_TRANSFORMATION
223 #define nbuiltin_alias (sizeof (builtin_alias) / sizeof (builtin_alias[0]))
224 
225 static struct
226 {
227   const char *from;
228   const char *to;
229   const char *module;
230   int cost;
231 } builtin_trans[] =
232   {
233 #define BUILTIN_ALIAS(alias, real)
234 #define BUILTIN_TRANSFORMATION(From, To, Cost, Name, Fct, BtowcFct, \
235 			       MinF, MaxF, MinT, MaxT) \
236     { .from = From, .to = To, .module = Name, .cost = Cost },
237 #include <gconv_builtin.h>
238   };
239 #undef BUILTIN_ALIAS
240 #undef BUILTIN_TRANSFORMATION
241 #define nbuiltin_trans (sizeof (builtin_trans) / sizeof (builtin_trans[0]))
242 
243 
244 /* Filename extension for the modules.  */
245 #ifndef MODULE_EXT
246 # define MODULE_EXT ".so"
247 #endif
248 static const char gconv_module_ext[] = MODULE_EXT;
249 
250 
251 #include <programs/xmalloc.h>
252 #include <programs/xasprintf.h>
253 
254 
255 /* C string table handling.  */
256 struct Strtab;
257 struct Strent;
258 
259 /* Create new C string table object in memory.  */
260 extern struct Strtab *strtabinit (void);
261 
262 /* Free resources allocated for C string table ST.  */
263 extern void strtabfree (struct Strtab *st);
264 
265 /* Add string STR (length LEN is != 0) to C string table ST.  */
266 extern struct Strent *strtabadd (struct Strtab *st, const char *str,
267 				 size_t len);
268 
269 /* Finalize string table ST and store size in *SIZE and return a pointer.  */
270 extern void *strtabfinalize (struct Strtab *st, size_t *size);
271 
272 /* Get offset in string table for string associated with SE.  */
273 extern size_t strtaboffset (struct Strent *se);
274 
275 /* String table we construct.  */
276 static struct Strtab *strtab;
277 
278 
279 
280 int
main(int argc,char * argv[])281 main (int argc, char *argv[])
282 {
283   int remaining;
284   int status = 0;
285 
286   /* Enable memory use testing.  */
287   /* mcheck_pedantic (NULL); */
288   mtrace ();
289 
290   /* Set locale via LC_ALL.  */
291   setlocale (LC_ALL, "");
292 
293   /* Set the text message domain.  */
294   textdomain (_libc_intl_domainname);
295 
296   /* Parse and process arguments.  */
297   argp_parse (&argp, argc, argv, 0, &remaining, NULL);
298 
299   if (nostdlib && remaining == argc)
300     error (2, 0, _("Directory arguments required when using --nostdlib"));
301 
302   /* Initialize the string table.  */
303   strtab = strtabinit ();
304 
305   /* Handle all directories mentioned.  */
306   while (remaining < argc)
307     status |= handle_dir (argv[remaining++]);
308 
309   if (! nostdlib)
310     {
311       /* In any case also handle the standard directory.  */
312       char *path = strdupa (GCONV_PATH), *tp = strsep (&path, ":");
313       while (tp != NULL)
314 	{
315 	  status |= handle_dir (tp);
316 
317 	  tp = strsep (&path, ":");
318 	}
319     }
320 
321   /* Add the builtin transformations and aliases without overwriting
322      anything.  */
323   add_builtins ();
324 
325   /* Store aliases in an array.  */
326   get_aliases ();
327 
328   /* Get list of all modules.  */
329   get_modules ();
330 
331   /* Generate list of all the names we know to handle in some way.  */
332   generate_name_list ();
333 
334   /* Now we know all the names we will handle, collect information
335      about them.  */
336   generate_name_info ();
337 
338   /* Write the output file, but only if we haven't seen any error.  */
339   if (status == 0)
340     status = write_output ();
341   else
342     error (1, 0, _("no output file produced because warnings were issued"));
343 
344   return status;
345 }
346 
347 
348 /* Handle program arguments.  */
349 static error_t
parse_opt(int key,char * arg,struct argp_state * state)350 parse_opt (int key, char *arg, struct argp_state *state)
351 {
352   switch (key)
353     {
354     case OPT_PREFIX:
355       prefix = arg;
356       prefix_len = strlen (prefix);
357       break;
358     case 'o':
359       output_file = arg;
360       output_file_len = strlen (output_file);
361       break;
362     case OPT_NOSTDLIB:
363       nostdlib = true;
364       break;
365     default:
366       return ARGP_ERR_UNKNOWN;
367     }
368   return 0;
369 }
370 
371 
372 static char *
more_help(int key,const char * text,void * input)373 more_help (int key, const char *text, void *input)
374 {
375   char *tp = NULL;
376   switch (key)
377     {
378     case ARGP_KEY_HELP_EXTRA:
379       /* We print some extra information.  */
380       if (asprintf (&tp, gettext ("\
381 For bug reporting instructions, please see:\n\
382 %s.\n"), REPORT_BUGS_TO) < 0)
383 	return NULL;
384       return tp;
385     default:
386       break;
387     }
388   return (char *) text;
389 }
390 
391 
392 /* Print the version information.  */
393 static void
print_version(FILE * stream,struct argp_state * state)394 print_version (FILE *stream, struct argp_state *state)
395 {
396   fprintf (stream, "iconvconfig %s%s\n", PKGVERSION, VERSION);
397   fprintf (stream, gettext ("\
398 Copyright (C) %s Free Software Foundation, Inc.\n\
399 This is free software; see the source for copying conditions.  There is NO\n\
400 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
401 "), "2022");
402   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
403 }
404 
405 
406 static int
alias_compare(const void * p1,const void * p2)407 alias_compare (const void *p1, const void *p2)
408 {
409   const struct alias *a1 = (const struct alias *) p1;
410   const struct alias *a2 = (const struct alias *) p2;
411 
412   return strcmp (a1->fromname, a2->fromname);
413 }
414 
415 
416 static void
new_alias(const char * fromname,size_t fromlen,const char * toname,size_t tolen)417 new_alias (const char *fromname, size_t fromlen, const char *toname,
418 	   size_t tolen)
419 {
420   struct alias *newp;
421   void **inserted;
422 
423   newp = (struct alias *) xmalloc (sizeof (struct alias) + fromlen + tolen);
424 
425   newp->fromname = mempcpy (newp->toname, toname, tolen);
426   memcpy (newp->fromname, fromname, fromlen);
427   newp->module = NULL;
428 
429   inserted = (void **) tsearch (newp, &aliases, alias_compare);
430   if (inserted == NULL)
431     error (EXIT_FAILURE, errno, gettext ("while inserting in search tree"));
432   if (*inserted != newp)
433     /* Something went wrong, free this entry.  */
434     free (newp);
435   else
436     {
437       newp->froment = strtabadd (strtab, newp->fromname, fromlen);
438       newp->toent = strtabadd (strtab, newp->toname, tolen);
439     }
440 }
441 
442 
443 /* Add new alias.  */
444 static void
add_alias(char * rp)445 add_alias (char *rp)
446 {
447   /* We now expect two more string.  The strings are normalized
448      (converted to UPPER case) and strored in the alias database.  */
449   char *from;
450   char *to;
451   char *wp;
452 
453   while (isspace (*rp))
454     ++rp;
455   from = wp = rp;
456   while (*rp != '\0' && !isspace (*rp))
457     *wp++ = toupper (*rp++);
458   if (*rp == '\0')
459     /* There is no `to' string on the line.  Ignore it.  */
460     return;
461   *wp++ = '\0';
462   to = ++rp;
463   while (isspace (*rp))
464     ++rp;
465   while (*rp != '\0' && !isspace (*rp))
466     *wp++ = toupper (*rp++);
467   if (to == wp)
468     /* No `to' string, ignore the line.  */
469     return;
470   *wp++ = '\0';
471 
472   assert (strlen (from) + 1 == (size_t) (to - from));
473   assert (strlen (to) + 1 == (size_t) (wp - to));
474 
475   new_alias (from, to - from, to, wp - to);
476 }
477 
478 
479 static void
append_alias(const void * nodep,VISIT value,int level)480 append_alias (const void *nodep, VISIT value, int level)
481 {
482   if (value != leaf && value != postorder)
483     return;
484 
485   if (nalias_list_max == nalias_list)
486     {
487       nalias_list_max += 50;
488       alias_list = (struct alias **) xrealloc (alias_list,
489 					       (nalias_list_max
490 						* sizeof (struct alias *)));
491     }
492 
493   alias_list[nalias_list++] = *(struct alias **) nodep;
494 }
495 
496 
497 static void
get_aliases(void)498 get_aliases (void)
499 {
500   twalk (aliases, append_alias);
501 }
502 
503 
504 static int
module_compare(const void * p1,const void * p2)505 module_compare (const void *p1, const void *p2)
506 {
507   const struct module *m1 = (const struct module *) p1;
508   const struct module *m2 = (const struct module *) p2;
509   int result;
510 
511   result = strcmp (m1->fromname, m2->fromname);
512   if (result == 0)
513     result = strcmp (m1->toname, m2->toname);
514 
515   return result;
516 }
517 
518 
519 /* Create new module record.  */
520 static void
new_module(const char * fromname,size_t fromlen,const char * toname,size_t tolen,const char * dir_in,const char * filename,size_t filelen,int cost,size_t need_ext)521 new_module (const char *fromname, size_t fromlen, const char *toname,
522 	    size_t tolen, const char *dir_in,
523 	    const char *filename, size_t filelen, int cost, size_t need_ext)
524 {
525   struct module *new_module;
526   size_t dirlen = strlen (dir_in) + 1;
527   const char *directory = xstrdup (dir_in);
528   char *tmp;
529   void **inserted;
530 
531   new_module = (struct module *) xmalloc (sizeof (struct module)
532 					  + fromlen + tolen + filelen
533 					  + need_ext);
534 
535   new_module->fromname = mempcpy (new_module->toname, toname, tolen);
536 
537   new_module->filename = mempcpy (new_module->fromname, fromname, fromlen);
538 
539   new_module->cost = cost;
540   new_module->next = NULL;
541 
542   tmp = mempcpy (new_module->filename, filename, filelen);
543   if (need_ext)
544     {
545       memcpy (tmp - 1, gconv_module_ext, need_ext + 1);
546       filelen += need_ext;
547     }
548   new_module->directory = directory;
549 
550   /* Now insert the new module data structure in our search tree.  */
551   inserted = (void **) tsearch (new_module, &modules, module_compare);
552   if (inserted == NULL)
553     error (EXIT_FAILURE, errno, "while inserting in search tree");
554   if (*inserted != new_module)
555     free (new_module);
556   else
557     {
558       new_module->fromname_strent = strtabadd (strtab, new_module->fromname,
559 					       fromlen);
560       new_module->toname_strent = strtabadd (strtab, new_module->toname,
561 					     tolen);
562       new_module->filename_strent = strtabadd (strtab, new_module->filename,
563 					       filelen);
564       new_module->directory_strent = strtabadd (strtab, directory, dirlen);
565     }
566 }
567 
568 
569 /* Add new module.  */
570 static void
add_module(char * rp,const char * directory,size_t dirlen,int modcount)571 add_module (char *rp, const char *directory,
572 	    size_t dirlen __attribute__ ((__unused__)),
573 	    int modcount __attribute__ ((__unused__)))
574 {
575   /* We expect now
576      1. `from' name
577      2. `to' name
578      3. filename of the module
579      4. an optional cost value
580   */
581   char *from;
582   char *to;
583   char *module;
584   char *wp;
585   int need_ext;
586   int cost;
587 
588   while (isspace (*rp))
589     ++rp;
590   from = rp;
591   while (*rp != '\0' && !isspace (*rp))
592     {
593       *rp = toupper (*rp);
594       ++rp;
595     }
596   if (*rp == '\0')
597     return;
598   *rp++ = '\0';
599   to = wp = rp;
600   while (isspace (*rp))
601     ++rp;
602   while (*rp != '\0' && !isspace (*rp))
603     *wp++ = toupper (*rp++);
604   if (*rp == '\0')
605     return;
606   *wp++ = '\0';
607   do
608     ++rp;
609   while (isspace (*rp));
610   module = wp;
611   while (*rp != '\0' && !isspace (*rp))
612     *wp++ = *rp++;
613   if (*rp == '\0')
614     {
615       /* There is no cost, use one by default.  */
616       *wp++ = '\0';
617       cost = 1;
618     }
619   else
620     {
621       /* There might be a cost value.  */
622       char *endp;
623 
624       *wp++ = '\0';
625       cost = strtol (rp, &endp, 10);
626       if (rp == endp || cost < 1)
627 	/* No useful information.  */
628 	cost = 1;
629     }
630 
631   if (module[0] == '\0')
632     /* No module name given.  */
633     return;
634 
635   /* See whether we must add the ending.  */
636   need_ext = 0;
637   if ((size_t) (wp - module) < sizeof (gconv_module_ext)
638       || memcmp (wp - sizeof (gconv_module_ext), gconv_module_ext,
639 		 sizeof (gconv_module_ext)) != 0)
640     /* We must add the module extension.  */
641     need_ext = sizeof (gconv_module_ext) - 1;
642 
643   assert (strlen (from) + 1 == (size_t) (to - from));
644   assert (strlen (to) + 1 == (size_t) (module - to));
645   assert (strlen (module) + 1 == (size_t) (wp - module));
646 
647   new_module (from, to - from, to, module - to, directory, module, wp - module,
648 	      cost, need_ext);
649 }
650 
651 /* Read config files and add the data for this directory to cache.  */
652 static int
handle_dir(const char * dir)653 handle_dir (const char *dir)
654 {
655   char *newp = NULL;
656   size_t dirlen = strlen (dir);
657   bool found = false;
658 
659   /* End directory path with a '/' if it doesn't already.  */
660   if (dir[dirlen - 1] != '/')
661     {
662       newp = xmalloc (dirlen + 2);
663       memcpy (newp, dir, dirlen);
664       newp[dirlen++] = '/';
665       newp[dirlen] = '\0';
666       dir = newp;
667     }
668 
669   found = gconv_parseconfdir (dir[0] == '/' ? prefix : NULL, dir, dirlen);
670 
671   if (!found)
672     {
673       error (0, errno, "failed to open gconv configuration files in `%s'",
674 	     dir);
675       error (0, 0,
676 	     "ensure that the directory contains either a valid "
677 	     "gconv-modules file or a gconv-modules.d directory with "
678 	     "configuration files with names ending in .conf.");
679     }
680 
681   free (newp);
682 
683   return found ? 0 : 1;
684 }
685 
686 
687 static void
append_module(const void * nodep,VISIT value,int level)688 append_module (const void *nodep, VISIT value, int level)
689 {
690   struct module *mo;
691 
692   if (value != leaf && value != postorder)
693     return;
694 
695   mo = *(struct module **) nodep;
696 
697   if (nmodule_list > 0
698       && strcmp (module_list[nmodule_list - 1]->fromname, mo->fromname) == 0)
699     {
700       /* Same name.  */
701       mo->next = module_list[nmodule_list - 1];
702       module_list[nmodule_list - 1] = mo;
703 
704       return;
705     }
706 
707   if (nmodule_list_max == nmodule_list)
708     {
709       nmodule_list_max += 50;
710       module_list = (struct module **) xrealloc (module_list,
711 						 (nmodule_list_max
712 						  * sizeof (struct module *)));
713     }
714 
715   module_list[nmodule_list++] = mo;
716 }
717 
718 
719 static void
get_modules(void)720 get_modules (void)
721 {
722   twalk (modules, append_module);
723 }
724 
725 
726 static void
add_builtins(void)727 add_builtins (void)
728 {
729   size_t cnt;
730 
731   /* Add all aliases.  */
732   for (cnt = 0; cnt < nbuiltin_alias; ++cnt)
733     new_alias (builtin_alias[cnt].from,
734 	       strlen (builtin_alias[cnt].from) + 1,
735 	       builtin_alias[cnt].to,
736 	       strlen (builtin_alias[cnt].to) + 1);
737 
738   /* add the builtin transformations.  */
739   for (cnt = 0; cnt < nbuiltin_trans; ++cnt)
740     new_module (builtin_trans[cnt].from,
741 		strlen (builtin_trans[cnt].from) + 1,
742 		builtin_trans[cnt].to,
743 		strlen (builtin_trans[cnt].to) + 1,
744 		"", builtin_trans[cnt].module,
745 		strlen (builtin_trans[cnt].module) + 1,
746 		builtin_trans[cnt].cost, 0);
747 }
748 
749 
750 static int
name_compare(const void * p1,const void * p2)751 name_compare (const void *p1, const void *p2)
752 {
753   const struct name *n1 = (const struct name *) p1;
754   const struct name *n2 = (const struct name *) p2;
755 
756   return strcmp (n1->name, n2->name);
757 }
758 
759 
760 static struct name *
new_name(const char * str,struct Strent * strent)761 new_name (const char *str, struct Strent *strent)
762 {
763   struct name *newp = (struct name *) xmalloc (sizeof (struct name));
764 
765   newp->name = str;
766   newp->strent = strent;
767   newp->module_idx = -1;
768   newp->hashval = __hash_string (str);
769 
770   ++nnames;
771 
772   return newp;
773 }
774 
775 
776 static void
generate_name_list(void)777 generate_name_list (void)
778 {
779   size_t i;
780 
781   /* A name we always need.  */
782   tsearch (new_name ("INTERNAL", strtabadd (strtab, "INTERNAL",
783 					    sizeof ("INTERNAL"))),
784 	   &names, name_compare);
785 
786   for (i = 0; i < nmodule_list; ++i)
787     {
788       struct module *runp;
789 
790       if (strcmp (module_list[i]->fromname, "INTERNAL") != 0)
791 	tsearch (new_name (module_list[i]->fromname,
792 			   module_list[i]->fromname_strent),
793 		 &names, name_compare);
794 
795       for (runp = module_list[i]; runp != NULL; runp = runp->next)
796 	if (strcmp (runp->toname, "INTERNAL") != 0)
797 	  tsearch (new_name (runp->toname, runp->toname_strent),
798 		   &names, name_compare);
799     }
800 }
801 
802 
803 static int
name_to_module_idx(const char * name,int add)804 name_to_module_idx (const char *name, int add)
805 {
806   struct name **res;
807   struct name fake_name = { .name = name };
808   int idx;
809 
810   res = (struct name **) tfind (&fake_name, &names, name_compare);
811   if (res == NULL)
812     abort ();
813 
814   idx = (*res)->module_idx;
815   if (idx == -1 && add)
816     /* No module index assigned yet.  */
817     idx = (*res)->module_idx = nname_info++;
818 
819   return idx;
820 }
821 
822 
823 static void
generate_name_info(void)824 generate_name_info (void)
825 {
826   size_t i;
827   int idx;
828 
829   name_info = (struct name_info *) xcalloc (nmodule_list + 1,
830 					    sizeof (struct name_info));
831 
832   /* First add a special entry for the INTERNAL name.  This must have
833      index zero.  */
834   idx = name_to_module_idx ("INTERNAL", 1);
835   name_info[0].canonical_name = "INTERNAL";
836   name_info[0].canonical_strent = strtabadd (strtab, "INTERNAL",
837 					     sizeof ("INTERNAL"));
838   assert (nname_info == 1);
839 
840   for (i = 0; i < nmodule_list; ++i)
841     {
842       struct module *runp;
843 
844       for (runp = module_list[i]; runp != NULL; runp = runp->next)
845 	if (strcmp (runp->fromname, "INTERNAL") == 0)
846 	  {
847 	    idx = name_to_module_idx (runp->toname, 1);
848 	    name_info[idx].from_internal = runp;
849 	    assert (name_info[idx].canonical_name == NULL
850 		    || strcmp (name_info[idx].canonical_name,
851 			       runp->toname) == 0);
852 	    name_info[idx].canonical_name = runp->toname;
853 	    name_info[idx].canonical_strent = runp->toname_strent;
854 	  }
855 	else if (strcmp (runp->toname, "INTERNAL") == 0)
856 	  {
857 	    idx = name_to_module_idx (runp->fromname, 1);
858 	    name_info[idx].to_internal = runp;
859 	    assert (name_info[idx].canonical_name == NULL
860 		    || strcmp (name_info[idx].canonical_name,
861 			       runp->fromname) == 0);
862 	    name_info[idx].canonical_name = runp->fromname;
863 	    name_info[idx].canonical_strent = runp->fromname_strent;
864 	  }
865 	else
866 	  {
867 	    /* This is a transformation not to or from the INTERNAL
868 	       encoding.  */
869 	    int from_idx = name_to_module_idx (runp->fromname, 1);
870 	    int to_idx = name_to_module_idx (runp->toname, 1);
871 	    struct other_conv_list *newp;
872 
873 	    newp = (struct other_conv_list *)
874 	      xmalloc (sizeof (struct other_conv_list));
875 	    newp->other_conv.module_idx = to_idx;
876 	    newp->other_conv.module = runp;
877 	    newp->other_conv.next = NULL; /* XXX Allow multiple module sequence */
878 	    newp->dest_idx = to_idx;
879 	    newp->next = name_info[from_idx].other_conv_list;
880 	    name_info[from_idx].other_conv_list = newp;
881 	    assert (name_info[from_idx].canonical_name == NULL
882 		    || strcmp (name_info[from_idx].canonical_name,
883 			       runp->fromname) == 0);
884 	    name_info[from_idx].canonical_name = runp->fromname;
885 	    name_info[from_idx].canonical_strent = runp->fromname_strent;
886 
887 	    ++nextra_modules;
888 	  }
889     }
890 
891   /* Now add the module index information for all the aliases.  */
892   for (i = 0; i < nalias_list; ++i)
893     {
894       struct name fake_name = { .name = alias_list[i]->toname };
895       struct name **tonamep;
896 
897       tonamep = (struct name **) tfind (&fake_name, &names, name_compare);
898       if (tonamep != NULL)
899 	{
900 	  struct name *newp = new_name (alias_list[i]->fromname,
901 					alias_list[i]->froment);
902 	  newp->module_idx = (*tonamep)->module_idx;
903 	  tsearch (newp, &names, name_compare);
904 	}
905     }
906 }
907 
908 
909 static int
is_prime(unsigned long int candidate)910 is_prime (unsigned long int candidate)
911 {
912   /* No even number and none less than 10 will be passed here.  */
913   unsigned long int divn = 3;
914   unsigned long int sq = divn * divn;
915 
916   while (sq < candidate && candidate % divn != 0)
917     {
918       ++divn;
919       sq += 4 * divn;
920       ++divn;
921     }
922 
923   return candidate % divn != 0;
924 }
925 
926 
927 static uint32_t
next_prime(uint32_t seed)928 next_prime (uint32_t seed)
929 {
930   /* Make it definitely odd.  */
931   seed |= 1;
932 
933   while (!is_prime (seed))
934     seed += 2;
935 
936   return seed;
937 }
938 
939 
940 /* Format of the output file.
941 
942    Offset   Length       Description
943    0000     4            Magic header bytes
944    0004     2            Offset of string table (stoff)
945    0006     2            Offset of name hashing table (hoff)
946    0008     2            Hashing table size (hsize)
947    000A     2            Offset of module table (moff)
948    000C     2            Offset of other conversion module table (ooff)
949 
950    stoff    ???          String table
951 
952    hoff     8*hsize      Array of tuples
953 			    string table offset
954 			    module index
955 
956    moff     ???          Array of tuples
957 			    canonical name offset
958 			    from-internal module dir name offset
959 			    from-internal module name off
960 			    to-internal module dir name offset
961 			    to-internal module name offset
962 			    offset into other conversion table
963 
964    ooff     ???          One or more of
965 			    number of steps/modules
966 			    one or more of tuple
967 			      canonical name offset for output
968 			      module dir name offset
969 			      module name offset
970 			 (following last entry with step count 0)
971 */
972 
973 static struct hash_entry *hash_table;
974 static size_t hash_size;
975 
976 /* Function to insert the names.  */
name_insert(const void * nodep,VISIT value,int level)977 static void name_insert (const void *nodep, VISIT value, int level)
978 {
979   struct name *name;
980   unsigned int idx;
981   unsigned int hval2;
982 
983   if (value != leaf && value != postorder)
984     return;
985 
986   name = *(struct name **) nodep;
987   idx = name->hashval % hash_size;
988   hval2 = 1 + name->hashval % (hash_size - 2);
989 
990   while (hash_table[idx].string_offset != 0)
991     if ((idx += hval2) >= hash_size)
992       idx -= hash_size;
993 
994   hash_table[idx].string_offset = strtaboffset (name->strent);
995 
996   assert (name->module_idx != -1);
997   hash_table[idx].module_idx = name->module_idx;
998 }
999 
1000 static int
write_output(void)1001 write_output (void)
1002 {
1003   int fd;
1004   char *string_table;
1005   size_t string_table_size;
1006   struct gconvcache_header header;
1007   struct module_entry *module_table;
1008   char *extra_table;
1009   char *cur_extra_table;
1010   size_t n;
1011   int idx;
1012   struct iovec iov[6];
1013   static const gidx_t null_word;
1014   size_t total;
1015   char finalname[prefix_len + sizeof GCONV_MODULES_CACHE];
1016   char tmpfname[(output_file == NULL ? sizeof finalname : output_file_len + 1)
1017 		+ strlen (".XXXXXX")];
1018 
1019   /* Open the output file.  */
1020   if (output_file == NULL)
1021     {
1022       assert (GCONV_MODULES_CACHE[0] == '/');
1023       strcpy (stpcpy (mempcpy (tmpfname, prefix, prefix_len),
1024 		      GCONV_MODULES_CACHE),
1025 	      ".XXXXXX");
1026       strcpy (mempcpy (finalname, prefix, prefix_len), GCONV_MODULES_CACHE);
1027     }
1028   else
1029     strcpy (mempcpy (tmpfname, output_file, output_file_len), ".XXXXXX");
1030   fd = mkstemp (tmpfname);
1031   if (fd == -1)
1032     return 1;
1033 
1034   /* Create the string table.  */
1035   string_table = strtabfinalize (strtab, &string_table_size);
1036 
1037   /* Create the hashing table.  We know how many strings we have.
1038      Creating a perfect hash table is not reasonable here.  Therefore
1039      we use open hashing and a table size which is the next prime 50%
1040      larger than the number of strings.  */
1041   hash_size = next_prime (nnames + (nnames >> 1));
1042   hash_table = (struct hash_entry *) xcalloc (hash_size,
1043 					      sizeof (struct hash_entry));
1044   /* Fill the hash table.  */
1045   twalk (names, name_insert);
1046 
1047   /* Create the section for the module list.  */
1048   module_table = (struct module_entry *) xcalloc (sizeof (struct module_entry),
1049 						  nname_info);
1050 
1051   /* Allocate memory for the non-INTERNAL conversions.  The allocated
1052      memory can be more than is actually needed.  */
1053   extra_table = (char *) xcalloc (sizeof (struct extra_entry)
1054 				  + sizeof (gidx_t)
1055 				  + sizeof (struct extra_entry_module),
1056 				  nextra_modules);
1057   cur_extra_table = extra_table;
1058 
1059   /* Fill in the module information.  */
1060   for (n = 0; n < nname_info; ++n)
1061     {
1062       module_table[n].canonname_offset =
1063 	strtaboffset (name_info[n].canonical_strent);
1064 
1065       if (name_info[n].from_internal == NULL)
1066 	{
1067 	  module_table[n].fromdir_offset = 0;
1068 	  module_table[n].fromname_offset = 0;
1069 	}
1070       else
1071 	{
1072 	  module_table[n].fromdir_offset =
1073 	    strtaboffset (name_info[n].from_internal->directory_strent);
1074 	  module_table[n].fromname_offset =
1075 	    strtaboffset (name_info[n].from_internal->filename_strent);
1076 	}
1077 
1078       if (name_info[n].to_internal == NULL)
1079 	{
1080 	  module_table[n].todir_offset = 0;
1081 	  module_table[n].toname_offset = 0;
1082 	}
1083       else
1084 	{
1085 	  module_table[n].todir_offset =
1086 	    strtaboffset (name_info[n].to_internal->directory_strent);
1087 	  module_table[n].toname_offset =
1088 	    strtaboffset (name_info[n].to_internal->filename_strent);
1089 	}
1090 
1091       if (name_info[n].other_conv_list != NULL)
1092 	{
1093 	  struct other_conv_list *other = name_info[n].other_conv_list;
1094 
1095 	  /* Store the reference.  We add 1 to distinguish the entry
1096 	     at offset zero from the case where no extra modules are
1097 	     available.  The file reader has to account for the
1098 	     offset.  */
1099 	  module_table[n].extra_offset = 1 + cur_extra_table - extra_table;
1100 
1101 	  do
1102 	    {
1103 	      struct other_conv *runp;
1104 	      struct extra_entry *extra;
1105 
1106 	      /* Allocate new entry.  */
1107 	      extra = (struct extra_entry *) cur_extra_table;
1108 	      cur_extra_table += sizeof (struct extra_entry);
1109 	      extra->module_cnt = 0;
1110 
1111 	      runp = &other->other_conv;
1112 	      do
1113 		{
1114 		  cur_extra_table += sizeof (struct extra_entry_module);
1115 		  extra->module[extra->module_cnt].outname_offset =
1116 		    runp->next == NULL
1117 		    ? other->dest_idx : runp->next->module_idx;
1118 		  extra->module[extra->module_cnt].dir_offset =
1119 		    strtaboffset (runp->module->directory_strent);
1120 		  extra->module[extra->module_cnt].name_offset =
1121 		    strtaboffset (runp->module->filename_strent);
1122 		  ++extra->module_cnt;
1123 
1124 		  runp = runp->next;
1125 		}
1126 	      while (runp != NULL);
1127 
1128 	      other = other->next;
1129 	    }
1130 	  while (other != NULL);
1131 
1132 	  /* Final module_cnt is zero.  */
1133 	  *((gidx_t *) cur_extra_table) = 0;
1134 	  cur_extra_table += sizeof (gidx_t);
1135 	}
1136     }
1137 
1138   /* Clear padding.  */
1139   memset (&header, 0, sizeof (struct gconvcache_header));
1140 
1141   header.magic = GCONVCACHE_MAGIC;
1142 
1143   iov[0].iov_base = &header;
1144   iov[0].iov_len = sizeof (struct gconvcache_header);
1145   total = iov[0].iov_len;
1146 
1147   header.string_offset = total;
1148   iov[1].iov_base = string_table;
1149   iov[1].iov_len = string_table_size;
1150   total += iov[1].iov_len;
1151 
1152   idx = 2;
1153   if ((string_table_size & (sizeof (gidx_t) - 1)) != 0)
1154     {
1155       iov[2].iov_base = (void *) &null_word;
1156       iov[2].iov_len = (sizeof (gidx_t)
1157 			- (string_table_size & (sizeof (gidx_t) - 1)));
1158       total += iov[2].iov_len;
1159       ++idx;
1160     }
1161 
1162   header.hash_offset = total;
1163   header.hash_size = hash_size;
1164   iov[idx].iov_base = hash_table;
1165   iov[idx].iov_len = hash_size * sizeof (struct hash_entry);
1166   total += iov[idx].iov_len;
1167   ++idx;
1168 
1169   header.module_offset = total;
1170   iov[idx].iov_base = module_table;
1171   iov[idx].iov_len = nname_info * sizeof (struct module_entry);
1172   total += iov[idx].iov_len;
1173   ++idx;
1174 
1175   assert ((size_t) (cur_extra_table - extra_table)
1176 	  <= ((sizeof (struct extra_entry) + sizeof (gidx_t)
1177 	       + sizeof (struct extra_entry_module))
1178 	      * nextra_modules));
1179   header.otherconv_offset = total;
1180   iov[idx].iov_base = extra_table;
1181   iov[idx].iov_len = cur_extra_table - extra_table;
1182   total += iov[idx].iov_len;
1183   ++idx;
1184 
1185   if ((size_t) TEMP_FAILURE_RETRY (writev (fd, iov, idx)) != total
1186       /* The file was created with mode 0600.  Make it world-readable.  */
1187       || fchmod (fd, 0644) != 0
1188       /* Rename the file, possibly replacing an old one.  */
1189       || rename (tmpfname, output_file ?: finalname) != 0)
1190     {
1191       int save_errno = errno;
1192       close (fd);
1193       unlink (tmpfname);
1194       error (EXIT_FAILURE, save_errno,
1195 	     gettext ("cannot generate output file"));
1196     }
1197 
1198   close (fd);
1199 
1200   return 0;
1201 }
1202