(root)/
glibc-2.38/
nss/
makedb.c
       1  /* Create simple DB database from textual input.
       2     Copyright (C) 1996-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the 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     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  #include <argp.h>
      20  #include <assert.h>
      21  #include <ctype.h>
      22  #include <errno.h>
      23  #include <error.h>
      24  #include <fcntl.h>
      25  #include <inttypes.h>
      26  #include <libintl.h>
      27  #include <locale.h>
      28  #include <search.h>
      29  #include <stdbool.h>
      30  #include <stdio.h>
      31  #include <stdlib.h>
      32  #include <string.h>
      33  #include <unistd.h>
      34  #include <stdint.h>
      35  #include <sys/mman.h>
      36  #include <sys/param.h>
      37  #include <sys/stat.h>
      38  #include <sys/uio.h>
      39  #include "nss_db/nss_db.h"
      40  #include <libc-diag.h>
      41  
      42  /* Get libc version number.  */
      43  #include "../version.h"
      44  
      45  /* The hashing function we use.  */
      46  #include "../intl/hash-string.h"
      47  
      48  /* SELinux support.  */
      49  #ifdef HAVE_SELINUX
      50  # include <selinux/label.h>
      51  # include <selinux/selinux.h>
      52  #endif
      53  
      54  #ifndef MAP_POPULATE
      55  # define MAP_POPULATE 0
      56  #endif
      57  
      58  #define PACKAGE _libc_intl_domainname
      59  
      60  /* List of data bases.  */
      61  struct database
      62  {
      63    char dbid;
      64    bool extra_string;
      65    struct database *next;
      66    void *entries;
      67    size_t nentries;
      68    size_t nhashentries;
      69    stridx_t *hashtable;
      70    size_t keystrlen;
      71    stridx_t *keyidxtab;
      72    char *keystrtab;
      73  } *databases;
      74  static size_t ndatabases;
      75  static size_t nhashentries_total;
      76  static size_t valstrlen;
      77  static void *valstrtree;
      78  static char *valstrtab;
      79  static size_t extrastrlen;
      80  
      81  /* Database entry.  */
      82  struct dbentry
      83  {
      84    stridx_t validx;
      85    uint32_t hashval;
      86    char str[0];
      87  };
      88  
      89  /* Stored string entry.  */
      90  struct valstrentry
      91  {
      92    stridx_t idx;
      93    bool extra_string;
      94    char str[0];
      95  };
      96  
      97  
      98  /* True if any entry has been added.  */
      99  static bool any_dbentry;
     100  
     101  /* If non-zero convert key to lower case.  */
     102  static int to_lowercase;
     103  
     104  /* If non-zero print content of input file, one entry per line.  */
     105  static int do_undo;
     106  
     107  /* If non-zero do not print informational messages.  */
     108  static int be_quiet;
     109  
     110  /* Name of output file.  */
     111  static const char *output_name;
     112  
     113  /* Name and version of program.  */
     114  static void print_version (FILE *stream, struct argp_state *state);
     115  void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
     116  
     117  /* Definitions of arguments for argp functions.  */
     118  static const struct argp_option options[] =
     119  {
     120    { "fold-case", 'f', NULL, 0, N_("Convert key to lower case") },
     121    { "output", 'o', N_("NAME"), 0, N_("Write output to file NAME") },
     122    { "quiet", 'q', NULL, 0,
     123      N_("Do not print messages while building database") },
     124    { "undo", 'u', NULL, 0,
     125      N_("Print content of database file, one entry a line") },
     126    { "generated", 'g', N_("CHAR"), 0,
     127      N_("Generated line not part of iteration") },
     128    { NULL, 0, NULL, 0, NULL }
     129  };
     130  
     131  /* Short description of program.  */
     132  static const char doc[] = N_("Create simple database from textual input.");
     133  
     134  /* Strings for arguments in help texts.  */
     135  static const char args_doc[] = N_("\
     136  INPUT-FILE OUTPUT-FILE\n-o OUTPUT-FILE INPUT-FILE\n-u INPUT-FILE");
     137  
     138  /* Prototype for option handler.  */
     139  static error_t parse_opt (int key, char *arg, struct argp_state *state);
     140  
     141  /* Function to print some extra text in the help message.  */
     142  static char *more_help (int key, const char *text, void *input);
     143  
     144  /* Data structure to communicate with argp functions.  */
     145  static struct argp argp =
     146  {
     147    options, parse_opt, args_doc, doc, NULL, more_help
     148  };
     149  
     150  
     151  /* List of databases which are not part of the iteration table.  */
     152  static struct db_option
     153  {
     154    char dbid;
     155    struct db_option *next;
     156  } *db_options;
     157  
     158  
     159  /* Prototypes for local functions.  */
     160  static int process_input (FILE *input, const char *inname,
     161  			  int to_lowercase, int be_quiet);
     162  static int print_database (int fd);
     163  static void compute_tables (void);
     164  static int write_output (int fd);
     165  
     166  /* SELinux support.  */
     167  #ifdef HAVE_SELINUX
     168  /* Set the SELinux file creation context for the given file. */
     169  static void set_file_creation_context (const char *outname, mode_t mode);
     170  static void reset_file_creation_context (void);
     171  #else
     172  # define set_file_creation_context(_outname,_mode)
     173  # define reset_file_creation_context()
     174  #endif
     175  
     176  
     177  /* External functions.  */
     178  #include <programs/xmalloc.h>
     179  
     180  
     181  int
     182  main (int argc, char *argv[])
     183  {
     184    const char *input_name;
     185    FILE *input_file;
     186    int remaining;
     187    int mode = 0644;
     188  
     189    /* Set locale via LC_ALL.  */
     190    setlocale (LC_ALL, "");
     191  
     192    /* Set the text message domain.  */
     193    textdomain (_libc_intl_domainname);
     194  
     195    /* Initialize local variables.  */
     196    input_name = NULL;
     197  
     198    /* Parse and process arguments.  */
     199    argp_parse (&argp, argc, argv, 0, &remaining, NULL);
     200  
     201    /* Determine file names.  */
     202    if (do_undo || output_name != NULL)
     203      {
     204        if (remaining + 1 != argc)
     205  	{
     206  	wrong_arguments:
     207  	  error (0, 0, gettext ("wrong number of arguments"));
     208  	  argp_help (&argp, stdout, ARGP_HELP_SEE,
     209  		     program_invocation_short_name);
     210  	  exit (1);
     211  	}
     212        input_name = argv[remaining];
     213      }
     214    else
     215      {
     216        if (remaining + 2 != argc)
     217  	goto wrong_arguments;
     218  
     219        input_name = argv[remaining++];
     220        output_name = argv[remaining];
     221      }
     222  
     223    /* Special handling if we are asked to print the database.  */
     224    if (do_undo)
     225      {
     226        int fd = open (input_name, O_RDONLY);
     227        if (fd == -1)
     228  	error (EXIT_FAILURE, errno, gettext ("cannot open database file `%s'"),
     229  	       input_name);
     230  
     231        int status = print_database (fd);
     232  
     233        close (fd);
     234  
     235        return status;
     236      }
     237  
     238    /* Open input file.  */
     239    if (strcmp (input_name, "-") == 0 || strcmp (input_name, "/dev/stdin") == 0)
     240      input_file = stdin;
     241    else
     242      {
     243        struct stat64 st;
     244  
     245        input_file = fopen64 (input_name, "r");
     246        if (input_file == NULL)
     247  	error (EXIT_FAILURE, errno, gettext ("cannot open input file `%s'"),
     248  	       input_name);
     249  
     250        /* Get the access rights from the source file.  The output file should
     251  	 have the same.  */
     252        if (fstat64 (fileno (input_file), &st) >= 0)
     253  	mode = st.st_mode & ACCESSPERMS;
     254      }
     255  
     256    /* Start the real work.  */
     257    int status = process_input (input_file, input_name, to_lowercase, be_quiet);
     258  
     259    /* Close files.  */
     260    if (input_file != stdin)
     261      fclose (input_file);
     262  
     263    /* No need to continue when we did not read the file successfully.  */
     264    if (status != EXIT_SUCCESS)
     265      return status;
     266  
     267    /* Bail out if nothing is to be done.  */
     268    if (!any_dbentry)
     269      {
     270        if (be_quiet)
     271  	return EXIT_SUCCESS;
     272        else
     273  	error (EXIT_SUCCESS, 0, gettext ("no entries to be processed"));
     274      }
     275  
     276    /* Compute hash and string tables.  */
     277    compute_tables ();
     278  
     279    /* Open output file.  This must not be standard output so we don't
     280       handle "-" and "/dev/stdout" special.  */
     281    char *tmp_output_name;
     282    if (asprintf (&tmp_output_name, "%s.XXXXXX", output_name) == -1)
     283      error (EXIT_FAILURE, errno, gettext ("cannot create temporary file name"));
     284  
     285    set_file_creation_context (output_name, mode);
     286    int fd = mkstemp (tmp_output_name);
     287    reset_file_creation_context ();
     288    if (fd == -1)
     289      error (EXIT_FAILURE, errno, gettext ("cannot create temporary file"));
     290  
     291    status = write_output (fd);
     292  
     293    if (status == EXIT_SUCCESS)
     294      {
     295        struct stat64 st;
     296  
     297        if (fstat64 (fd, &st) == 0)
     298  	{
     299  	  if ((st.st_mode & ACCESSPERMS) != mode)
     300  	    /* We ignore problems with changing the mode.  */
     301  	    fchmod (fd, mode);
     302  	}
     303        else
     304  	{
     305  	  error (0, errno, gettext ("cannot stat newly created file"));
     306  	  status = EXIT_FAILURE;
     307  	}
     308      }
     309  
     310    close (fd);
     311  
     312    if (status == EXIT_SUCCESS)
     313      {
     314        if (rename (tmp_output_name, output_name) != 0)
     315  	{
     316  	  error (0, errno, gettext ("cannot rename temporary file"));
     317  	  status = EXIT_FAILURE;
     318  	  goto do_unlink;
     319  	}
     320      }
     321    else
     322    do_unlink:
     323      unlink (tmp_output_name);
     324  
     325    return status;
     326  }
     327  
     328  
     329  /* Handle program arguments.  */
     330  static error_t
     331  parse_opt (int key, char *arg, struct argp_state *state)
     332  {
     333    struct db_option *newp;
     334  
     335    switch (key)
     336      {
     337      case 'f':
     338        to_lowercase = 1;
     339        break;
     340      case 'o':
     341        output_name = arg;
     342        break;
     343      case 'q':
     344        be_quiet = 1;
     345        break;
     346      case 'u':
     347        do_undo = 1;
     348        break;
     349      case 'g':
     350        newp = xmalloc (sizeof (*newp));
     351        newp->dbid = arg[0];
     352        newp->next = db_options;
     353        db_options = newp;
     354        break;
     355      default:
     356        return ARGP_ERR_UNKNOWN;
     357      }
     358    return 0;
     359  }
     360  
     361  
     362  static char *
     363  more_help (int key, const char *text, void *input)
     364  {
     365    char *tp = NULL;
     366    switch (key)
     367      {
     368      case ARGP_KEY_HELP_EXTRA:
     369        /* We print some extra information.  */
     370        if (asprintf (&tp, gettext ("\
     371  For bug reporting instructions, please see:\n\
     372  %s.\n"), REPORT_BUGS_TO) < 0)
     373  	return NULL;
     374        return tp;
     375      default:
     376        break;
     377      }
     378    return (char *) text;
     379  }
     380  
     381  /* Print the version information.  */
     382  static void
     383  print_version (FILE *stream, struct argp_state *state)
     384  {
     385    fprintf (stream, "makedb %s%s\n", PKGVERSION, VERSION);
     386    fprintf (stream, gettext ("\
     387  Copyright (C) %s Free Software Foundation, Inc.\n\
     388  This is free software; see the source for copying conditions.  There is NO\n\
     389  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
     390  "), "2023");
     391    fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
     392  }
     393  
     394  
     395  static int
     396  dbentry_compare (const void *p1, const void *p2)
     397  {
     398    const struct dbentry *d1 = (const struct dbentry *) p1;
     399    const struct dbentry *d2 = (const struct dbentry *) p2;
     400  
     401    if (d1->hashval != d2->hashval)
     402      return d1->hashval < d2->hashval ? -1 : 1;
     403  
     404    return strcmp (d1->str, d2->str);
     405  }
     406  
     407  
     408  static int
     409  valstr_compare (const void *p1, const void *p2)
     410  {
     411    const struct valstrentry *d1 = (const struct valstrentry *) p1;
     412    const struct valstrentry *d2 = (const struct valstrentry *) p2;
     413  
     414    return strcmp (d1->str, d2->str);
     415  }
     416  
     417  
     418  static int
     419  process_input (FILE *input, const char *inname, int to_lowercase, int be_quiet)
     420  {
     421    char *line;
     422    size_t linelen;
     423    int status;
     424    size_t linenr;
     425  
     426    line = NULL;
     427    linelen = 0;
     428    status = EXIT_SUCCESS;
     429    linenr = 0;
     430  
     431    struct database *last_database = NULL;
     432  
     433    while (!feof_unlocked (input))
     434      {
     435        ssize_t n = getline (&line, &linelen, input);
     436        if (n < 0)
     437  	/* This means end of file or some bug.  */
     438  	break;
     439        if (n == 0)
     440  	/* Short read.  Probably interrupted system call. */
     441  	continue;
     442  
     443        ++linenr;
     444  
     445        if (line[n - 1] == '\n')
     446  	/* Remove trailing newline.  */
     447  	line[--n] = '\0';
     448  
     449        char *cp = line;
     450        while (isspace (*cp))
     451  	++cp;
     452  
     453        if (*cp == '#' || *cp == '\0')
     454  	/* First non-space character in line '#': it's a comment.
     455  	   Also go to the next line if it is empty except for whitespaces. */
     456  	continue;
     457  
     458        /* Skip over the character indicating the database so that it is not
     459  	 affected by TO_LOWERCASE.  */
     460        char *key = cp++;
     461        while (*cp != '\0' && !isspace (*cp))
     462  	{
     463  	  if (to_lowercase)
     464  	    *cp = tolower (*cp);
     465  	  ++cp;
     466  	}
     467  
     468        if (*cp == '\0')
     469  	/* It's a line without a value field.  */
     470  	continue;
     471  
     472        *cp++ = '\0';
     473        size_t keylen = cp - key;
     474  
     475        while (isspace (*cp))
     476  	++cp;
     477  
     478        char *data = cp;
     479        size_t datalen = (&line[n] - cp) + 1;
     480  
     481        /* Find the database.  */
     482        if (last_database == NULL || last_database->dbid != key[0])
     483  	{
     484  	  last_database = databases;
     485  	  while (last_database != NULL && last_database->dbid != key[0])
     486  	    last_database = last_database->next;
     487  
     488  	  if (last_database == NULL)
     489  	    {
     490  	      last_database = xmalloc (sizeof (*last_database));
     491  	      last_database->dbid = key[0];
     492  	      last_database->extra_string = false;
     493  	      last_database->next = databases;
     494  	      last_database->entries = NULL;
     495  	      last_database->nentries = 0;
     496  	      last_database->keystrlen = 0;
     497  	      databases = last_database;
     498  
     499  	      struct db_option *runp = db_options;
     500  	      while (runp != NULL)
     501  		if (runp->dbid == key[0])
     502  		  {
     503  		    last_database->extra_string = true;
     504  		    break;
     505  		  }
     506  		else
     507  		  runp = runp->next;
     508  	    }
     509  	}
     510  
     511        /* Skip the database selector.  */
     512        ++key;
     513        --keylen;
     514  
     515        /* Store the data.  */
     516        struct valstrentry *nentry = xmalloc (sizeof (struct valstrentry)
     517  					    + datalen);
     518        if (last_database->extra_string)
     519  	nentry->idx = extrastrlen;
     520        else
     521  	nentry->idx = valstrlen;
     522        nentry->extra_string = last_database->extra_string;
     523        memcpy (nentry->str, data, datalen);
     524  
     525        struct valstrentry **fdata = tsearch (nentry, &valstrtree,
     526  					    valstr_compare);
     527        if (fdata == NULL)
     528  	error (EXIT_FAILURE, errno, gettext ("cannot create search tree"));
     529  
     530        if (*fdata != nentry)
     531  	{
     532  	  /* We can reuse a string.  */
     533  	  free (nentry);
     534  	  nentry = *fdata;
     535  	}
     536        else
     537  	if (last_database->extra_string)
     538  	  extrastrlen += datalen;
     539  	else
     540  	  valstrlen += datalen;
     541  
     542        /* Store the key.  */
     543        struct dbentry *newp = xmalloc (sizeof (struct dbentry) + keylen);
     544        newp->validx = nentry->idx;
     545        newp->hashval = __hash_string (key);
     546        memcpy (newp->str, key, keylen);
     547  
     548        struct dbentry **found = tsearch (newp, &last_database->entries,
     549  					dbentry_compare);
     550        if (found == NULL)
     551  	error (EXIT_FAILURE, errno, gettext ("cannot create search tree"));
     552  
     553        if (*found != newp)
     554  	{
     555  	  free (newp);
     556  	  if (!be_quiet)
     557  	    error_at_line (0, 0, inname, linenr, gettext ("duplicate key"));
     558  	  continue;
     559  	}
     560  
     561        ++last_database->nentries;
     562        last_database->keystrlen += keylen;
     563  
     564        any_dbentry = true;
     565      }
     566  
     567    if (ferror_unlocked (input))
     568      {
     569        error (0, 0, gettext ("problems while reading `%s'"), inname);
     570        status = EXIT_FAILURE;
     571      }
     572  
     573    return status;
     574  }
     575  
     576  
     577  static void
     578  copy_valstr (const void *nodep, const VISIT which, const int depth)
     579  {
     580    if (which != leaf && which != postorder)
     581      return;
     582  
     583    const struct valstrentry *p = *(const struct valstrentry **) nodep;
     584  
     585    strcpy (valstrtab + (p->extra_string ? valstrlen : 0) + p->idx, p->str);
     586  }
     587  
     588  
     589  /* Determine if the candidate is prime by using a modified trial division
     590     algorithm. The candidate must be both odd and greater than 4.  */
     591  static int
     592  is_prime (size_t candidate)
     593  {
     594    size_t divn = 3;
     595    size_t sq = divn * divn;
     596  
     597    assert (candidate > 4 && candidate % 2 != 0);
     598  
     599    while (sq < candidate && candidate % divn != 0)
     600      {
     601        ++divn;
     602        sq += 4 * divn;
     603        ++divn;
     604      }
     605  
     606    return candidate % divn != 0;
     607  }
     608  
     609  
     610  static size_t
     611  next_prime (size_t seed)
     612  {
     613    /* Make sure that we're always greater than 4.  */
     614    seed = (seed + 4) | 1;
     615  
     616    while (!is_prime (seed))
     617      seed += 2;
     618  
     619    return seed;
     620  }
     621  
     622  static size_t max_chainlength;
     623  static char *wp;
     624  static size_t nhashentries;
     625  static bool copy_string;
     626  
     627  void add_key(const void *nodep, VISIT which, void *arg)
     628  {
     629    if (which != leaf && which != postorder)
     630      return;
     631  
     632    const struct database *db = (const struct database *) arg;
     633    const struct dbentry *dbe = *(const struct dbentry **) nodep;
     634  
     635    ptrdiff_t stridx;
     636    if (copy_string)
     637      {
     638        stridx = wp - db->keystrtab;
     639        wp = stpcpy (wp, dbe->str) + 1;
     640      }
     641    else
     642      stridx = 0;
     643  
     644    size_t hidx = dbe->hashval % nhashentries;
     645    size_t hval2 = 1 + dbe->hashval % (nhashentries - 2);
     646    size_t chainlength = 0;
     647  
     648    while (db->hashtable[hidx] != ~((stridx_t) 0))
     649      {
     650        ++chainlength;
     651        if ((hidx += hval2) >= nhashentries)
     652  	hidx -= nhashentries;
     653      }
     654  
     655    db->hashtable[hidx] = ((db->extra_string ? valstrlen : 0)
     656  			     + dbe->validx);
     657    db->keyidxtab[hidx] = stridx;
     658  
     659    max_chainlength = MAX (max_chainlength, chainlength);
     660  }
     661  
     662  static void
     663  compute_tables (void)
     664  {
     665    valstrtab = xmalloc (roundup (valstrlen + extrastrlen, sizeof (stridx_t)));
     666    while ((valstrlen + extrastrlen) % sizeof (stridx_t) != 0)
     667      valstrtab[valstrlen++] = '\0';
     668    twalk (valstrtree, copy_valstr);
     669  
     670    static struct database *db;
     671    for (db = databases; db != NULL; db = db->next)
     672      if (db->nentries != 0)
     673        {
     674  	++ndatabases;
     675  
     676  	/* We simply use an odd number large than twice the number of
     677  	   elements to store in the hash table for the size.  This gives
     678  	   enough efficiency.  */
     679  #define TEST_RANGE 30
     680  	size_t nhashentries_min = next_prime (db->nentries < TEST_RANGE
     681  					      ? db->nentries
     682  					      : db->nentries * 2 - TEST_RANGE);
     683  	size_t nhashentries_max = MAX (nhashentries_min, db->nentries * 4);
     684  	size_t nhashentries_best = nhashentries_min;
     685  	size_t chainlength_best = db->nentries;
     686  
     687  	db->hashtable = xmalloc (2 * nhashentries_max * sizeof (stridx_t)
     688  				 + db->keystrlen);
     689  	db->keyidxtab = db->hashtable + nhashentries_max;
     690  	db->keystrtab = (char *) (db->keyidxtab + nhashentries_max);
     691  
     692  	copy_string = false;
     693  	nhashentries = nhashentries_min;
     694  	for (size_t cnt = 0; cnt < TEST_RANGE; ++cnt)
     695  	  {
     696  	    memset (db->hashtable, '\xff', nhashentries * sizeof (stridx_t));
     697  
     698  	    max_chainlength = 0;
     699  	    wp = db->keystrtab;
     700  
     701  	    twalk_r (db->entries, add_key, db);
     702  
     703  	    if (max_chainlength == 0)
     704  	      {
     705  		/* No need to look further, this is as good as it gets.  */
     706  		nhashentries_best = nhashentries;
     707  		break;
     708  	      }
     709  
     710  	    if (max_chainlength < chainlength_best)
     711  	      {
     712  		chainlength_best = max_chainlength;
     713  		nhashentries_best = nhashentries;
     714  	      }
     715  
     716  	    nhashentries = next_prime (nhashentries + 1);
     717  	    if (nhashentries > nhashentries_max)
     718  	      break;
     719  	  }
     720  
     721  	/* Recompute the best table again, this time fill in the strings.  */
     722  	nhashentries = nhashentries_best;
     723  	memset (db->hashtable, '\xff',
     724  		2 * nhashentries_max * sizeof (stridx_t));
     725  	copy_string = true;
     726  	wp = db->keystrtab;
     727  
     728  	twalk_r (db->entries, add_key, db);
     729  
     730  	db->nhashentries = nhashentries_best;
     731  	nhashentries_total += nhashentries_best;
     732      }
     733  }
     734  
     735  
     736  static int
     737  write_output (int fd)
     738  {
     739    struct nss_db_header *header;
     740    uint64_t file_offset = (sizeof (struct nss_db_header)
     741  			  + (ndatabases * sizeof (header->dbs[0])));
     742    header = alloca (file_offset);
     743  
     744    header->magic = NSS_DB_MAGIC;
     745    header->ndbs = ndatabases;
     746    header->valstroffset = file_offset;
     747    header->valstrlen = valstrlen;
     748  
     749    size_t filled_dbs = 0;
     750    size_t iov_nelts = 2 + ndatabases * 3;
     751    struct iovec iov[iov_nelts];
     752    iov[0].iov_base = header;
     753    iov[0].iov_len = file_offset;
     754  
     755    iov[1].iov_base = valstrtab;
     756    iov[1].iov_len = valstrlen + extrastrlen;
     757    file_offset += iov[1].iov_len;
     758  
     759    size_t keydataoffset = file_offset + nhashentries_total * sizeof (stridx_t);
     760    for (struct database *db = databases; db != NULL; db = db->next)
     761      if (db->entries != NULL)
     762        {
     763  	assert (file_offset % sizeof (stridx_t) == 0);
     764  	assert (filled_dbs < ndatabases);
     765  
     766  	header->dbs[filled_dbs].id = db->dbid;
     767  	memset (header->dbs[filled_dbs].pad, '\0',
     768  		sizeof (header->dbs[0].pad));
     769  	header->dbs[filled_dbs].hashsize = db->nhashentries;
     770  
     771  	iov[2 + filled_dbs].iov_base = db->hashtable;
     772  	iov[2 + filled_dbs].iov_len = db->nhashentries * sizeof (stridx_t);
     773  	header->dbs[filled_dbs].hashoffset = file_offset;
     774  	file_offset += iov[2 + filled_dbs].iov_len;
     775  
     776  	iov[2 + ndatabases + filled_dbs * 2].iov_base = db->keyidxtab;
     777  	iov[2 + ndatabases + filled_dbs * 2].iov_len
     778  	  = db->nhashentries * sizeof (stridx_t);
     779  	header->dbs[filled_dbs].keyidxoffset = keydataoffset;
     780  	keydataoffset += iov[2 + ndatabases + filled_dbs * 2].iov_len;
     781  
     782  	iov[3 + ndatabases + filled_dbs * 2].iov_base = db->keystrtab;
     783  	iov[3 + ndatabases + filled_dbs * 2].iov_len = db->keystrlen;
     784  	header->dbs[filled_dbs].keystroffset = keydataoffset;
     785  	keydataoffset += iov[3 + ndatabases + filled_dbs * 2].iov_len;
     786  
     787  	++filled_dbs;
     788        }
     789  
     790    assert (filled_dbs == ndatabases);
     791    assert (file_offset == (iov[0].iov_len + iov[1].iov_len
     792  			  + nhashentries_total * sizeof (stridx_t)));
     793    header->allocate = file_offset;
     794  
     795  #if __GNUC_PREREQ (10, 0) && !__GNUC_PREREQ (11, 0)
     796    DIAG_PUSH_NEEDS_COMMENT;
     797    /* Avoid GCC 10 false positive warning: specified size exceeds maximum
     798       object size.  */
     799    DIAG_IGNORE_NEEDS_COMMENT (10, "-Wstringop-overflow");
     800  #endif
     801  
     802    assert (iov_nelts <= INT_MAX);
     803    if (writev (fd, iov, iov_nelts) != keydataoffset)
     804      {
     805        error (0, errno, gettext ("failed to write new database file"));
     806        return EXIT_FAILURE;
     807      }
     808  
     809  #if __GNUC_PREREQ (10, 0) && !__GNUC_PREREQ (11, 0)
     810    DIAG_POP_NEEDS_COMMENT;
     811  #endif
     812  
     813    return EXIT_SUCCESS;
     814  }
     815  
     816  
     817  static int
     818  print_database (int fd)
     819  {
     820    struct stat64 st;
     821    if (fstat64 (fd, &st) != 0)
     822      error (EXIT_FAILURE, errno, gettext ("cannot stat database file"));
     823  
     824    const struct nss_db_header *header = mmap (NULL, st.st_size, PROT_READ,
     825  					     MAP_PRIVATE|MAP_POPULATE, fd, 0);
     826    if (header == MAP_FAILED)
     827      error (EXIT_FAILURE, errno, gettext ("cannot map database file"));
     828  
     829    if (header->magic != NSS_DB_MAGIC)
     830      error (EXIT_FAILURE, 0, gettext ("file not a database file"));
     831  
     832    const char *valstrtab = (const char *) header + header->valstroffset;
     833  
     834    for (unsigned int dbidx = 0; dbidx < header->ndbs; ++dbidx)
     835      {
     836        const stridx_t *stridxtab
     837  	= ((const stridx_t *) ((const char *) header
     838  			       + header->dbs[dbidx].keyidxoffset));
     839        const char *keystrtab
     840  	= (const char *) header + header->dbs[dbidx].keystroffset;
     841        const stridx_t *hashtab
     842  	= (const stridx_t *) ((const char *) header
     843  			      + header->dbs[dbidx].hashoffset);
     844  
     845        for (uint32_t hidx = 0; hidx < header->dbs[dbidx].hashsize; ++hidx)
     846  	if (hashtab[hidx] != ~((stridx_t) 0))
     847  	  printf ("%c%s %s\n",
     848  		  header->dbs[dbidx].id,
     849  		  keystrtab + stridxtab[hidx],
     850  		  valstrtab + hashtab[hidx]);
     851      }
     852  
     853    return EXIT_SUCCESS;
     854  }
     855  
     856  
     857  #ifdef HAVE_SELINUX
     858  
     859  static void
     860  set_file_creation_context (const char *outname, mode_t mode)
     861  {
     862    static int enabled;
     863    static int enforcing;
     864    struct selabel_handle *label_hnd = NULL;
     865    char* ctx;
     866  
     867    /* Check if SELinux is enabled, and remember. */
     868    if (enabled == 0)
     869      enabled = is_selinux_enabled () ? 1 : -1;
     870    if (enabled < 0)
     871      return;
     872  
     873    /* Check if SELinux is enforcing, and remember. */
     874    if (enforcing == 0)
     875      enforcing = security_getenforce () ? 1 : -1;
     876  
     877    /* Open the file contexts backend. */
     878    label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
     879    if (!label_hnd)
     880      {
     881        error (enforcing > 0 ? EXIT_FAILURE : 0, 0,
     882  	     gettext ("cannot initialize SELinux context"));
     883        return;
     884      }
     885    /* Determine the context which the file should have. */
     886    ctx = NULL;
     887    if (selabel_lookup(label_hnd, &ctx, outname, S_IFREG | mode) == 0)
     888      {
     889        if (setfscreatecon (ctx) != 0)
     890  	error (enforcing > 0 ? EXIT_FAILURE : 0, 0,
     891  	       gettext ("cannot set file creation context for `%s'"),
     892  	       outname);
     893  
     894        freecon (ctx);
     895      }
     896  
     897    /* Close the file contexts backend. */
     898    selabel_close(label_hnd);
     899  }
     900  
     901  static void
     902  reset_file_creation_context (void)
     903  {
     904    setfscreatecon (NULL);
     905  }
     906  #endif