This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: nss_files testing


On 04/15/2016 12:10 PM, Florian Weimer wrote:
> I'm attaching a preview patch for testing nss_files. On recent
> kernels, with user namespaces enabled, the test can run without
> special privileges. There is also direct chroot support for systems
> which lack user namespaces.

TLDR; This looks great to me. You should be checking this in.
I need something like this to test the MERGE code in nss.

Just run it unconditionally in 'check' and exit(77) if you can't 
chroot or don't have namespaces enabled. This is an important enough
test framework that it should run always during development IMO.

The test should automatically run in 'xcheck' (superset of the tests)
where you guarantee the required privileges.

> One tricky bit is how to get the device nodes in /dev. I use a
> recursive bind mount for this (non-recursive outside of a namespace,
> due to bad devpts interactions). This means that the code is
> currently Linux-specific. I could probably add something that
> recreates critical device nodes (null, zero, random, urandom) when
> running as root, then the requirement for the bind mount would go
> away.

OK.

> Some things need cleanup. I want to submit this for real once I have
> some experience with adding nss_db support (which will reuse the
> chroot setup code from tst-nss-files-aux, moving it to
> tst-nss-files). Longer term, this approach should also work for
> testing nscd.

I think what you've got here should go into master. It's far better
than anything else we have and a good place for others to contribute.

> tst-nss-files-enumeration currently fails (bug 18991).

That's fine, we can xfail that.

> 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

OK.

>  
>  # 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

OK.

>  
>  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

You need a dependency on $(libdl) from the binary to ensure it is built by
the time you come to use it.

e.g.
$(objpfx)tst-nss-files: $(objpfx)tst-nss-aux.o $(objpfx)tst-nss-files-aux.o $(libdl)

> 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;
> +            }
> +        }
> +    }
> +}

OK.

> +
> +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;
> +}

OK.

> +
> +/* 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);

Why 17? Should this instead have been 77? UNSUPPORTED without diff installed?

We should add diffutils to manual/install.texi under "Recommended Tools for Compilation"

Makefile, Makerules, and configure all use diff.

> +    }
> +  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);

OK.

> +}
> +
> +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 ();
> +}

OK.

> +
> +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 ();
> +}
> +

OK.

> +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 ();
> +}

OK.

> +
> +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);
> +}

OK.

> +
> +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);
> +}
> +

OK.

> +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;
> +}
> +

OK.

> +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;
> +}

OK.

> +
> +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);
> +}

OK.

> +
> +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);
> +}

OK.

> +
> +/* Scratch buffer for storing one formatted addrinfo item.  */
> +struct format_ai_item
> +{
> +  unsigned count;
> +  char buffer[256];
> +};
> +

OK.

> +static void
> +format_ai_item (struct addrinfo *ai, struct format_ai_item *item)
> +{
> +
> +  /* ai_socktype */

Comment style.

> +  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 */

Likewise.

> +  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));
> +}

OK. Very very orderly. I like the style.

> +
> +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;
> +}

OK.

> +
> +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;
> +}
> +

OK.

> +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;
> +    }
> +}

OK.

> +
> +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);
> +    }
> +}

OK.

> +
> +/* 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);
> +}

OK.

> +
> +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;
> +}

OK.

> +
> +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);
> +}

OK.

> +
> +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;
> +}

OK.

> +
> +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;
> +}
> +

OK.

> +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 ();
> +    }
> +}
> +
> +

OK.

> +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 ();
> +    }
> +}
> +

OK.

> +static int errors;
> +pthread_mutex_t errors_lock = PTHREAD_MUTEX_INITIALIZER;
> +

*_delayed_failure functions need comments explaining the semantics.

> +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 */

OK.

> 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;

OK.

> +
> +/* 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;
> +    }
> +}
> +

OK.

> +/* 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);

OK.

> +}
> +
> +/* 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);
> +}

OK.

> +
> +/* 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);
> +}

OK.

> +
> +/* 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);
> +}

OK.

> +
> +/* 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;
> +}

OK.

> +
> +/* 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);
> +}

OK.

> +
> +static void
> +write_chroot_file_1 (const char *name, FILE *out, void *closure)
> +{
> +  fputs (closure, out);
> +}

OK.

> +
> +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);
> +}

OK.

> +
> +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;
> +}

OK.

> +
> +/* 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;

OK.

> +      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;
> +}

OK.

> +
> +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");

OK.

> +
> +      /* Preload DSOs outside the chroot.  */
> +      void *handle = dlopen (LIBNSS_FILES_SO, RTLD_LAZY);

Ah! Very clever. I was wondering how that would work.

> +      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);
> +}

OK.

> +
> +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);
> +}

OK.

> +
> +struct etc_file_closure
> +{
> +  const char *name;
> +  create_file_callback callback;
> +  void *closure;
> +};

OK.

> +
> +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);
> +}

OK.

> +
> +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);
> +}
> +

OK.

> +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 ();
> +    }
> +}
> +

OK.

> +
> +static void
> +__attribute__ ((destructor))
> +fini (void)
> +{
> +  process_cleanup_items ();
> +}
> diff -

OK.

-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 */

OK.

> 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);
> +}

OK.

> +
> +/* 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.  */

OK.

> +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);
> +        }
> +    }

OK.

> +
> +  fclose (reference);
> +  free (line_buffer);
> +
> +  switch (use_enumeration_initialization)
> +    {
> +    case no_setent:
> +      break;
> +    case use_setent:
> +    case use_setent_stayopen:
> +      endpwent ();
> +      break;
> +    }
> +}

OK.

> +
> +/* 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;

Don't use magic constants please.

> +           ++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);

Elegant. Nice composition.

> +
> +  return 0;
> +}

OK.

> +
> +#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;

OK. Great test!

> +}
> +
> +#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",
> +              "");

OK.

> +}
> +
> +/* 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);

OK.

> +}
> +
> +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"


-- 
Cheers,
Carlos.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]