2016-04-15  Florian Weimer  <fweimer@redhat.com>

	* nss/tst-nss-aux.h: New file.
	* nss/tst-nss-aux.c: Likewise.
	* nss/tst-nss-files-aux.h: Likewise.
	* nss/tst-nss-files-aux.c: Likewise.
	* nss/tst-nss-files.c: Likewise.
	* nss/tst-nss-files-enumeration.c: Likewise.
	* nss/tst-nss-files-longline.c: Likewise.
	* nss/Makefile (tests): Add tst-nss-files,
	tst-nss-files-enumeration.
	(xtests): Add tst-nss-files-longline.
	(extra-test-objs): Add tst-nss-aux.o, tst-nss-files-aux.o.
	(test-extras): Add tst-nss-aux, tst-nss-files-aux.
	(tst-nss-files, tst-nss-files-enumeration)
	(tst-nss-files-longline): Link against tst-nss-aux.o,
	tst-nss-files-aux.o, -ldl.

diff --git a/nss/Makefile b/nss/Makefile
index 1f016d9..4c6e34f 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -51,8 +51,8 @@ extra-objs		+= $(makedb-modules:=.o)
 tests-static            = tst-field
 tests			= test-netdb tst-nss-test1 test-digits-dots \
 			  tst-nss-getpwent bug17079 \
-			  $(tests-static)
-xtests			= bug-erange
+			  $(tests-static) tst-nss-files tst-nss-files-enumeration
+xtests			= bug-erange tst-nss-files-longline
 
 # Specify rules for the nss_* modules.  We have some services.
 services		:= files db
@@ -88,7 +88,8 @@ routines                += $(libnss_files-routines)
 static-only-routines    += $(libnss_files-routines)
 tests-static		+= tst-nss-static
 endif
-extra-test-objs		+= nss_test1.os
+extra-test-objs += nss_test1.os tst-nss-aux.o tst-nss-files-aux.o
+test-extras += tst-nss-aux tst-nss-files-aux
 
 include ../Rules
 
@@ -125,3 +126,13 @@ $(objpfx)/libnss_test1.so$(libnss_test1.so-version): $(objpfx)/libnss_test1.so
 	$(make-link)
 endif
 $(objpfx)tst-nss-test1.out: $(objpfx)/libnss_test1.so$(libnss_test1.so-version)
+
+$(objpfx)tst-nss-files: $(objpfx)tst-nss-aux.o $(objpfx)tst-nss-files-aux.o
+$(objpfx)tst-nss-files-enumeration: \
+  $(objpfx)tst-nss-aux.o $(objpfx)tst-nss-files-aux.o
+$(objpfx)tst-nss-files-longline: \
+  $(objpfx)tst-nss-aux.o $(objpfx)tst-nss-files-aux.o
+
+LDLIBS-tst-nss-files = -ldl
+LDLIBS-tst-nss-files-enumeration = -ldl
+LDLIBS-tst-nss-files-longline = -ldl
diff --git a/nss/tst-nss-aux.c b/nss/tst-nss-aux.c
new file mode 100644
index 0000000..7c5de7e
--- /dev/null
+++ b/nss/tst-nss-aux.c
@@ -0,0 +1,643 @@
+/* Test support routines for NSS tests, independent of the service module.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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 <arpa/inet.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "tst-nss-aux.h"
+
+static void
+xwritev_fully (int fd, struct iovec *buffers, size_t count)
+{
+  while (count > 0)
+    {
+      /* Skip zero-length write requests.  */
+      if (buffers->iov_len == 0)
+        {
+          ++buffers;
+          --count;
+          continue;
+        }
+      /* Try to rewrite the remaing buffers.  */
+      ssize_t ret = writev (fd, buffers, count);
+      if (ret < 0)
+        {
+          printf ("error: writev: %m\n");
+          abort ();
+        }
+      if (ret == 0)
+        {
+          printf ("error: writev: invalid return value zero\n");
+          abort ();
+        }
+      /* Find the buffers that were successfully written.  */
+      while (ret > 0)
+        {
+          if (count == 0)
+            {
+              printf ("error: internal writev consistency failure\n");
+              abort ();
+            }
+          /* Current buffer was partially written.  */
+          if (buffers->iov_len > (size_t) ret)
+            {
+              buffers->iov_base += ret;
+              buffers->iov_len -= ret;
+              ret = 0;
+            }
+          else
+            {
+              ret -= buffers->iov_len;
+              buffers->iov_len = 0;
+              ++buffers;
+              --count;
+            }
+        }
+    }
+}
+
+static char *
+write_to_temp_file (const char *prefix, const char *str)
+{
+  char *name = xasprintf ("/tmp/tst-resolv-%s-XXXXXX", prefix);
+  int fd = mkstemp (name);
+  if (fd < 0)
+    {
+      printf ("error: mkstemp: %m\n");
+      abort ();
+    }
+  struct iovec buffers = {(char *) str, strlen (str)};
+  xwritev_fully (fd, &buffers, 1);
+  if (close (fd) != 0)
+    {
+      printf ("error: close: %m\n");
+      abort ();
+    }
+  return name;
+}
+
+/* Invokes diff with the file under path LEFT labeled as LEFT_LABEL,
+   and the file under path RIGHT labeled as RIGHT_LABEL.  */
+static void
+run_diff (const char *left_label, const char *left,
+          const char *right_label, const char *right)
+{
+  if (fflush (stdout) != 0)
+    {
+      printf ("error: fflush: %m\n");
+      abort ();
+    }
+
+  char *left_path = write_to_temp_file ("left-diff", left);
+  char *right_path = write_to_temp_file ("right-diff", right);
+
+  pid_t pid = fork ();
+  if (pid < 0)
+    {
+      printf ("error: fork: %m\n");
+      unlink (right_path);
+      unlink (left_path);
+      abort ();
+    }
+  else if (pid == 0)
+    {
+      execlp ("diff", "diff", "-u",
+              "--label", left_label, "--label", right_label,
+              "--", left_path, right_path,
+              NULL);
+      _exit (17);
+    }
+  else
+    {
+      int status;
+      int ret = waitpid (pid, &status, 0);
+      int error = errno;
+      unlink (right_path);
+      unlink (left_path);
+      errno = error;
+      if (ret < 0)
+        {
+          printf ("error: waitpid: %m\n");
+          abort ();
+        }
+      if (!WIFEXITED (status) || WEXITSTATUS (status) != 1)
+        {
+          printf ("warning: could not run diff, exit status: %d\n"
+                  "*** %s ***\n%s\n"
+                  "*** %s ***\n%s\n",
+                  status, left_label, left, right_label, right);
+        }
+    }
+
+  free (right_path);
+  free (left_path);
+}
+
+static const char *
+format_address_family (int family)
+{
+  switch (family)
+    {
+    case AF_INET:
+      return "INET";
+    case AF_INET6:
+      return "INET6";
+    }
+  printf ("error: invalid address family: %d\n", family);
+  abort ();
+}
+
+static int
+address_length (int family)
+{
+  switch (family)
+    {
+    case AF_INET:
+      return 4;
+    case AF_INET6:
+      return 16;
+    }
+  printf ("error: invalid address family: %d\n", family);
+  abort ();
+}
+
+static size_t
+socket_address_length (int family)
+{
+  switch (family)
+    {
+    case AF_INET:
+      return sizeof (struct sockaddr_in);
+    case AF_INET6:
+      return sizeof (struct sockaddr_in6);
+    }
+  printf ("error: invalid address family: %d\n", family);
+  abort ();
+}
+
+char *
+format_herrno (int code)
+{
+  const char *errstr;
+  switch (code)
+    {
+    case HOST_NOT_FOUND:
+      errstr = "HOST_NOT_FOUND";
+      break;
+    case NO_ADDRESS:
+      errstr = "NO_ADDRESS";
+      break;
+    case NO_RECOVERY:
+      errstr = "NO_RECOVERY";
+      break;
+    case TRY_AGAIN:
+      errstr = "TRY_AGAIN";
+      break;
+    default:
+      printf ("error: invalid h_errno value: %d\n", code);
+      abort ();
+    }
+  return xasprintf ("error: %s\n", errstr);
+}
+
+static void
+format_hostent_emit_one (FILE *out, const char *buf, unsigned count)
+{
+  if (count == 1)
+    fprintf (out, "address: %s\n", buf);
+  else if (count > 1)
+    fprintf (out, "address: %s (repeated %u times)\n", buf, count);
+}
+
+char *
+format_hostent (struct hostent *h)
+{
+  if (h == NULL)
+    return format_herrno (h_errno);
+
+  struct tst_memstream mem;
+  xopen_memstream (&mem);
+
+  if (h->h_length !=  address_length (h->h_addrtype))
+    {
+      printf ("error: invalid address length %d for %s\n",
+              h->h_length, format_address_family (h->h_addrtype));
+      abort ();
+    }
+  fprintf (mem.out, "name: %s\n", h->h_name);
+  for (char **alias = h->h_aliases; *alias != NULL; ++alias)
+    fprintf (mem.out, "alias: %s\n", *alias);
+  char prev_buf[128];
+  unsigned count = 0;
+  for (char **addr = h->h_addr_list; *addr != NULL; ++addr)
+    {
+      char buf[sizeof (prev_buf)];
+      if (inet_ntop (h->h_addrtype, *addr, buf, sizeof (buf)) == NULL)
+        {
+          printf ("error: inet_ntop failed: %m\n");
+          abort ();
+        }
+      if (count > 0 && strcmp (buf, prev_buf) == 0)
+        ++count;
+      else
+        {
+          format_hostent_emit_one (mem.out, prev_buf, count);
+          strcpy (prev_buf, buf);
+          count = 1;
+        }
+    }
+  format_hostent_emit_one (mem.out, prev_buf, count);
+
+  xclose_memstream (&mem);
+  return mem.buffer;
+}
+
+char *
+format_netent (struct netent *e)
+{
+  if (e == NULL)
+    return format_herrno (h_errno);
+
+  struct tst_memstream mem;
+  xopen_memstream (&mem);
+
+  if (e->n_name != NULL)
+    fprintf (mem.out, "name: %s\n", e->n_name);
+  for (char **ap = e->n_aliases; *ap != NULL; ++ap)
+    fprintf (mem.out, "alias: %s\n", *ap);
+  if (e->n_addrtype != AF_INET)
+    fprintf (mem.out, "addrtype: %d\n", e->n_addrtype);
+  fprintf (mem.out, "net: 0x%08x\n", e->n_net);
+
+  xclose_memstream (&mem);
+  return mem.buffer;
+}
+
+void
+check_hostent (struct hostent *h, const char *expected)
+{
+  char *formatted = format_hostent (h);
+  if (strcmp (formatted, expected) != 0)
+    {
+      record_delayed_failure ();
+      printf ("error: hostent comparison failure\n");
+      run_diff ("expected", expected,
+                "actual", formatted);
+    }
+  free (formatted);
+}
+
+void
+check_netent (struct netent *e, const char *expected)
+{
+  char *formatted = format_netent (e);
+  if (strcmp (formatted, expected) != 0)
+    {
+      record_delayed_failure ();
+      printf ("error: hostent comparison failure\n");
+      run_diff ("expected", expected,
+                "actual", formatted);
+    }
+  free (formatted);
+}
+
+/* Scratch buffer for storing one formatted addrinfo item.  */
+struct format_ai_item
+{
+  unsigned count;
+  char buffer[256];
+};
+
+static void
+format_ai_item (struct addrinfo *ai, struct format_ai_item *item)
+{
+
+  /* ai_socktype */
+  char type_buf[32];
+  const char *type_str;
+  switch (ai->ai_socktype)
+    {
+    case SOCK_RAW:
+      type_str = "RAW";
+      break;
+    case SOCK_DGRAM:
+      type_str = "DGRAM";
+      break;
+    case SOCK_STREAM:
+      type_str = "STREAM";
+      break;
+    default:
+      snprintf (type_buf, sizeof (type_buf), "%d", ai->ai_socktype);
+      type_str = type_buf;
+    }
+
+  /* ai_protocol */
+  char proto_buf[32];
+  const char *proto_str;
+  switch (ai->ai_protocol)
+    {
+    case IPPROTO_IP:
+      proto_str = "IP";
+      break;
+    case IPPROTO_UDP:
+      proto_str = "UDP";
+      break;
+    case IPPROTO_TCP:
+      proto_str = "TCP";
+      break;
+    default:
+      snprintf (proto_buf, sizeof (proto_buf), "%d", ai->ai_protocol);
+      proto_str = proto_buf;
+    }
+
+  /* ai_addrlen */
+  if (ai->ai_addrlen != socket_address_length (ai->ai_family))
+    {
+      printf ("error: invalid address length %d for %s\n",
+              ai->ai_addrlen, format_address_family (ai->ai_family));
+      abort ();
+    }
+
+  char addr[128];
+  uint16_t port;
+  {
+    const char *ret;
+    switch (ai->ai_family)
+      {
+      case AF_INET:
+        {
+          struct sockaddr_in *sin = (struct sockaddr_in *) ai->ai_addr;
+          ret = inet_ntop (AF_INET, &sin->sin_addr, addr, sizeof (addr));
+          port = sin->sin_port;
+        }
+        break;
+      case AF_INET6:
+        {
+          struct sockaddr_in6 *sin = (struct sockaddr_in6 *) ai->ai_addr;
+          ret = inet_ntop (AF_INET6, &sin->sin6_addr, addr, sizeof (addr));
+          port = sin->sin6_port;
+        }
+        break;
+      default:
+        printf ("error: invalid address family: %d\n", ai->ai_family);
+        abort ();
+      }
+    if (ret == NULL)
+      {
+        printf ("error: ntop failed: %m\n");
+        abort ();
+      }
+  }
+
+  snprintf (item->buffer, sizeof (item->buffer),
+            "%s/%s %s %u", type_str, proto_str, addr, ntohs (port));
+}
+
+static void
+format_ai_emit_one (FILE *out, struct format_ai_item *pending)
+{
+  if (pending->count == 1)
+    fprintf (out, "address: %s\n", pending->buffer);
+  else if (pending->count > 1)
+    fprintf (out, "address: %s (repeated %u times)\n",
+             pending->buffer, pending->count);
+  pending->count = 0;
+}
+
+static void
+format_ai_flag (FILE *out, struct addrinfo *ai, int flag, const char *name,
+                int * flags_printed)
+{
+  if ((ai->ai_flags & flag) != 0)
+    fprintf (out, " %s", name);
+  *flags_printed |= flag;
+}
+
+static void
+format_ai_flags (FILE *out, struct addrinfo *ai, int *flags,
+                 struct format_ai_item *pending)
+{
+  /* ai_flags */
+  if (ai->ai_flags != *flags)
+    {
+      format_ai_emit_one (out, pending);
+      fprintf (out, "flags:");
+      int flags_printed = 0;
+#define FLAG(flag) format_ai_flag (out, ai, flag, #flag, &flags_printed)
+      FLAG (AI_PASSIVE);
+      FLAG (AI_CANONNAME);
+      FLAG (AI_NUMERICHOST);
+      FLAG (AI_V4MAPPED);
+      FLAG (AI_ALL);
+      FLAG (AI_ADDRCONFIG);
+      FLAG (AI_IDN);
+      FLAG (AI_CANONIDN);
+      FLAG (AI_IDN_ALLOW_UNASSIGNED);
+      FLAG (AI_IDN_USE_STD3_ASCII_RULES);
+      FLAG (AI_NUMERICSERV);
+#undef FLAG
+      int remaining = ai->ai_flags & ~flags_printed;
+      if (remaining != 0)
+        fprintf (out, " %08x", remaining);
+      fprintf (out, "\n");
+      *flags = ai->ai_flags;
+    }
+}
+
+static void
+format_ai_one (FILE *out, struct addrinfo *ai, int *flags,
+               struct format_ai_item *pending)
+{
+  format_ai_flags (out, ai, flags, pending);
+
+  struct format_ai_item item;
+  format_ai_item (ai, &item);
+  if (pending->count == 0 || strcmp (pending->buffer, item.buffer) != 0)
+    {
+      strcpy (pending->buffer, item.buffer);
+      pending->count = 1;
+    }
+  else
+    ++pending->count;
+
+  /* ai_canonname */
+  if (ai->ai_canonname != NULL)
+    {
+      format_ai_emit_one (out, pending);
+      fprintf (out, "canonname: %s\n", ai->ai_canonname);
+    }
+}
+
+/* Format all the addresses in one address family.  */
+static void
+format_ai_family (FILE *out, struct addrinfo *ai, int family, int *flags)
+{
+  struct format_ai_item pending;
+  pending.count = 0;
+  while (ai != NULL)
+    {
+      if (ai->ai_family == family)
+        format_ai_one (out, ai, flags, &pending);
+      ai = ai->ai_next;
+    }
+  format_ai_emit_one (out, &pending);
+}
+
+char *
+format_addrinfo (struct addrinfo *ai, int ret)
+{
+  int errno_copy = errno;
+
+  struct tst_memstream mem;
+  xopen_memstream (&mem);
+  if (ret != 0)
+    {
+      fprintf (mem.out, "error: %s\n", gai_strerror (ret));
+      if (ret == EAI_SYSTEM)
+        {
+          errno = errno_copy;
+          fprintf (mem.out, "error: %m\n");
+        }
+    }
+  else
+    {
+      int flags = 0;
+      format_ai_family (mem.out, ai, AF_INET, &flags);
+      format_ai_family (mem.out, ai, AF_INET6, &flags);
+    }
+
+  xclose_memstream (&mem);
+  return mem.buffer;
+}
+
+void
+check_addrinfo (struct addrinfo *ai, int ret, const char *expected)
+{
+  char *formatted = format_addrinfo (ai, ret);
+  if (strcmp (formatted, expected) != 0)
+    {
+      record_delayed_failure ();
+      printf ("error: addrinfo comparison failure\n");
+      run_diff ("expected", expected, "actual", formatted);
+    }
+  free (formatted);
+}
+
+void
+xopen_memstream (struct tst_memstream *stream)
+{
+  *stream = (struct tst_memstream) {};
+  stream->out = open_memstream (&stream->buffer, &stream->length);
+}
+
+void
+xclose_memstream (struct tst_memstream *stream)
+{
+  if (ferror (stream->out))
+    {
+      printf ("error: memstream write: %m\n");
+      abort ();
+    }
+  if (fclose (stream->out) != 0)
+    {
+      printf ("error: memstream close: %m\n");
+      abort ();
+    }
+  stream->out = NULL;
+}
+
+char *
+__attribute__ ((format (printf, 1, 2)))
+xasprintf (const char *format, ...)
+{
+  va_list ap;
+  va_start (ap, format);
+  char *result;
+  if (vasprintf (&result, format, ap) < 0)
+    {
+      printf ("error: vasprintf: %m\n");
+      abort ();
+    }
+  va_end (ap);
+  return result;
+}
+
+void
+xlock (pthread_mutex_t *mutex)
+{
+  int ret = pthread_mutex_lock (mutex);
+  if (ret != 0)
+    {
+      errno = ret;
+      printf ("error: pthread_mutex_lock: %m\n");
+      abort ();
+    }
+}
+
+
+void
+xunlock (pthread_mutex_t *mutex)
+{
+  int ret = pthread_mutex_unlock (mutex);
+  if (ret != 0)
+    {
+      errno = ret;
+      printf ("error: pthread_mutex_unlock: %m\n");
+      abort ();
+    }
+}
+
+static int errors;
+pthread_mutex_t errors_lock = PTHREAD_MUTEX_INITIALIZER;
+
+void
+record_delayed_failure (void)
+{
+  xlock (&errors_lock);
+  if (errors != INT_MAX)
+    ++errors;
+  xunlock (&errors_lock);
+}
+
+void
+reset_delayed_failures (void)
+{
+  /* Also reinitialize the lock.  */
+  errors_lock = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
+  errors = 0;
+}
+
+void
+__attribute__ ((destructor))
+report_delayed_failures (void)
+{
+  int errors_local;
+  xlock (&errors_lock);
+  errors_local = errors;
+  xunlock (&errors_lock);
+  if (errors_local > 0)
+    {
+      printf ("error: %d failing subtests\n", errors);
+      _exit (1);
+    }
+}
diff --git a/nss/tst-nss-aux.h b/nss/tst-nss-aux.h
new file mode 100644
index 0000000..984c1db
--- /dev/null
+++ b/nss/tst-nss-aux.h
@@ -0,0 +1,68 @@
+/* Test support routines for NSS tests, independent of the service module.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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/>.  */
+
+#ifndef TST_NSS_AUX_H
+#define TST_NSS_AUX_H
+
+#include <netdb.h>
+#include <stdio.h>
+#include <pthread.h>
+
+/* Comparison of responses.  */
+char *format_herrno (int);
+char *format_hostent (struct hostent *);
+char *format_netent (struct netent *);
+void check_hostent (struct hostent *, const char *expected);
+void check_netent (struct netent *, const char *expected);
+char *format_addrinfo (struct addrinfo *, int ret);
+void check_addrinfo (struct addrinfo *ai, int ret, const char *expected);
+
+/* Abort the processes after running exit handlers, to record the fact
+   that a subtest failed.  */
+void record_delayed_failure (void);
+
+/* Reset the number of subtest failures to zero.  Useful at the start
+   of a new test sequence in a child process.  */
+void reset_delayed_failures (void);
+
+/* Explicitly report encountered failures, using _exit (1).  Typically
+   invoked before _exit (0) in a child process.  */
+void report_delayed_failures (void);
+
+/* Helper routines for constructing strings.  xclose_memstream
+   performs error checking on the stream and aborts on failure.  */
+struct tst_memstream
+{
+  /* Opened by xopen_memstream, closed by xclose_memstream.  */
+  FILE *out;
+  /* Needs to be freed separately after after a call to
+     xclose_memstream.  */
+  char *buffer;
+  size_t length;
+};
+void xopen_memstream (struct tst_memstream *stream);
+void xclose_memstream (struct tst_memstream *);
+
+/* Error-checking wrappers around thread functions.  They abort the
+   process on error.  */
+char *xasprintf (const char *format, ...)
+  __attribute__ ((format (printf, 1, 2)));
+void xlock (pthread_mutex_t *);
+void xunlock (pthread_mutex_t *);
+
+#endif /* TST_NSS_AUX_H */
diff --git a/nss/tst-nss-files-aux.c b/nss/tst-nss-files-aux.c
new file mode 100644
index 0000000..9b6252f
--- /dev/null
+++ b/nss/tst-nss-files-aux.c
@@ -0,0 +1,407 @@
+/* Test support routines for nss_files.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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 <dlfcn.h>
+#include <gnu/lib-names.h>
+#include <netdb.h>
+#include <nss.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "tst-nss-aux.h"
+#include "tst-nss-files-aux.h"
+
+/* The enumeration value indicates which system call is invoked on the
+   path to be cleaned up.  */
+enum cleanup_action
+  {
+    cleanup_unlink,
+    cleanup_rmdir,
+    cleanup_umount,
+    cleanup_dlclose
+  };
+
+/* A single-linked list of clean-up tasks.  */
+static struct cleanup_item
+{
+  struct cleanup_item *next;
+  char *path;
+  pid_t pid;
+  enum cleanup_action action;
+} *cleanup_items;
+static pthread_mutex_t cleanup_items_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Process a single clean-up item.  ITEM and ITEM->path are not
+   deallocated, only the action is carried out.  */
+static void
+run_cleanup_item (const struct cleanup_item *item)
+{
+  switch (item->action)
+    {
+    case cleanup_unlink:
+      if (unlink (item->path) != 0)
+        printf ("warning: unlink (\"%s\"): %m", item->path);
+      break;
+    case cleanup_rmdir:
+      if (rmdir (item->path) != 0)
+        printf ("warning: rmdir (\"%s\"): %m", item->path);
+      break;
+    case cleanup_umount:
+      if (umount2 (item->path, MNT_DETACH) != 0)
+        printf ("warning: umount (\"%s\"): %m", item->path);
+      break;
+    case cleanup_dlclose:
+      dlclose (item->path);
+      break;
+    }
+}
+
+/* Add a new clean-up item to the cleanup_items list.  A copy of PATH
+   is made.  */
+static void
+push_cleanup_item (const char *path, enum cleanup_action action)
+{
+  /* Fully initialized cleanup_item.  Passed to run_cleanup_item on
+     allocation errors.  Also serves as a template for the newly
+     allocated item.  */
+  struct cleanup_item local =
+    {
+      .next = cleanup_items,
+      .path = (char *) path,
+      .pid = getpid (),
+      .action = action,
+    };
+
+  struct cleanup_item *item = malloc (sizeof (*item));
+  if (item == NULL)
+    {
+      printf ("error: malloc (%zu): %m\n", sizeof (*item));
+      run_cleanup_item (&local);
+      exit (1);
+    }
+  *item = local;
+  if (action != cleanup_dlclose)
+    {
+      item->path = strdup (path);
+      if (item->path == NULL)
+        {
+          free (item);
+          printf ("error: strdup: %m\n");
+          run_cleanup_item (&local);
+          exit (1);
+        }
+    }
+  xlock (&cleanup_items_lock);
+  item->action = action;
+  cleanup_items = item;
+  xunlock (&cleanup_items_lock);
+}
+
+/* Process the cleanup_items, deallocating them.  Only cleanup items
+   which have been registered in this process are run.  */
+static void
+process_cleanup_items (void)
+{
+  pid_t pid = getpid ();
+  xlock (&cleanup_items_lock);
+  while (cleanup_items != NULL)
+    {
+      struct cleanup_item *item = cleanup_items;
+      cleanup_items = item->next;
+      if (item->pid == pid)
+        run_cleanup_item (item);
+      if (item->action != cleanup_dlclose)
+        free (item->path);
+      free (item);
+    }
+  xunlock (&cleanup_items_lock);
+}
+
+/* Creates a directory which is removed in a clean-up action.  */
+static void
+cleanup_mkdir (const char *path)
+{
+  if (mkdir (path, 0777) != 0)
+    {
+      printf ("error: mkdir (\"%s\"): %m", path);
+      exit (1);
+    }
+  push_cleanup_item (path, cleanup_rmdir);
+}
+
+/* If true, use MS_REC in bind mounts.  Set by enable_chroot below.  */
+static bool use_recursive_bind_mount;
+
+/* Creates a directory and establishes a bind mount for it.  Mount and
+   directory are removed in cleanup actions.  */
+static void
+cleanup_bind_mount (const char *target, const char *source)
+{
+  cleanup_mkdir (target);
+  int flags = MS_BIND;
+  /* Be conservative about bind recursive mounts.  Bind-mounting
+     /dev/pts and /dev/shm can have unexpected side effects.  */
+  if (use_recursive_bind_mount)
+    flags |= MS_REC;
+  if (mount (source, target, "", flags, NULL) != 0)
+    {
+      printf ("error: bind mount of \"%s\" on \"%s\": %m\n",
+              source, target);
+      exit (1);
+    }
+  push_cleanup_item (target, cleanup_umount);
+}
+
+/* Concatenate the two strings, with error checking.  The returned
+   pointer needs to be freed.  */
+static char *
+xconcat2 (const char *a, const char *b)
+{
+  char *result;
+  if (asprintf (&result, "%s%s", a, b) < 0)
+    {
+      printf ("error: asprintf: %m\n");
+      exit (1);
+    }
+  return result;
+}
+
+/* Create a bind mount in the (future) chroot environment at ROOT, for
+   the directory SOURCE outside the environment.  */
+static void
+cleanup_bind_mount_in_chroot (const char *root, const char *source)
+{
+  char *dir = xconcat2 (root, source);
+  cleanup_bind_mount (dir, source);
+  free (dir);
+}
+
+static void
+write_chroot_file_1 (const char *name, FILE *out, void *closure)
+{
+  fputs (closure, out);
+}
+
+static void
+write_chroot_file (const char *root, const char *name, const char *contents)
+{
+  create_chroot_file (root, name, write_chroot_file_1, (void *) contents);
+}
+
+char *
+create_chroot (void)
+{
+  /* Use /var/tmp because some of the files can be quite large.  */
+  char *root = strdup ("/var/tmp/nss-files-root-XXXXXX");
+  if (root == NULL)
+    {
+      printf ("error: strdup: %m\n");
+      exit (1);
+    }
+  if (mkdtemp (root) == NULL)
+    {
+      printf ("error: mkdtemp (\"%s\"): %m", root);
+      exit (1);
+    }
+  push_cleanup_item (root, cleanup_rmdir);
+  cleanup_bind_mount_in_chroot (root, "/dev");
+  cleanup_bind_mount_in_chroot (root, "/proc");
+  cleanup_bind_mount_in_chroot (root, "/sys");
+  char *dir = xconcat2 (root, "/etc");
+  cleanup_mkdir (dir);
+  free (dir);
+  dir = xconcat2 (root, "/tmp");
+  cleanup_mkdir (dir);
+  free (dir);
+
+  /* Approximate standard system configuration.  */
+  write_chroot_file (root, "/etc/resolv.conf",
+                     "search localdomain\n"
+                     "nameserver 192.0.2.1\n");
+  write_chroot_file (root, "/etc/host.conf", "multi on\n");
+  return root;
+}
+
+/* Avoid pthread_once because it is not in libc, only in
+   libpthread.  */
+static bool configure_nss_done;
+static pthread_mutex_t configure_nss_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Move this process into its own namespace, to support chroot and
+   bind mounts by unprivileged users.  Fall back to chroot in case
+   namespace support is not available.  Returns true if the process
+   can perform chroot.  */
+static bool
+enable_chroot (void)
+{
+#ifdef CLONE_NEWUSER
+  if (unshare (CLONE_NEWUSER | CLONE_NEWNS) == 0)
+    {
+      if (chroot ("/") != 0)
+        {
+          printf ("warning: chroot within namespace failed: %m\n");
+          return false;
+        }
+      /* Recursive bind mounts should not hose the file system setup
+         outside of the container.  */
+      use_recursive_bind_mount = true;
+      return true;
+    }
+  printf ("info: unshare (CLONE_NEWUSER | CLONE_NEWNS) failed: %m\n"
+          "info: falling back to chroot (requiring root privileges)\n");
+#endif
+  if (chroot ("/") != 0)
+    {
+      printf ("warning: chroot failed: %m\n");
+      return false;
+    }
+  return true;
+}
+
+void
+configure_nss_files (void)
+{
+  xlock (&configure_nss_lock);
+  if (!configure_nss_done)
+    {
+      __nss_configure_lookup ("hosts", "files");
+      __nss_configure_lookup ("passwd", "files");
+      __nss_configure_lookup ("networks", "files");
+
+      /* Preload DSOs outside the chroot.  */
+      void *handle = dlopen (LIBNSS_FILES_SO, RTLD_LAZY);
+      if (handle == NULL)
+        {
+          printf ("error: cannot open " LIBNSS_FILES_SO ": %s\n",
+                  dlerror ());
+          exit (1);
+        }
+      push_cleanup_item (handle, cleanup_dlclose);
+
+      if (!enable_chroot ())
+        {
+          xunlock (&configure_nss_lock);
+          exit (77);
+        }
+    }
+  xunlock (&configure_nss_lock);
+}
+
+void
+create_chroot_file (const char *root, const char *name,
+                      create_file_callback callback, void *closure)
+{
+  char *path = xconcat2 (root, name);
+  push_cleanup_item (path, cleanup_unlink);
+  FILE *f = fopen (path, "w");
+  if (f == NULL)
+    {
+      printf ("error: could not open \"%s\" for writing: %m\n", path);
+      exit (1);
+    }
+  __fsetlocking (f, FSETLOCKING_BYCALLER);
+
+  if (callback != NULL)
+    callback (name, f, closure);
+
+  fflush (f);
+  if (ferror (f))
+    {
+      printf ("error: could not write file: %s\n", path);
+      exit (1);
+    }
+
+  fclose (f);
+  free (path);
+}
+
+struct etc_file_closure
+{
+  const char *name;
+  create_file_callback callback;
+  void *closure;
+};
+
+static void
+create_etc_file_1 (const char *name, FILE *out, void *closure)
+{
+  struct etc_file_closure *arg = closure;
+  arg->callback (arg->name, out, arg->closure);
+}
+
+void
+create_etc_file (const char *root, const char *name,
+                      create_file_callback callback, void *closure)
+{
+  struct etc_file_closure efc =
+    {
+      .name = name,
+      .callback = callback,
+      .closure = closure,
+    };
+  char *etc_name = xconcat2 ("/etc/", name);
+  create_chroot_file (root, etc_name, create_etc_file_1, &efc);
+  free (etc_name);
+}
+
+void
+isolate_in_subprocess (const char *root, void (*callback) (void))
+{
+  pid_t pid = fork ();
+  if (pid < 0)
+    {
+      printf ("error: fork: %m\n");
+      exit (1);
+    }
+  if (pid == 0)
+    {
+      if (root != NULL && chroot (root) != 0)
+        {
+          printf ("error: chroot (\"%s\"): %m", root);
+          _exit (1);
+        }
+      reset_delayed_failures ();
+      callback ();
+      report_delayed_failures ();
+      _exit (0);
+    }
+  int status;
+  if (waitpid (pid, &status, 0) < 0)
+    {
+      printf ("error: waitpid (%d): %m\n", (int) pid);
+      exit (1);
+    }
+  if (status != 0)
+    {
+      printf ("error: unexpected exit status in isolate_in_subprocess: %d\n",
+              status);
+      record_delayed_failure ();
+    }
+}
+
+
+static void
+__attribute__ ((destructor))
+fini (void)
+{
+  process_cleanup_items ();
+}
diff --git a/nss/tst-nss-files-aux.h b/nss/tst-nss-files-aux.h
new file mode 100644
index 0000000..ce9b64a
--- /dev/null
+++ b/nss/tst-nss-files-aux.h
@@ -0,0 +1,48 @@
+/* Test support routines for nss_files.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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/>.  */
+
+#ifndef TST_NSS_FILES_AUX_H
+#define TST_NSS_FILES_AUX_H
+
+/* Create a new temporary directory and populate it with bind mounts
+   and other files needed for a chroot environment.  The returned
+   pointer needs to be freed.  The created directory structure is
+   removed by clean-up actions.  */
+char *create_chroot (void);
+
+/* Override the system NSS configuration.  */
+void configure_nss_files (void);
+
+/* Runs CALLBACK in a subprocess.  if ROOT is not NULL, chroot to that
+   directory in the child process.  */
+void isolate_in_subprocess (const char *root, void (*callback) (void));
+
+/* Callback function to create nss_files database files.  */
+typedef void (*create_file_callback) (const char *name, FILE *, void *);
+
+/* Create the database file NAME (an absolute pathname) in the chroot
+   environment at ROOT.  The file data is provided by CALLBACK.  */
+void create_chroot_file (const char *root, const char *name,
+                         create_file_callback callback, void *closure);
+
+/* Create the database file NAME in the /etc directory of the chroot
+   environment at ROOT.  The file data is provided by CALLBACK.  */
+void create_etc_file (const char *root, const char *name,
+                      create_file_callback callback, void *closure);
+
+#endif /* TST_NSS_FILES_AUX_H */
diff --git a/nss/tst-nss-files-enumeration.c b/nss/tst-nss-files-enumeration.c
new file mode 100644
index 0000000..97f6e23
--- /dev/null
+++ b/nss/tst-nss-files-enumeration.c
@@ -0,0 +1,206 @@
+/* Test nss_files enumeration of /etc/passwd.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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 <errno.h>
+#include <pwd.h>
+
+#include "tst-nss-aux.h"
+#include "tst-nss-files-aux.h"
+
+/* create_file_callback for /etc/passwd.  */
+static void
+create_passwd (const char *name, FILE *out, void *closure)
+{
+  fputs ("root:x:0:0:glibc root test user:/root:/bin/bash\n", out);
+  fputs ("user0:x:1000:1000:glibc test user 0:/home/user0:/bin/bash\n", out);
+
+  fputs ("user1:x:1001:1001:glibc test user 1, padding ", out);
+  {
+    unsigned state = 1;
+    for (int i = 0; i < 10000; ++i)
+      {
+        fputc ('a' + (state % 26), out);
+        state = state * 22695477 + 1;
+      }
+  }
+  fputs (":/home/user1:/bin/bash\n", out);
+
+  fputs ("user2:x:1002:1002:glibc test user 2:/home/user2:/bin/bash\n", out);
+}
+
+/* This variable controls whether test cases use the _r variant of
+   certain functions.  */
+static bool use_r_variant;
+
+/* The enumeration values specify whether to call the set*ent before
+   enumeration, and whether to set the stayopen flag to true.  */
+static enum
+  {
+    no_setent,
+    use_setent,
+    use_setent_stayopen
+  } use_enumeration_initialization;
+
+/* Return the value of use_enumeration_initialization as a string.  */
+static const char *
+current_enumeration_initialization (void)
+{
+  switch (use_enumeration_initialization)
+    {
+    case no_setent:
+      return "no_setent";
+    case use_setent:
+      return "use_setent";
+    case use_setent_stayopen:
+      return "use_setent_stayopen";
+    }
+  return "unknown";
+}
+
+/* A test callback for enumerating /etc/passwd.  It runs in a chrooted
+   child process.  The enumerated data is compared with the file on
+   disk.  */
+static void
+test_enumeration_passwd (void)
+{
+  switch (use_enumeration_initialization)
+    {
+    case no_setent:
+      break;
+    case use_setent:
+    case use_setent_stayopen:
+      setpwent ();
+      break;
+    }
+
+  FILE *reference = fopen ("/etc/passwd", "r");
+  char *line_buffer = NULL;
+  size_t line_buffer_length = 0;
+
+  while (true)
+    {
+      ssize_t line_length = getline (&line_buffer, &line_buffer_length,
+                                     reference);
+      if (ferror (reference))
+        {
+          printf ("error: getline failed: %m\n");
+          exit (1);
+        }
+      if (feof (reference))
+        line_length = 0;
+
+      /* FIXME: Add _r variant.  */
+      errno = 0;
+      struct passwd *pw = getpwent ();
+      if (pw == NULL && errno == 0)
+        {
+          /* End of input.  */
+          if (line_length != 0)
+            {
+              printf ("error: insufficient data enumerating passwd\n");
+              exit (1);
+            }
+          break;
+        }
+      else if (pw == NULL)
+        {
+          printf ("error: getpwent: %m\n");
+          exit (1);
+        }
+      else
+        {
+          if (line_length == 0)
+            {
+              printf ("error: too much data enumerating passwd\n");
+              exit (1);
+            }
+
+          char *actual_buffer = NULL;
+          size_t actual_size = 0;
+          FILE *actual = open_memstream (&actual_buffer, &actual_size);
+          if (actual == NULL)
+            {
+              printf ("error: open_memstream: %m\n");
+              exit (1);
+            }
+          if (putpwent (pw, actual) != 0)
+            {
+              printf ("error: putpwent: %m\n");
+              exit (1);
+            }
+          fclose (actual);
+          if (actual_size != (size_t) line_length
+              || memcmp (actual_buffer, line_buffer, actual_size) != 0)
+            {
+              printf ("error: mismatch in passwd:\nactual:");
+              fwrite (actual_buffer, actual_size, 1, stdout);
+              puts ("\nexpected:");
+              fwrite (line_buffer, line_length, 1, stdout);
+            }
+          free (actual_buffer);
+        }
+    }
+
+  fclose (reference);
+  free (line_buffer);
+
+  switch (use_enumeration_initialization)
+    {
+    case no_setent:
+      break;
+    case use_setent:
+    case use_setent_stayopen:
+      endpwent ();
+      break;
+    }
+}
+
+/* Invoke test_enumeration_passwd in various variants.  */
+static void
+test_enumeration (const char *root)
+{
+  for (int which_r_variant = 0; which_r_variant < 2; ++which_r_variant)
+    {
+      use_r_variant = which_r_variant;
+      for (int which_enumeration_initialization = 0;
+           which_enumeration_initialization < 3;
+           ++which_enumeration_initialization)
+        {
+          use_enumeration_initialization = which_enumeration_initialization;
+          printf ("info: test_enumeration_passwd (%s, %s)\n",
+                  use_r_variant ? "getpwent_r" : "getpwent",
+                  current_enumeration_initialization ());
+          isolate_in_subprocess (root, test_enumeration_passwd);
+        }
+    }
+}
+
+static int
+do_test (void)
+{
+  configure_nss_files ();
+  char *root = create_chroot ();
+  create_etc_file (root, "passwd", create_passwd, NULL);
+  test_enumeration (root);
+  free (root);
+
+  return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
diff --git a/nss/tst-nss-files-longline.c b/nss/tst-nss-files-longline.c
new file mode 100644
index 0000000..df3dabc
--- /dev/null
+++ b/nss/tst-nss-files-longline.c
@@ -0,0 +1,94 @@
+/* Test nss_files with an extremely long line.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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 "tst-nss-aux.h"
+#include "tst-nss-files-aux.h"
+
+/* create_file_callback for /etc/hosts.  */
+static void
+create_hosts (const char *name, FILE *out, void *closure)
+{
+  fputs ("127.0.0.1 localhost localhost.localdomain localhost4\n", out);
+  fputs ("::1 localhost localhost.localdomain localhost6\n", out);
+
+  /* Very long line.  */
+  printf ("info: writing very long /etc/hosts line\n");
+  fputs ("192.0.2.1", out);
+  {
+    unsigned long long written = 0;
+    char hostname[] = " this-is-quite-a-long-host-name-XXXXXX.example";
+    size_t hostname_length = strlen (hostname);
+    char *p = strchr (hostname, 'X');
+    for (p[0] = 'a'; p[0] <= 'z'; ++p[0])
+      for (p[1] = 'a'; p[1] <= 'z'; ++p[1])
+        for (p[2] = 'a'; p[2] <= 'z'; ++p[2])
+          for (p[3] = 'a'; p[3] <= 'z'; ++p[3])
+            for (p[4] = 'a'; p[4] <= 'z'; ++p[4])
+              for (p[5] = 'a'; p[5] <= 'z'; ++p[5])
+                {
+                  if (fwrite (hostname, hostname_length, 1, out) != 1)
+                    {
+                      printf ("error: fwrite (\"%s\"): %m", name);
+                      exit (1);
+                    }
+                  written += hostname_length;
+                  if (written > INT_MAX)
+                    goto all_written;
+                }
+  }
+ all_written:
+  fputc ('\n', out);
+  printf ("info: very long /etc/hosts line written\n");
+
+  /* End of file marker.  */
+  fputs ("192.0.2.99 sentinel.example\n", out);
+}
+
+static void
+test_hosts_longline (void)
+{
+  check_hostent
+    (gethostbyname ("this-is-quite-a-long-host-name.example"),
+     "error: HOST_NOT_FOUND\n");
+
+  struct addrinfo *ai;
+  struct addrinfo hints =
+    {
+      .ai_family = AF_UNSPEC,
+      .ai_protocol = IPPROTO_TCP,
+      .ai_socktype = SOCK_STREAM,
+    };
+  int ret = getaddrinfo ("sentinel.example", "80", &hints, &ai);
+  check_addrinfo (ai, ret, "address: STREAM/TCP 192.0.2.99 80\n");
+  if (ret == 0)
+    freeaddrinfo (ai);
+}
+
+static int
+do_test (void)
+{
+  configure_nss_files ();
+  char *root = create_chroot ();
+  create_etc_file (root, "hosts", create_hosts, NULL);
+  isolate_in_subprocess (root, test_hosts_longline);
+  free (root);
+  return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
diff --git a/nss/tst-nss-files.c b/nss/tst-nss-files.c
new file mode 100644
index 0000000..b233374
--- /dev/null
+++ b/nss/tst-nss-files.c
@@ -0,0 +1,314 @@
+/* Test nss_files parsing.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   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 <arpa/inet.h>
+#include <errno.h>
+#include <limits.h>
+#include <netdb.h>
+#include <nss.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tst-nss-aux.h"
+#include "tst-nss-files-aux.h"
+
+/* create_file_callback for /etc/netgroup.  */
+static void
+create_netgroup (const char *name, FILE *out, void *closure)
+{
+  fputs ("netgr gr1 gr2 gr3\n"
+         "gr1 (,u1,)\n"
+         "gr2 (,u2,)\n"
+         "gr3 gr5 gr6\n"
+         "gr5\n"
+         "gr6 (,u3,)\n", out);
+}
+
+/* create_file_callback for /etc/passwd.  */
+static void
+create_passwd (const char *name, FILE *out, void *closure)
+{
+  fputs ("root:x:0:0:glibc root test user:/root:/bin/bash\n", out);
+  fputs ("user0:x:1000:1000:glibc test user 0:/home/user0:/bin/bash\n", out);
+}
+
+/* create_file_callback for /etc/hosts.  */
+static void
+create_hosts (const char *name, FILE *out, void *closure)
+{
+  fputs ("127.0.0.1 localhost localhost.localdomain localhost4\n", out);
+  fputs ("::1 localhost localhost.localdomain localhost6\n", out);
+
+  /* Many IPv4 duplicates.  files-host.c does not reuse arrays of
+     addresses, therefore consuming excessive amounts of memory for
+     multiple matching entries.  If we ever fix that, we should test
+     with a much higher limit here.  */
+  enum { duplicate_count = 1000 };
+  for (unsigned i = 0; i < duplicate_count; ++i)
+    fputs ("192.0.2.254 duplicate-host-v4\n", out);
+
+  /* Many IPv6 duplicates.  */
+  for (unsigned i = 0; i < duplicate_count; ++i)
+    fputs ("2001:db8::fe duplicate-host-v6\n", out);
+
+  /* Both many IPv4 and IPv6 duplicates, non-interleaved.  */
+  for (unsigned i = 0; i < duplicate_count; ++i)
+    fputs ("192.0.2.253 duplicate-host\n", out);
+  for (unsigned i = 0; i < duplicate_count; ++i)
+    fputs ("2001:db8::fd duplicate-host\n", out);
+
+  /* Both many IPv4 and IPv6 duplicates, interleaved.  */
+  for (unsigned i = 0; i < duplicate_count; ++i)
+    {
+      fputs ("2001:db8::fc duplicate-host-interleaved\n", out);
+      fputs ("192.0.2.252 duplicate-host-interleaved\n", out);
+    }
+
+  /* End of file marker.  */
+  fputs ("192.0.2.99 sentinel.example\n", out);
+}
+
+/* Invoke all create_file_callback routines for the chroot environment
+   ROOT.  */
+static void
+create_database_files (const char *root)
+{
+  create_etc_file (root, "hosts", create_hosts, NULL);
+  create_etc_file (root, "netgroup", create_netgroup, NULL);
+  create_etc_file (root, "passwd", create_passwd, NULL);
+}
+
+/* Check that the reconfiguration to the test files was successful.
+   This function runs in a chrooted child process.  */
+static void
+test_basic (void)
+{
+  const struct passwd *pw = getpwuid (0);
+  if (pw == NULL)
+    {
+      printf ("error: root user lookup failed: %m\n");
+      exit (1);
+    }
+  if (strcmp (pw->pw_gecos, "glibc root test user") != 0)
+    {
+      printf ("error: incorrect GECOS field for root user: \"%s\n\"",
+              pw->pw_gecos);
+      exit (1);
+    }
+  if (access ("/dev/null", R_OK))
+    {
+      printf ("error: /dev/null is missing in chroot\n");
+      exit (1);
+    }
+  printf ("info: chroot appears to be set up properly\n");
+}
+
+/* Function to compare elements of an array of char * elements, as
+   null-terminated strings.  */
+static int
+sort_strings (const void *a, const void *b)
+{
+  return strcmp (*(const char **) a, *(const char **) b);
+}
+
+/* Test case from bug 17363: getnetgrent does not return the complete
+   list of entries if one of the netgroups in its definition tree is
+   empty.  This function runs in a chrooted child process.  */
+static void
+test_bug17363 (void)
+{
+  printf ("info: test_bug17363\n");
+  bool errors = false;
+
+  int ret = setnetgrent ("netgr");
+  if (ret != 1)
+    {
+      printf ("error: setnetgrent (\"netgr\"): %m\n");
+      errors = true;
+    }
+
+#define COUNT 3
+  static const char *const expected[COUNT] =
+    {
+      ",u1,",
+      ",u2,",
+      ",u3,",
+    };
+
+  char *actual[COUNT] = {};
+
+  for (size_t i = 0; ; ++i)
+    {
+      char *host;
+      char *user;
+      char *domain;
+      ret = getnetgrent (&host, &user, &domain);
+      if (ret != 1)
+        {
+          if (i == COUNT)
+            break;
+          printf ("error: getnetgrent (%zu)\n", i);
+          errors = true;
+          break;
+        }
+      if (i == COUNT)
+        {
+          printf ("error: getnetgrent (%zu): unexpected success\n", i);
+          errors = true;
+          break;
+        }
+
+      if (host == NULL)
+        host = (char *) "";
+      if (user == NULL)
+        user = (char *) "";
+      if (domain == NULL)
+        domain = (char *) "";
+
+      ret = asprintf (actual + i, "%s,%s,%s", host, user, domain);
+      if (ret < 0)
+        {
+          printf ("error: asprintf: %m\n");
+          exit (1);
+        }
+    }
+
+  endnetgrent ();
+
+  if (!errors)
+    {
+      qsort (actual, COUNT, sizeof (actual[0]), sort_strings);
+      for (size_t i = 0; i < COUNT; ++i)
+        {
+          if (strcmp (actual[i], expected[i]) != 0)
+            {
+              printf ("error: getnetgrent (%zu): \"%s\" instead of \"%s\"\n",
+                      i, actual[i], expected[i]);
+              errors = true;
+            }
+          free (actual[i]);
+        }
+    }
+#undef COUNT
+
+  exit (errors);
+}
+
+/* Look up HOSTNAME, with address FAMILY, and compare it to
+   EXPECTED_AI (getaddrinfo) and EXPECTED (gethostbyname).  */
+static void
+test_hosts (int family,
+            const char *hostname,
+            const char *expected_ai, const char *expected)
+{
+  {
+    struct addrinfo *ai;
+    struct addrinfo hints =
+      {
+        .ai_family = family,
+        .ai_protocol = IPPROTO_TCP,
+        .ai_socktype = SOCK_STREAM,
+      };
+    int ret = getaddrinfo (hostname, "80", &hints, &ai);
+    check_addrinfo (ai, ret, expected_ai);
+    if (ret == 0)
+      freeaddrinfo (ai);
+  }
+
+  if (family == AF_INET)
+    check_hostent (gethostbyname (hostname), expected);
+
+  if (family != AF_UNSPEC)
+    check_hostent (gethostbyname2 (hostname, family), expected);
+}
+
+/* Test case for bug 16072.  This function runs in a chrooted child
+   process.  */
+static void
+test_bug16072 (void)
+{
+  printf ("info: test_bug16072\n");
+  test_hosts (AF_INET, "duplicate-host",
+              "address: STREAM/TCP 192.0.2.253 80 (repeated 1000 times)\n",
+              "name: duplicate-host\n"
+              "address: 192.0.2.253 (repeated 1000 times)\n");
+  test_hosts (AF_INET, "duplicate-host-v4",
+              "address: STREAM/TCP 192.0.2.254 80 (repeated 1000 times)\n",
+              "name: duplicate-host-v4\n"
+              "address: 192.0.2.254 (repeated 1000 times)\n");
+  test_hosts (AF_INET6, "duplicate-host",
+              "address: STREAM/TCP 2001:db8::fd 80 (repeated 1000 times)\n",
+              "name: duplicate-host\n"
+              "address: 2001:db8::fd (repeated 1000 times)\n");
+  test_hosts (AF_INET6, "duplicate-host-v6",
+              "address: STREAM/TCP 2001:db8::fe 80 (repeated 1000 times)\n",
+              "name: duplicate-host-v6\n"
+              "address: 2001:db8::fe (repeated 1000 times)\n");
+  test_hosts (AF_UNSPEC, "duplicate-host",
+              "address: STREAM/TCP 192.0.2.253 80 (repeated 1000 times)\n"
+              "address: STREAM/TCP 2001:db8::fd 80 (repeated 1000 times)\n",
+              "");
+  test_hosts (AF_UNSPEC, "duplicate-host-v4",
+              "address: STREAM/TCP 192.0.2.254 80 (repeated 1000 times)\n",
+              "");
+  test_hosts (AF_UNSPEC, "duplicate-host-v6",
+              "address: STREAM/TCP 2001:db8::fe 80 (repeated 1000 times)\n",
+              "");
+  test_hosts (AF_UNSPEC, "duplicate-host-interleaved",
+              "address: STREAM/TCP 192.0.2.252 80 (repeated 1000 times)\n"
+              "address: STREAM/TCP 2001:db8::fc 80 (repeated 1000 times)\n",
+              "");
+}
+
+/* This test makes sure that the last entry in /etc/hosts can be read.
+   It runs in a chrooted child process.  */
+static void
+test_hosts_sentinel (void)
+{
+  printf ("info: test_hosts_sentinel\n");
+  test_hosts (AF_INET, "sentinel.example",
+              /* getaddrinfo */
+              "address: STREAM/TCP 192.0.2.99 80\n",
+              /* gethostbyname */
+              "name: sentinel.example\n"
+              "address: 192.0.2.99\n");
+  test_hosts (AF_UNSPEC, "sentinel.example",
+              "address: STREAM/TCP 192.0.2.99 80\n", NULL);
+}
+
+static int
+do_test (void)
+{
+  configure_nss_files ();
+  char *root = create_chroot ();
+  create_database_files (root);
+  isolate_in_subprocess (root, test_basic);
+  isolate_in_subprocess (root, test_bug17363);
+  isolate_in_subprocess (root, test_bug16072);
+  isolate_in_subprocess (root, test_hosts_sentinel);
+  free (root);
+
+  return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
