mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-10-30 10:45:40 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			880 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			880 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Create simple DB database from textual input.
 | |
|    Copyright (C) 1996-2012 Free Software Foundation, Inc.
 | |
|    This file is part of the GNU C Library.
 | |
|    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
 | |
| 
 | |
|    The GNU C Library is free software; you can redistribute it and/or
 | |
|    modify it under the terms of the GNU Lesser General Public
 | |
|    License as published by the Free Software Foundation; either
 | |
|    version 2.1 of the License, or (at your option) any later version.
 | |
| 
 | |
|    The GNU C Library is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|    Lesser General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU Lesser General Public
 | |
|    License along with the GNU C Library; if not, see
 | |
|    <http://www.gnu.org/licenses/>.  */
 | |
| 
 | |
| #include <argp.h>
 | |
| #include <assert.h>
 | |
| #include <ctype.h>
 | |
| #include <errno.h>
 | |
| #include <error.h>
 | |
| #include <fcntl.h>
 | |
| #include <inttypes.h>
 | |
| #include <libintl.h>
 | |
| #include <locale.h>
 | |
| #include <search.h>
 | |
| #include <stdbool.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| #include <sys/mman.h>
 | |
| #include <sys/param.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/uio.h>
 | |
| #include "nss_db/nss_db.h"
 | |
| 
 | |
| /* Get libc version number.  */
 | |
| #include "../version.h"
 | |
| 
 | |
| /* The hashing function we use.  */
 | |
| #include "../intl/hash-string.h"
 | |
| 
 | |
| /* SELinux support.  */
 | |
| #ifdef HAVE_SELINUX
 | |
| # include <selinux/selinux.h>
 | |
| #endif
 | |
| 
 | |
| #ifndef MAP_POPULATE
 | |
| # define MAP_POPULATE 0
 | |
| #endif
 | |
| 
 | |
| #define PACKAGE _libc_intl_domainname
 | |
| 
 | |
| /* List of data bases.  */
 | |
| struct database
 | |
| {
 | |
|   char dbid;
 | |
|   bool extra_string;
 | |
|   struct database *next;
 | |
|   void *entries;
 | |
|   size_t nentries;
 | |
|   size_t nhashentries;
 | |
|   stridx_t *hashtable;
 | |
|   size_t keystrlen;
 | |
|   stridx_t *keyidxtab;
 | |
|   char *keystrtab;
 | |
| } *databases;
 | |
| static size_t ndatabases;
 | |
| static size_t nhashentries_total;
 | |
| static size_t valstrlen;
 | |
| static void *valstrtree;
 | |
| static char *valstrtab;
 | |
| static size_t extrastrlen;
 | |
| 
 | |
| /* Database entry.  */
 | |
| struct dbentry
 | |
| {
 | |
|   stridx_t validx;
 | |
|   uint32_t hashval;
 | |
|   char str[0];
 | |
| };
 | |
| 
 | |
| /* Stored string entry.  */
 | |
| struct valstrentry
 | |
| {
 | |
|   stridx_t idx;
 | |
|   bool extra_string;
 | |
|   char str[0];
 | |
| };
 | |
| 
 | |
| 
 | |
| /* True if any entry has been added.  */
 | |
| static bool any_dbentry;
 | |
| 
 | |
| /* If non-zero convert key to lower case.  */
 | |
| static int to_lowercase;
 | |
| 
 | |
| /* If non-zero print content of input file, one entry per line.  */
 | |
| static int do_undo;
 | |
| 
 | |
| /* If non-zero do not print informational messages.  */
 | |
| static int be_quiet;
 | |
| 
 | |
| /* Name of output file.  */
 | |
| static const char *output_name;
 | |
| 
 | |
| /* Name and version of program.  */
 | |
| static void print_version (FILE *stream, struct argp_state *state);
 | |
| void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
 | |
| 
 | |
| /* Definitions of arguments for argp functions.  */
 | |
| static const struct argp_option options[] =
 | |
| {
 | |
|   { "fold-case", 'f', NULL, 0, N_("Convert key to lower case") },
 | |
|   { "output", 'o', N_("NAME"), 0, N_("Write output to file NAME") },
 | |
|   { "quiet", 'q', NULL, 0,
 | |
|     N_("Do not print messages while building database") },
 | |
|   { "undo", 'u', NULL, 0,
 | |
|     N_("Print content of database file, one entry a line") },
 | |
|   { "generated", 'g', N_("CHAR"), 0,
 | |
|     N_("Generated line not part of iteration") },
 | |
|   { NULL, 0, NULL, 0, NULL }
 | |
| };
 | |
| 
 | |
| /* Short description of program.  */
 | |
| static const char doc[] = N_("Create simple database from textual input.");
 | |
| 
 | |
| /* Strings for arguments in help texts.  */
 | |
| static const char args_doc[] = N_("\
 | |
| INPUT-FILE OUTPUT-FILE\n-o OUTPUT-FILE INPUT-FILE\n-u INPUT-FILE");
 | |
| 
 | |
| /* Prototype for option handler.  */
 | |
| static error_t parse_opt (int key, char *arg, struct argp_state *state);
 | |
| 
 | |
| /* Function to print some extra text in the help message.  */
 | |
| static char *more_help (int key, const char *text, void *input);
 | |
| 
 | |
| /* Data structure to communicate with argp functions.  */
 | |
| static struct argp argp =
 | |
| {
 | |
|   options, parse_opt, args_doc, doc, NULL, more_help
 | |
| };
 | |
| 
 | |
| 
 | |
| /* List of databases which are not part of the iteration table.  */
 | |
| static struct db_option
 | |
| {
 | |
|   char dbid;
 | |
|   struct db_option *next;
 | |
| } *db_options;
 | |
| 
 | |
| 
 | |
| /* Prototypes for local functions.  */
 | |
| static int process_input (FILE *input, const char *inname,
 | |
| 			  int to_lowercase, int be_quiet);
 | |
| static int print_database (int fd);
 | |
| static void compute_tables (void);
 | |
| static int write_output (int fd);
 | |
| 
 | |
| /* SELinux support.  */
 | |
| #ifdef HAVE_SELINUX
 | |
| /* Set the SELinux file creation context for the given file. */
 | |
| static void set_file_creation_context (const char *outname, mode_t mode);
 | |
| static void reset_file_creation_context (void);
 | |
| #else
 | |
| # define set_file_creation_context(_outname,_mode)
 | |
| # define reset_file_creation_context()
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /* External functions.  */
 | |
| extern void *xmalloc (size_t n)
 | |
|   __attribute_malloc__ __attribute_alloc_size (1);
 | |
| extern void *xcalloc (size_t n, size_t s)
 | |
|   __attribute_malloc__ __attribute_alloc_size (1, 2);
 | |
| 
 | |
| 
 | |
| int
 | |
| main (int argc, char *argv[])
 | |
| {
 | |
|   const char *input_name;
 | |
|   FILE *input_file;
 | |
|   int remaining;
 | |
|   int mode = 0644;
 | |
| 
 | |
|   /* Set locale via LC_ALL.  */
 | |
|   setlocale (LC_ALL, "");
 | |
| 
 | |
|   /* Set the text message domain.  */
 | |
|   textdomain (_libc_intl_domainname);
 | |
| 
 | |
|   /* Initialize local variables.  */
 | |
|   input_name = NULL;
 | |
| 
 | |
|   /* Parse and process arguments.  */
 | |
|   argp_parse (&argp, argc, argv, 0, &remaining, NULL);
 | |
| 
 | |
|   /* Determine file names.  */
 | |
|   if (do_undo || output_name != NULL)
 | |
|     {
 | |
|       if (remaining + 1 != argc)
 | |
| 	{
 | |
| 	wrong_arguments:
 | |
| 	  error (0, 0, gettext ("wrong number of arguments"));
 | |
| 	  argp_help (&argp, stdout, ARGP_HELP_SEE,
 | |
| 		     program_invocation_short_name);
 | |
| 	  exit (1);
 | |
| 	}
 | |
|       input_name = argv[remaining];
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       if (remaining + 2 != argc)
 | |
| 	goto wrong_arguments;
 | |
| 
 | |
|       input_name = argv[remaining++];
 | |
|       output_name = argv[remaining];
 | |
|     }
 | |
| 
 | |
|   /* Special handling if we are asked to print the database.  */
 | |
|   if (do_undo)
 | |
|     {
 | |
|       int fd = open (input_name, O_RDONLY);
 | |
|       if (fd == -1)
 | |
| 	error (EXIT_FAILURE, errno, gettext ("cannot open database file `%s'"),
 | |
| 	       input_name);
 | |
| 
 | |
|       int status = print_database (fd);
 | |
| 
 | |
|       close (fd);
 | |
| 
 | |
|       return status;
 | |
|     }
 | |
| 
 | |
|   /* Open input file.  */
 | |
|   if (strcmp (input_name, "-") == 0 || strcmp (input_name, "/dev/stdin") == 0)
 | |
|     input_file = stdin;
 | |
|   else
 | |
|     {
 | |
|       struct stat64 st;
 | |
| 
 | |
|       input_file = fopen64 (input_name, "r");
 | |
|       if (input_file == NULL)
 | |
| 	error (EXIT_FAILURE, errno, gettext ("cannot open input file `%s'"),
 | |
| 	       input_name);
 | |
| 
 | |
|       /* Get the access rights from the source file.  The output file should
 | |
| 	 have the same.  */
 | |
|       if (fstat64 (fileno (input_file), &st) >= 0)
 | |
| 	mode = st.st_mode & ACCESSPERMS;
 | |
|     }
 | |
| 
 | |
|   /* Start the real work.  */
 | |
|   int status = process_input (input_file, input_name, to_lowercase, be_quiet);
 | |
| 
 | |
|   /* Close files.  */
 | |
|   if (input_file != stdin)
 | |
|     fclose (input_file);
 | |
| 
 | |
|   /* No need to continue when we did not read the file successfully.  */
 | |
|   if (status != EXIT_SUCCESS)
 | |
|     return status;
 | |
| 
 | |
|   /* Bail out if nothing is to be done.  */
 | |
|   if (!any_dbentry)
 | |
|     {
 | |
|       if (be_quiet)
 | |
| 	return EXIT_SUCCESS;
 | |
|       else
 | |
| 	error (EXIT_SUCCESS, 0, gettext ("no entries to be processed"));
 | |
|     }
 | |
| 
 | |
|   /* Compute hash and string tables.  */
 | |
|   compute_tables ();
 | |
| 
 | |
|   /* Open output file.  This must not be standard output so we don't
 | |
|      handle "-" and "/dev/stdout" special.  */
 | |
|   char *tmp_output_name;
 | |
|   if (asprintf (&tmp_output_name, "%s.XXXXXX", output_name) == -1)
 | |
|     error (EXIT_FAILURE, errno, gettext ("cannot create temporary file name"));
 | |
| 
 | |
|   set_file_creation_context (output_name, mode);
 | |
|   int fd = mkstemp (tmp_output_name);
 | |
|   reset_file_creation_context ();
 | |
|   if (fd == -1)
 | |
|     error (EXIT_FAILURE, errno, gettext ("cannot create temporary file"));
 | |
| 
 | |
|   status = write_output (fd);
 | |
| 
 | |
|   if (status == EXIT_SUCCESS)
 | |
|     {
 | |
|       struct stat64 st;
 | |
| 
 | |
|       if (fstat64 (fd, &st) == 0)
 | |
| 	{
 | |
| 	  if ((st.st_mode & ACCESSPERMS) != mode)
 | |
| 	    /* We ignore problems with changing the mode.  */
 | |
| 	    fchmod (fd, mode);
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  error (0, errno, gettext ("cannot stat newly created file"));
 | |
| 	  status = EXIT_FAILURE;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   close (fd);
 | |
| 
 | |
|   if (status == EXIT_SUCCESS)
 | |
|     {
 | |
|       if (rename (tmp_output_name, output_name) != 0)
 | |
| 	{
 | |
| 	  error (0, errno, gettext ("cannot rename temporary file"));
 | |
| 	  status = EXIT_FAILURE;
 | |
| 	  goto do_unlink;
 | |
| 	}
 | |
|     }
 | |
|   else
 | |
|   do_unlink:
 | |
|     unlink (tmp_output_name);
 | |
| 
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* Handle program arguments.  */
 | |
| static error_t
 | |
| parse_opt (int key, char *arg, struct argp_state *state)
 | |
| {
 | |
|   struct db_option *newp;
 | |
| 
 | |
|   switch (key)
 | |
|     {
 | |
|     case 'f':
 | |
|       to_lowercase = 1;
 | |
|       break;
 | |
|     case 'o':
 | |
|       output_name = arg;
 | |
|       break;
 | |
|     case 'q':
 | |
|       be_quiet = 1;
 | |
|       break;
 | |
|     case 'u':
 | |
|       do_undo = 1;
 | |
|       break;
 | |
|     case 'g':
 | |
|       newp = xmalloc (sizeof (*newp));
 | |
|       newp->dbid = arg[0];
 | |
|       newp->next = db_options;
 | |
|       db_options = newp;
 | |
|       break;
 | |
|     default:
 | |
|       return ARGP_ERR_UNKNOWN;
 | |
|     }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static char *
 | |
| more_help (int key, const char *text, void *input)
 | |
| {
 | |
|   switch (key)
 | |
|     {
 | |
|     case ARGP_KEY_HELP_EXTRA:
 | |
|       /* We print some extra information.  */
 | |
|       return strdup (gettext ("\
 | |
| For bug reporting instructions, please see:\n\
 | |
| <http://www.gnu.org/software/libc/bugs.html>.\n"));
 | |
|     default:
 | |
|       break;
 | |
|     }
 | |
|   return (char *) text;
 | |
| }
 | |
| 
 | |
| /* Print the version information.  */
 | |
| static void
 | |
| print_version (FILE *stream, struct argp_state *state)
 | |
| {
 | |
|   fprintf (stream, "makedb (GNU %s) %s\n", PACKAGE, VERSION);
 | |
|   fprintf (stream, gettext ("\
 | |
| Copyright (C) %s Free Software Foundation, Inc.\n\
 | |
| This is free software; see the source for copying conditions.  There is NO\n\
 | |
| warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
 | |
| "), "2012");
 | |
|   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| dbentry_compare (const void *p1, const void *p2)
 | |
| {
 | |
|   const struct dbentry *d1 = (const struct dbentry *) p1;
 | |
|   const struct dbentry *d2 = (const struct dbentry *) p2;
 | |
| 
 | |
|   if (d1->hashval != d2->hashval)
 | |
|     return d1->hashval < d2->hashval ? -1 : 1;
 | |
| 
 | |
|   return strcmp (d1->str, d2->str);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| valstr_compare (const void *p1, const void *p2)
 | |
| {
 | |
|   const struct valstrentry *d1 = (const struct valstrentry *) p1;
 | |
|   const struct valstrentry *d2 = (const struct valstrentry *) p2;
 | |
| 
 | |
|   return strcmp (d1->str, d2->str);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| process_input (input, inname, to_lowercase, be_quiet)
 | |
|      FILE *input;
 | |
|      const char *inname;
 | |
|      int to_lowercase;
 | |
|      int be_quiet;
 | |
| {
 | |
|   char *line;
 | |
|   size_t linelen;
 | |
|   int status;
 | |
|   size_t linenr;
 | |
| 
 | |
|   line = NULL;
 | |
|   linelen = 0;
 | |
|   status = EXIT_SUCCESS;
 | |
|   linenr = 0;
 | |
| 
 | |
|   struct database *last_database = NULL;
 | |
| 
 | |
|   while (!feof_unlocked (input))
 | |
|     {
 | |
|       ssize_t n = getline (&line, &linelen, input);
 | |
|       if (n < 0)
 | |
| 	/* This means end of file or some bug.  */
 | |
| 	break;
 | |
|       if (n == 0)
 | |
| 	/* Short read.  Probably interrupted system call. */
 | |
| 	continue;
 | |
| 
 | |
|       ++linenr;
 | |
| 
 | |
|       if (line[n - 1] == '\n')
 | |
| 	/* Remove trailing newline.  */
 | |
| 	line[--n] = '\0';
 | |
| 
 | |
|       char *cp = line;
 | |
|       while (isspace (*cp))
 | |
| 	++cp;
 | |
| 
 | |
|       if (*cp == '#' || *cp == '\0')
 | |
| 	/* First non-space character in line '#': it's a comment.
 | |
| 	   Also go to the next line if it is empty except for whitespaces. */
 | |
| 	continue;
 | |
| 
 | |
|       /* Skip over the character indicating the database so that it is not
 | |
| 	 affected by TO_LOWERCASE.  */
 | |
|       char *key = cp++;
 | |
|       while (*cp != '\0' && !isspace (*cp))
 | |
| 	{
 | |
| 	  if (to_lowercase)
 | |
| 	    *cp = tolower (*cp);
 | |
| 	  ++cp;
 | |
| 	}
 | |
| 
 | |
|       if (*cp == '\0')
 | |
| 	/* It's a line without a value field.  */
 | |
| 	continue;
 | |
| 
 | |
|       *cp++ = '\0';
 | |
|       size_t keylen = cp - key;
 | |
| 
 | |
|       while (isspace (*cp))
 | |
| 	++cp;
 | |
| 
 | |
|       char *data = cp;
 | |
|       size_t datalen = (&line[n] - cp) + 1;
 | |
| 
 | |
|       /* Find the database.  */
 | |
|       if (last_database == NULL || last_database->dbid != key[0])
 | |
| 	{
 | |
| 	  last_database = databases;
 | |
| 	  while (last_database != NULL && last_database->dbid != key[0])
 | |
| 	    last_database = last_database->next;
 | |
| 
 | |
| 	  if (last_database == NULL)
 | |
| 	    {
 | |
| 	      last_database = xmalloc (sizeof (*last_database));
 | |
| 	      last_database->dbid = key[0];
 | |
| 	      last_database->extra_string = false;
 | |
| 	      last_database->next = databases;
 | |
| 	      last_database->entries = NULL;
 | |
| 	      last_database->nentries = 0;
 | |
| 	      last_database->keystrlen = 0;
 | |
| 	      databases = last_database;
 | |
| 
 | |
| 	      struct db_option *runp = db_options;
 | |
| 	      while (runp != NULL)
 | |
| 		if (runp->dbid == key[0])
 | |
| 		  {
 | |
| 		    last_database->extra_string = true;
 | |
| 		    break;
 | |
| 		  }
 | |
| 		else
 | |
| 		  runp = runp->next;
 | |
| 	    }
 | |
| 	}
 | |
| 
 | |
|       /* Skip the database selector.  */
 | |
|       ++key;
 | |
|       --keylen;
 | |
| 
 | |
|       /* Store the data.  */
 | |
|       struct valstrentry *nentry = xmalloc (sizeof (struct valstrentry)
 | |
| 					    + datalen);
 | |
|       if (last_database->extra_string)
 | |
| 	nentry->idx = extrastrlen;
 | |
|       else
 | |
| 	nentry->idx = valstrlen;
 | |
|       nentry->extra_string = last_database->extra_string;
 | |
|       memcpy (nentry->str, data, datalen);
 | |
| 
 | |
|       struct valstrentry **fdata = tsearch (nentry, &valstrtree,
 | |
| 					    valstr_compare);
 | |
|       if (fdata == NULL)
 | |
| 	error (EXIT_FAILURE, errno, gettext ("cannot create search tree"));
 | |
| 
 | |
|       if (*fdata != nentry)
 | |
| 	{
 | |
| 	  /* We can reuse a string.  */
 | |
| 	  free (nentry);
 | |
| 	  nentry = *fdata;
 | |
| 	}
 | |
|       else
 | |
| 	if (last_database->extra_string)
 | |
| 	  extrastrlen += datalen;
 | |
| 	else
 | |
| 	  valstrlen += datalen;
 | |
| 
 | |
|       /* Store the key.  */
 | |
|       struct dbentry *newp = xmalloc (sizeof (struct dbentry) + keylen);
 | |
|       newp->validx = nentry->idx;
 | |
|       newp->hashval = __hash_string (key);
 | |
|       memcpy (newp->str, key, keylen);
 | |
| 
 | |
|       struct dbentry **found = tsearch (newp, &last_database->entries,
 | |
| 					dbentry_compare);
 | |
|       if (found == NULL)
 | |
| 	error (EXIT_FAILURE, errno, gettext ("cannot create search tree"));
 | |
| 
 | |
|       if (*found != newp)
 | |
| 	{
 | |
| 	  free (newp);
 | |
| 	  if (!be_quiet)
 | |
| 	    error_at_line (0, 0, inname, linenr, gettext ("duplicate key"));
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       ++last_database->nentries;
 | |
|       last_database->keystrlen += keylen;
 | |
| 
 | |
|       any_dbentry = true;
 | |
|     }
 | |
| 
 | |
|   if (ferror_unlocked (input))
 | |
|     {
 | |
|       error (0, 0, gettext ("problems while reading `%s'"), inname);
 | |
|       status = EXIT_FAILURE;
 | |
|     }
 | |
| 
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| copy_valstr (const void *nodep, const VISIT which, const int depth)
 | |
| {
 | |
|   if (which != leaf && which != postorder)
 | |
|     return;
 | |
| 
 | |
|   const struct valstrentry *p = *(const struct valstrentry **) nodep;
 | |
| 
 | |
|   strcpy (valstrtab + (p->extra_string ? valstrlen : 0) + p->idx, p->str);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| is_prime (size_t candidate)
 | |
| {
 | |
|   /* No even number and none less than 10 will be passed here.  */
 | |
|   size_t divn = 3;
 | |
|   size_t sq = divn * divn;
 | |
| 
 | |
|   while (sq < candidate && candidate % divn != 0)
 | |
|     {
 | |
|       ++divn;
 | |
|       sq += 4 * divn;
 | |
|       ++divn;
 | |
|     }
 | |
| 
 | |
|   return candidate % divn != 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static size_t
 | |
| next_prime (size_t seed)
 | |
| {
 | |
|   /* Make it definitely odd.  */
 | |
|   seed |= 1;
 | |
| 
 | |
|   while (!is_prime (seed))
 | |
|     seed += 2;
 | |
| 
 | |
|   return seed;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| compute_tables (void)
 | |
| {
 | |
|   valstrtab = xmalloc (roundup (valstrlen + extrastrlen, sizeof (stridx_t)));
 | |
|   while ((valstrlen + extrastrlen) % sizeof (stridx_t) != 0)
 | |
|     valstrtab[valstrlen++] = '\0';
 | |
|   twalk (valstrtree, copy_valstr);
 | |
| 
 | |
|   static struct database *db;
 | |
|   for (db = databases; db != NULL; db = db->next)
 | |
|     if (db->nentries != 0)
 | |
|       {
 | |
| 	++ndatabases;
 | |
| 
 | |
| 	/* We simply use an odd number large than twice the number of
 | |
| 	   elements to store in the hash table for the size.  This gives
 | |
| 	   enough efficiency.  */
 | |
| #define TEST_RANGE 30
 | |
| 	size_t nhashentries_min = next_prime (db->nentries < TEST_RANGE
 | |
| 					      ? db->nentries
 | |
| 					      : db->nentries * 2 - TEST_RANGE);
 | |
| 	size_t nhashentries_max = MAX (nhashentries_min, db->nentries * 4);
 | |
| 	size_t nhashentries_best = nhashentries_min;
 | |
| 	size_t chainlength_best = db->nentries;
 | |
| 
 | |
| 	db->hashtable = xmalloc (2 * nhashentries_max * sizeof (stridx_t)
 | |
| 				 + db->keystrlen);
 | |
| 	db->keyidxtab = db->hashtable + nhashentries_max;
 | |
| 	db->keystrtab = (char *) (db->keyidxtab + nhashentries_max);
 | |
| 
 | |
| 	static size_t max_chainlength;
 | |
| 	static char *wp;
 | |
| 	static size_t nhashentries;
 | |
| 	static bool copy_string;
 | |
| 
 | |
| 	void add_key(const void *nodep, const VISIT which, const int depth)
 | |
| 	{
 | |
| 	  if (which != leaf && which != postorder)
 | |
| 	    return;
 | |
| 
 | |
| 	  const struct dbentry *dbe = *(const struct dbentry **) nodep;
 | |
| 
 | |
| 	  ptrdiff_t stridx;
 | |
| 	  if (copy_string)
 | |
| 	    {
 | |
| 	      stridx = wp - db->keystrtab;
 | |
| 	      wp = stpcpy (wp, dbe->str) + 1;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    stridx = 0;
 | |
| 
 | |
| 	  size_t hidx = dbe->hashval % nhashentries;
 | |
| 	  size_t hval2 = 1 + dbe->hashval % (nhashentries - 2);
 | |
| 	  size_t chainlength = 0;
 | |
| 
 | |
| 	  while (db->hashtable[hidx] != ~((stridx_t) 0))
 | |
| 	    {
 | |
| 	      ++chainlength;
 | |
| 	      if ((hidx += hval2) >= nhashentries)
 | |
| 		hidx -= nhashentries;
 | |
| 	    }
 | |
| 
 | |
| 	  db->hashtable[hidx] = ((db->extra_string ? valstrlen : 0)
 | |
| 				 + dbe->validx);
 | |
| 	  db->keyidxtab[hidx] = stridx;
 | |
| 
 | |
| 	  max_chainlength = MAX (max_chainlength, chainlength);
 | |
| 	}
 | |
| 
 | |
| 	copy_string = false;
 | |
| 	nhashentries = nhashentries_min;
 | |
| 	for (size_t cnt = 0; cnt < TEST_RANGE; ++cnt)
 | |
| 	  {
 | |
| 	    memset (db->hashtable, '\xff', nhashentries * sizeof (stridx_t));
 | |
| 
 | |
| 	    max_chainlength = 0;
 | |
| 	    wp = db->keystrtab;
 | |
| 
 | |
| 	    twalk (db->entries, add_key);
 | |
| 
 | |
| 	    if (max_chainlength == 0)
 | |
| 	      {
 | |
| 		/* No need to look further, this is as good as it gets.  */
 | |
| 		nhashentries_best = nhashentries;
 | |
| 		break;
 | |
| 	      }
 | |
| 
 | |
| 	    if (max_chainlength < chainlength_best)
 | |
| 	      {
 | |
| 		chainlength_best = max_chainlength;
 | |
| 		nhashentries_best = nhashentries;
 | |
| 	      }
 | |
| 
 | |
| 	    nhashentries = next_prime (nhashentries + 1);
 | |
| 	    if (nhashentries > nhashentries_max)
 | |
| 	      break;
 | |
| 	  }
 | |
| 
 | |
| 	/* Recompute the best table again, this time fill in the strings.  */
 | |
| 	nhashentries = nhashentries_best;
 | |
| 	memset (db->hashtable, '\xff',
 | |
| 		2 * nhashentries_max * sizeof (stridx_t));
 | |
| 	copy_string = true;
 | |
| 	wp = db->keystrtab;
 | |
| 
 | |
| 	twalk (db->entries, add_key);
 | |
| 
 | |
| 	db->nhashentries = nhashentries_best;
 | |
| 	nhashentries_total += nhashentries_best;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| write_output (int fd)
 | |
| {
 | |
|   struct nss_db_header *header;
 | |
|   uint64_t file_offset = (sizeof (struct nss_db_header)
 | |
| 			  + (ndatabases * sizeof (header->dbs[0])));
 | |
|   header = alloca (file_offset);
 | |
| 
 | |
|   header->magic = NSS_DB_MAGIC;
 | |
|   header->ndbs = ndatabases;
 | |
|   header->valstroffset = file_offset;
 | |
|   header->valstrlen = valstrlen;
 | |
| 
 | |
|   size_t filled_dbs = 0;
 | |
|   struct iovec iov[2 + ndatabases * 3];
 | |
|   iov[0].iov_base = header;
 | |
|   iov[0].iov_len = file_offset;
 | |
| 
 | |
|   iov[1].iov_base = valstrtab;
 | |
|   iov[1].iov_len = valstrlen + extrastrlen;
 | |
|   file_offset += iov[1].iov_len;
 | |
| 
 | |
|   size_t keydataoffset = file_offset + nhashentries_total * sizeof (stridx_t);
 | |
|   for (struct database *db = databases; db != NULL; db = db->next)
 | |
|     if (db->entries != NULL)
 | |
|       {
 | |
| 	assert (file_offset % sizeof (stridx_t) == 0);
 | |
| 	assert (filled_dbs < ndatabases);
 | |
| 
 | |
| 	header->dbs[filled_dbs].id = db->dbid;
 | |
| 	memset (header->dbs[filled_dbs].pad, '\0',
 | |
| 		sizeof (header->dbs[0].pad));
 | |
| 	header->dbs[filled_dbs].hashsize = db->nhashentries;
 | |
| 
 | |
| 	iov[2 + filled_dbs].iov_base = db->hashtable;
 | |
| 	iov[2 + filled_dbs].iov_len = db->nhashentries * sizeof (stridx_t);
 | |
| 	header->dbs[filled_dbs].hashoffset = file_offset;
 | |
| 	file_offset += iov[2 + filled_dbs].iov_len;
 | |
| 
 | |
| 	iov[2 + ndatabases + filled_dbs * 2].iov_base = db->keyidxtab;
 | |
| 	iov[2 + ndatabases + filled_dbs * 2].iov_len
 | |
| 	  = db->nhashentries * sizeof (stridx_t);
 | |
| 	header->dbs[filled_dbs].keyidxoffset = keydataoffset;
 | |
| 	keydataoffset += iov[2 + ndatabases + filled_dbs * 2].iov_len;
 | |
| 
 | |
| 	iov[3 + ndatabases + filled_dbs * 2].iov_base = db->keystrtab;
 | |
| 	iov[3 + ndatabases + filled_dbs * 2].iov_len = db->keystrlen;
 | |
| 	header->dbs[filled_dbs].keystroffset = keydataoffset;
 | |
| 	keydataoffset += iov[3 + ndatabases + filled_dbs * 2].iov_len;
 | |
| 
 | |
| 	++filled_dbs;
 | |
|       }
 | |
| 
 | |
|   assert (filled_dbs == ndatabases);
 | |
|   assert (file_offset == (iov[0].iov_len + iov[1].iov_len
 | |
| 			  + nhashentries_total * sizeof (stridx_t)));
 | |
|   header->allocate = file_offset;
 | |
| 
 | |
|   if (writev (fd, iov, 2 + ndatabases * 3) != keydataoffset)
 | |
|     {
 | |
|       error (0, errno, gettext ("failed to write new database file"));
 | |
|       return EXIT_FAILURE;
 | |
|     }
 | |
| 
 | |
|   return EXIT_SUCCESS;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| print_database (int fd)
 | |
| {
 | |
|   struct stat64 st;
 | |
|   if (fstat64 (fd, &st) != 0)
 | |
|     error (EXIT_FAILURE, errno, gettext ("cannot stat database file"));
 | |
| 
 | |
|   const struct nss_db_header *header = mmap (NULL, st.st_size, PROT_READ,
 | |
| 					     MAP_PRIVATE|MAP_POPULATE, fd, 0);
 | |
|   if (header == MAP_FAILED)
 | |
|     error (EXIT_FAILURE, errno, gettext ("cannot map database file"));
 | |
| 
 | |
|   if (header->magic != NSS_DB_MAGIC)
 | |
|     error (EXIT_FAILURE, 0, gettext ("file not a database file"));
 | |
| 
 | |
|   const char *valstrtab = (const char *) header + header->valstroffset;
 | |
| 
 | |
|   for (unsigned int dbidx = 0; dbidx < header->ndbs; ++dbidx)
 | |
|     {
 | |
|       const stridx_t *stridxtab
 | |
| 	= ((const stridx_t *) ((const char *) header
 | |
| 			       + header->dbs[dbidx].keyidxoffset));
 | |
|       const char *keystrtab
 | |
| 	= (const char *) header + header->dbs[dbidx].keystroffset;
 | |
|       const stridx_t *hashtab
 | |
| 	= (const stridx_t *) ((const char *) header
 | |
| 			      + header->dbs[dbidx].hashoffset);
 | |
| 
 | |
|       for (uint32_t hidx = 0; hidx < header->dbs[dbidx].hashsize; ++hidx)
 | |
| 	if (hashtab[hidx] != ~((stridx_t) 0))
 | |
| 	  printf ("%c%s %s\n",
 | |
| 		  header->dbs[dbidx].id,
 | |
| 		  keystrtab + stridxtab[hidx],
 | |
| 		  valstrtab + hashtab[hidx]);
 | |
|     }
 | |
| 
 | |
|   return EXIT_SUCCESS;
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef HAVE_SELINUX
 | |
| static void
 | |
| set_file_creation_context (const char *outname, mode_t mode)
 | |
| {
 | |
|   static int enabled;
 | |
|   static int enforcing;
 | |
|   security_context_t ctx;
 | |
| 
 | |
|   /* Check if SELinux is enabled, and remember. */
 | |
|   if (enabled == 0)
 | |
|     enabled = is_selinux_enabled () ? 1 : -1;
 | |
|   if (enabled < 0)
 | |
|     return;
 | |
| 
 | |
|   /* Check if SELinux is enforcing, and remember. */
 | |
|   if (enforcing == 0)
 | |
|     enforcing = security_getenforce () ? 1 : -1;
 | |
| 
 | |
|   /* Determine the context which the file should have. */
 | |
|   ctx = NULL;
 | |
|   if (matchpathcon (outname, S_IFREG | mode, &ctx) == 0 && ctx != NULL)
 | |
|     {
 | |
|       if (setfscreatecon (ctx) != 0)
 | |
| 	error (enforcing > 0 ? EXIT_FAILURE : 0, 0,
 | |
| 	       gettext ("cannot set file creation context for `%s'"),
 | |
| 	       outname);
 | |
| 
 | |
|       freecon (ctx);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| reset_file_creation_context (void)
 | |
| {
 | |
|   setfscreatecon (NULL);
 | |
| }
 | |
| #endif
 |