about summary refs log tree commit diff
diff options
context:
space:
mode:
authorUlrich Drepper <drepper@gmail.com>2011-06-02 14:43:16 -0400
committerUlrich Drepper <drepper@gmail.com>2011-06-15 21:06:18 -0400
commit9ee76b5ae861ff9891e5586fc6906c94c447a9e0 (patch)
tree5c7035015bb26812e5b6aa8b56e9b37b98b737d8
parentc71ca1f89c6e89d8c4145e4c2fdcce2fc78812bd (diff)
downloadglibc-9ee76b5ae861ff9891e5586fc6906c94c447a9e0.tar.gz
glibc-9ee76b5ae861ff9891e5586fc6906c94c447a9e0.tar.xz
glibc-9ee76b5ae861ff9891e5586fc6906c94c447a9e0.zip
Rewrite makedb to avoid using db library
-rw-r--r--ChangeLog5
-rw-r--r--nss/Makefile14
-rw-r--r--nss/makedb.c676
3 files changed, 600 insertions, 95 deletions
diff --git a/ChangeLog b/ChangeLog
index b26c6b2e7f..9ce38a8516 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2011-06-02  Ulrich Drepper  <drepper@gmail.com>
+
+	* nss/makedb.c: Rewritten to not use database library.
+	* nss/Makefile: Update to build new makedb program.
+
 2011-06-14  Andreas Jaeger  <aj@suse.de>
 
 	* sysdeps/unix/sysv/linux/check_native.c: Include <string.h> for
diff --git a/nss/Makefile b/nss/Makefile
index c49f375e95..16578a3548 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -37,8 +37,10 @@ routines		= nsswitch getnssent getnssent_r digits_dots \
 databases		= proto service hosts network grp pwd rpc ethers \
 			  spwd netgrp key alias sgrp
 
-others                  := getent
-install-bin             := getent
+others                  := getent makedb
+install-bin             := getent makedb
+makedb-modules = xmalloc hash-string
+extra-objs		+= $(makedb-modules:=.o)
 
 tests			= test-netdb tst-nss-test1
 xtests			= bug-erange
@@ -60,7 +62,7 @@ extra-libs-others	= $(extra-libs)
 
 # The sources are found in the appropriate subdir.
 subdir-dirs = $(services:%=nss_%)
-vpath %.c $(subdir-dirs)
+vpath %.c $(subdir-dirs) ../locale/programs ../intl
 
 
 libnss_files-routines	:= $(addprefix files-,$(databases)) \
@@ -80,6 +82,10 @@ ifeq (yes,$(build-static-nss))
 $(objpfx)getent: $(objpfx)libnss_files.a
 endif
 
+ifeq (yes,$(have-selinux))
+LDLIBS-makedb		:= -lselinux
+endif
+
 # Depend on libc.so so a DT_NEEDED is generated in the shared objects.
 # This ensures they will load libc.so for needed symbols if loaded by
 # a statically-linked program that hasn't already loaded it.
@@ -88,6 +94,8 @@ $(services:%=$(objpfx)libnss_%.so): $(libnss-libc) \
 				    $(common-objpfx)libc_nonshared.a
 
 
+$(objpfx)makedb: $(makedb-modules:%=$(objpfx)%.o)
+
 distribute		+= nss_test1.c
 
 CFLAGS-nss_test1.c = -DNOT_IN_libc=1
diff --git a/nss/makedb.c b/nss/makedb.c
index 5390dc1373..a01b2350aa 100644
--- a/nss/makedb.c
+++ b/nss/makedb.c
@@ -1,5 +1,5 @@
 /* Create simple DB database from textual input.
-   Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
+   Copyright (C) 1996-2000, 2011 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
 
@@ -19,25 +19,115 @@
    02111-1307 USA.  */
 
 #include <argp.h>
+#include <assert.h>
 #include <ctype.h>
-#include <dlfcn.h>
 #include <errno.h>
 #include <error.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <libintl.h>
 #include <locale.h>
+#include <search.h>
 #include <stdio.h>
-#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include "nss_db/dummy-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
+
 #define PACKAGE _libc_intl_domainname
 
+/* String table index type.  */
+typedef uint32_t stridx_t;
+
+/* Database file header.  */
+struct nss_db_header
+{
+  uint32_t magic;
+#define NSS_DB_MAGIC 0xdd110601
+  uint32_t ndbs;
+  uint64_t valstroffset;
+  uint64_t valstrlen;
+  struct
+  {
+    char id;
+    enum nss_db_type { nss_db_type_hash = 0,
+		       nss_db_type_iterate,
+		       nss_db_type_int4 } type:8;
+    char pad[sizeof (uint32_t) - 2];
+    uint32_t hashsize;
+    uint64_t hashoffset;
+    uint64_t stroffset;
+  } dbs[0];
+};
+
+struct nss_db_entry
+{
+  stridx_t keyidx;
+  stridx_t dataidx;
+};
+
+
+/* List of data bases.  */
+struct database
+{
+  char dbid;
+  enum nss_db_type type;
+  struct database *next;
+  void *entries;
+  size_t nentries;
+  size_t keystrlen;
+  size_t nhashentries;
+  struct nss_db_entry *hashtable;
+  char *keystrtab;
+} *databases;
+static size_t ndatabases;
+static size_t valstrlen;
+static void *valstrtree;
+static char *valstrtab;
+
+/* Database entry.  */
+struct dbentry
+{
+  size_t keylen;
+  stridx_t validx;
+  uint32_t hashval;
+  char str[0];
+};
+
+/* Stored string entry.  */
+struct valstrentry
+{
+  stridx_t idx;
+  char str[0];
+};
+
+
+/* Database type specifiers.  */
+struct dbtype
+{
+  char dbid;
+  enum nss_db_type type;
+  struct dbtype *next;
+};
+static struct dbtype *dbtypes;
+
+
+/* True if any entry has been added.  */
+static bool any_dbentry;
+
 /* If non-zero convert key to lower case.  */
 static int to_lowercase;
 
@@ -63,11 +153,16 @@ static const struct argp_option options[] =
     N_("Do not print messages while building database") },
   { "undo", 'u', NULL, 0,
     N_("Print content of database file, one entry a line") },
+  { NULL, 0, NULL, 0, N_("Select index type") },
+  { "iterate", 'I', "KEY", 0,
+    N_("Index identified by KEY used to iterate over database") },
+  { "binary", 'B', "KEY", 0,
+    N_("Index identified by KEY has binary key value") },
   { NULL, 0, NULL, 0, NULL }
 };
 
 /* Short description of program.  */
-static const char doc[] = N_("Create simple DB database from textual input.");
+static const char doc[] = N_("Create simple database from textual input.");
 
 /* Strings for arguments in help texts.  */
 static const char args_doc[] = N_("\
@@ -87,9 +182,26 @@ static struct argp argp =
 
 
 /* Prototypes for local functions.  */
-static int process_input (FILE *input, const char *inname, NSS_DB *output,
+static int process_input (FILE *input, const char *inname,
 			  int to_lowercase, int be_quiet);
-static int print_database (NSS_DB *db);
+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__;
+extern void *xcalloc (size_t n, size_t m) __attribute_malloc__;
 
 
 int
@@ -97,8 +209,6 @@ main (int argc, char *argv[])
 {
   const char *input_name;
   FILE *input_file;
-  NSS_DB *db_file;
-  int status;
   int remaining;
   int mode = 0666;
 
@@ -136,24 +246,17 @@ main (int argc, char *argv[])
       output_name = argv[remaining];
     }
 
-  /* First load the shared object to initialize version dependend
-     variables.  */
-  if (load_db () != NSS_STATUS_SUCCESS)
-    error (EXIT_FAILURE, 0, gettext ("No usable database library found."));
-
   /* Special handling if we are asked to print the database.  */
   if (do_undo)
     {
-      dbopen (input_name, db_rdonly, 0666, &db_file);
-      if (db_file == NULL)
-	error (EXIT_FAILURE, 0, gettext ("cannot open database file `%s': %s"),
-	       input_name,
-	       (errno == EINVAL ? gettext ("incorrectly formatted file")
-		: strerror (errno)));
+      int fd = open (input_name, O_RDONLY);
+      if (fd == -1)
+	error (EXIT_FAILURE, errno, gettext ("cannot open database file `%s'"),
+	       input_name);
 
-      status = print_database (db_file);
+      int status = print_database (fd);
 
-      db_file->close (db_file->db, 0);
+      close (fd);
 
       return status;
     }
@@ -163,34 +266,83 @@ main (int argc, char *argv[])
     input_file = stdin;
   else
     {
-      struct stat st;
+      struct stat64 st;
 
-      input_file = fopen (input_name, "r");
+      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 (fstat (fileno (input_file), &st) >= 0)
+      if (fstat64 (fileno (input_file), &st) >= 0)
 	mode = st.st_mode & ACCESSPERMS;
     }
 
-  /* Open output file.  This must not be standard output so we don't
-     handle "-" and "/dev/stdout" special.  */
-  dbopen (output_name, DB_CREATE | db_truncate, mode, &db_file);
-  if (db_file == NULL)
-    error (EXIT_FAILURE, errno, gettext ("cannot open output file `%s'"),
-	   output_name);
-
   /* Start the real work.  */
-  status = process_input (input_file, input_name, db_file, to_lowercase,
-			  be_quiet);
+  int status = process_input (input_file, input_name, to_lowercase, be_quiet);
 
   /* Close files.  */
   if (input_file != stdin)
     fclose (input_file);
-  db_file->close (db_file->db, 0);
+
+  /* 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)
+    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"));
+  // XXX SELinux context
+
+  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;
 }
@@ -200,6 +352,7 @@ main (int argc, char *argv[])
 static error_t
 parse_opt (int key, char *arg, struct argp_state *state)
 {
+  struct dbtype *newtype;
   switch (key)
     {
     case 'f':
@@ -214,6 +367,17 @@ parse_opt (int key, char *arg, struct argp_state *state)
     case 'u':
       do_undo = 1;
       break;
+    case 'I':
+    case 'B':
+      if (arg[0] == '\0' || arg[1] != '\0')
+	error (EXIT_FAILURE, 0, gettext ("\
+argument for option to specify database type must be a single-byte character"));
+      newtype = xmalloc (sizeof (struct dbtype));
+      newtype->dbid = arg[0];
+      newtype->type = key == 'I' ? nss_db_type_iterate : nss_db_type_int4;
+      newtype->next = dbtypes;
+      dbtypes = newtype;
+      break;
     default:
       return ARGP_ERR_UNKNOWN;
     }
@@ -246,16 +410,44 @@ print_version (FILE *stream, struct argp_state *state)
 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\
-"), "2000");
+"), "2011");
   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
 }
 
 
 static int
-process_input (input, inname, output, to_lowercase, be_quiet)
+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;
+
+  if (d1->keylen < d2->keylen)
+    return -1;
+
+  if (d1->keylen > d2->keylen)
+    return 1;
+
+  return memcmp (d1->str, d2->str, d1->keylen);
+}
+
+
+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;
-     NSS_DB *output;
      int to_lowercase;
      int be_quiet;
 {
@@ -269,14 +461,11 @@ process_input (input, inname, output, to_lowercase, be_quiet)
   status = EXIT_SUCCESS;
   linenr = 0;
 
-  while (!feof (input))
-    {
-      DBT key;
-      DBT val;
-      char *cp;
-      int n;
+  struct database *last_database = NULL;
 
-      n = getline (&line, &linelen, input);
+  while (!feof_unlocked (input))
+    {
+      ssize_t n = getline (&line, &linelen, input);
       if (n < 0)
 	/* This means end of file or some bug.  */
 	break;
@@ -290,15 +479,18 @@ process_input (input, inname, output, to_lowercase, be_quiet)
 	/* Remove trailing newline.  */
 	line[--n] = '\0';
 
-      cp = line;
+      char *cp = line;
       while (isspace (*cp))
 	++cp;
 
-      if (*cp == '#')
-	/* First non-space character in line '#': it's a comment.  */
+      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;
 
-      key.data = cp;
+      /* 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)
@@ -306,44 +498,131 @@ process_input (input, inname, output, to_lowercase, be_quiet)
 	  ++cp;
 	}
 
-      if (key.data == cp)
-	/* It's an empty line.  */
+      if (*cp == '\0')
+	/* It's a line without a value field.  */
 	continue;
 
-      key.size = cp - (char *) key.data;
-      key.flags = 0;
+      *cp++ = '\0';
+      size_t keylen = cp - key;
 
       while (isspace (*cp))
 	++cp;
 
-      val.data = cp;
-      val.size = (&line[n] - cp) + 1;
-      val.flags = 0;
+      char *data = cp;
+      size_t datalen = (&line[n] - cp) + 1;
 
-      /* Store the value.  */
-      status = output->put (output->db, NULL, &key, &val, db_nooverwrite);
-      if (status != 0)
+      /* Find the database.  */
+      if (last_database == NULL || last_database->dbid != key[0])
 	{
-	  if (status == db_keyexist)
+	  last_database = databases;
+	  while (last_database != NULL && last_database->dbid != key[0])
+	    last_database = last_database->next;
+
+	  if (last_database == NULL)
 	    {
-	      if (!be_quiet)
-		error_at_line (0, 0, inname, linenr,
-			       gettext ("duplicate key"));
-	      /* This is no real error.  Just give a warning.  */
-	      status = 0;
-	      continue;
+	      last_database = xmalloc (sizeof (*last_database));
+	      last_database->dbid = key[0];
+	      last_database->type = nss_db_type_hash;	/* Default.  */
+	      last_database->next = databases;
+	      last_database->entries = NULL;
+	      last_database->nentries = 0;
+	      last_database->keystrlen = 0;
+	      databases = last_database;
+
+	      struct dbtype *typeit = dbtypes;
+	      while (typeit != NULL)
+		if (typeit->dbid == last_database->dbid)
+		  {
+		    last_database->type = typeit->type;
+		    break;
+		  }
+		else
+		  typeit = typeit->next;
 	    }
-	  else
-	    error (0, status, gettext ("while writing database file"));
+	}
 
-	  status = EXIT_FAILURE;
+      /* Skip the database selector.  */
+      ++key;
+      --keylen;
+
+      /* Check the key value if it has to be numeric.  */
+      unsigned long int keyvalue = 0;
+      if (last_database->type != nss_db_type_hash)
+	{
+	  char *endp;
+	  errno = 0;
+	  keyvalue = strtoul (key, &endp, 0);
+	  if ((keyvalue == ULONG_MAX && errno == ERANGE)
+	      || keyvalue > ~((stridx_t) 0))
+	    error (EXIT_FAILURE, 0,
+		   gettext ("index value in line %zu too large"), linenr);
+
+	  if (*endp != '\0')
+	    error (EXIT_FAILURE, 0,
+		   gettext ("index value in line %zu is not a number"),
+		   linenr);
+	}
+
+      /* Store the data.  */
+      struct valstrentry *nentry = xmalloc (sizeof (struct valstrentry)
+					    + datalen);
+      nentry->idx = valstrlen;
+      memcpy (nentry->str, data, datalen);
 
-	  clearerr (input);
-	  break;
+      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
+	valstrlen += datalen;
+
+      /* Store the key.  */
+      struct dbentry *newp;
+      if (last_database->type == nss_db_type_hash)
+	{
+	  newp = xmalloc (sizeof (struct dbentry) + keylen);
+	  newp->keylen = keylen;
+	  newp->validx = nentry->idx;
+	  newp->hashval = __hash_string (key);
+	  memcpy (newp->str, key, keylen);
 	}
+      else
+	{
+	  newp = xmalloc (sizeof (struct dbentry) + sizeof (stridx_t));
+	  newp->keylen = keylen = sizeof (stridx_t);
+	  newp->validx = nentry->idx;
+	  newp->hashval = keyvalue;
+	  stridx_t value = keyvalue;
+	  memcpy (newp->str, &value, sizeof (stridx_t));
+	}
+
+      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 (input))
+  if (ferror_unlocked (input))
     {
       error (0, 0, gettext ("problems while reading `%s'"), inname);
       status = EXIT_FAILURE;
@@ -353,38 +632,251 @@ process_input (input, inname, output, to_lowercase, be_quiet)
 }
 
 
-static int
-print_database (db)
-     NSS_DB *db;
+static void
+copy_valstr (const void *nodep, const VISIT which, const int depth)
 {
-  DBT key;
-  DBT val;
-  NSS_DBC *cursor;
-  int status;
+  if (which != leaf && which != postorder)
+    return;
+
+  const struct valstrentry *p = *(const struct valstrentry **) nodep;
+
+  strcpy (valstrtab + p->idx, p->str);
+}
+
+
+static void
+compute_tables (void)
+{
+  valstrtab = xmalloc (roundup (valstrlen, sizeof (stridx_t)));
+  while (valstrlen % sizeof (stridx_t) != 0)
+    valstrtab[valstrlen++] = '\0';
+  twalk (valstrtree, copy_valstr);
+
+  for (struct database *db = databases; db != NULL; db = db->next)
+    if (db->nentries != 0)
+      {
+	++ndatabases;
+
+	if (db->type == nss_db_type_iterate)
+	  {
+	    /* We need no hash table and no key value table in this case.  */
+	    db->nhashentries = 0;
+	    db->hashtable = NULL;
+	    db->keystrtab = NULL;
+	    db->keystrlen = 0;
+	    continue;
+	  }
+
+	if (db->keystrlen > ~((stridx_t) 0))
+	  error (EXIT_FAILURE, 0, gettext ("\
+table size too large; recompile with larger stridx_t"));
+
+	/* 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.  */
+	db->nhashentries = db->nentries * 2 + 1;
+	db->hashtable = xmalloc (db->nhashentries
+				 * sizeof (struct nss_db_entry));
+	memset (db->hashtable, '\xff',
+		db->nhashentries * sizeof (struct nss_db_entry));
+	db->keystrlen = roundup (db->keystrlen, sizeof (stridx_t));
+	db->keystrtab = xmalloc (db->keystrlen);
+
+	size_t max_chainlength = 0;
+	char *wp = db->keystrtab;
+
+	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 = wp - db->keystrtab;
+	  wp = mempcpy (wp, dbe->str, dbe->keylen);
+
+	  size_t hidx = dbe->hashval % db->nhashentries;
+	  size_t hval2 = 1 + dbe->hashval % (db->nhashentries - 2);
+	  size_t chainlength = 0;
+
+	  while (db->hashtable[hidx].keyidx != ~((stridx_t) 0))
+	    {
+	      ++chainlength;
+	      if ((hidx += hval2) >= db->nhashentries)
+		hidx -= db->nhashentries;
+	    }
+
+	  db->hashtable[hidx].keyidx = stridx;
+	  db->hashtable[hidx].dataidx = dbe->validx;
+
+	  max_chainlength = MAX (max_chainlength, chainlength);
+	}
 
-  status = db->cursor (db->db, NULL, &cursor);
-  if (status != 0)
+	twalk (db->entries, add_key);
+
+	// XXX if hash length is too long resize table and start again
+
+	while ((wp - db->keystrtab) % sizeof (stridx_t) != 0)
+	  *wp++ = '\0';
+    }
+}
+
+
+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 + 2 * ndatabases];
+  iov[0].iov_base = header;
+  iov[0].iov_len = file_offset;
+
+  iov[1].iov_base = valstrtab;
+  iov[1].iov_len = valstrlen;
+  file_offset += valstrlen;
+
+  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;
+	header->dbs[filled_dbs].type = db->type;
+	memset (header->dbs[filled_dbs].pad, '\0',
+		sizeof (header->dbs[0].pad));
+	header->dbs[filled_dbs].hashsize = db->nhashentries;
+
+	iov[2 + filled_dbs * 2].iov_base = db->hashtable;
+	iov[2 + filled_dbs * 2].iov_len = (db->nhashentries
+					   * sizeof (struct nss_db_entry));
+	header->dbs[filled_dbs].hashoffset = file_offset;
+	file_offset += iov[2 + filled_dbs * 2].iov_len;
+
+	iov[3 + filled_dbs * 2].iov_base = db->keystrtab;
+	iov[3 + filled_dbs * 2].iov_len = db->keystrlen;
+	header->dbs[filled_dbs].stroffset = file_offset;
+	file_offset += iov[3 + filled_dbs * 2].iov_len;
+
+	++filled_dbs;
+      }
+
+  assert (filled_dbs == ndatabases);
+
+  if (writev (fd, iov, 2 + 2 * ndatabases) != file_offset)
     {
-      error (0, status, gettext ("while reading database"));
+      error (0, errno, gettext ("failed to write new database file"));
       return EXIT_FAILURE;
     }
 
-  key.flags = 0;
-  val.flags = 0;
-  status = cursor->c_get (cursor->cursor, &key, &val, db_first);
-  while (status == 0)
+  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)
     {
-      printf ("%.*s %s\n", (int) key.size, (char *) key.data,
-	      (char *) val.data);
+      const char *keystrtab
+	= (const char *) header + header->dbs[dbidx].stroffset;
+      const struct nss_db_entry *hashtab
+	= (const struct nss_db_entry *) ((const char *) header
+					 + header->dbs[dbidx].hashoffset);
 
-      status = cursor->c_get (cursor->cursor, &key, &val, db_next);
+      if (header->dbs[dbidx].type == nss_db_type_hash)
+	{
+	  for (uint32_t hidx = 0; hidx < header->dbs[dbidx].hashsize; ++hidx)
+	    if (hashtab[hidx].keyidx != ~((stridx_t) 0))
+	      printf ("%c%s %s\n",
+		      header->dbs[dbidx].id,
+		      keystrtab + hashtab[hidx].keyidx,
+		      valstrtab + hashtab[hidx].dataidx);
+	}
+      else if (header->dbs[dbidx].type == nss_db_type_iterate)
+	{
+	  const char *endvalstrtab = valstrtab + header->valstrlen;
+	  const char *cp = valstrtab;
+	  unsigned int count = 0;
+	  while (cp < endvalstrtab && *cp != '\0')
+	    {
+	      printf ("%c%u %s\n", header->dbs[dbidx].id, count++, cp);
+	      cp = rawmemchr (cp, '\0') + 1;
+	    }
+	}
+      else
+	{
+	  assert (header->dbs[dbidx].type == nss_db_type_int4);
+	  for (uint32_t hidx = 0; hidx < header->dbs[dbidx].hashsize; ++hidx)
+	    if (hashtab[hidx].keyidx != ~((stridx_t) 0))
+	      printf ("%c%" PRIu32 " %s\n",
+		      header->dbs[dbidx].id,
+		      *((uint32_t *) (keystrtab + hashtab[hidx].keyidx)),
+		      valstrtab + hashtab[hidx].dataidx);
+	}
     }
 
-  if (status != db_notfound)
+  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 ();
+  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)
     {
-      error (0, status, gettext ("while reading database"));
-      return EXIT_FAILURE;
+      if (setfscreatecon (ctx) != 0)
+	error (enforcing > 0 ? EXIT_FAILURE : 0, 0,
+	       gettext ("cannot set file creation context for `%s'"),
+	       outname);
+
+      freecon (ctx);
     }
+}
 
-  return EXIT_SUCCESS;
+static void
+reset_file_creation_context (void)
+{
+  setfscreatecon (NULL);
 }
+#endif