8597553
commit 2714c5f3c95f90977167c1d21326d907fb76b419
8597553
Author: Florian Weimer <fweimer@redhat.com>
8597553
Date:   Fri Jun 2 15:50:36 2017 +0200
8597553
8597553
    resolv: Tests for various versions of res_init
8597553
8597553
diff --git a/resolv/Makefile b/resolv/Makefile
8597553
index d41fd4603d7b7d77..0ebb2af795c6fb7b 100644
8597553
--- a/resolv/Makefile
8597553
+++ b/resolv/Makefile
8597553
@@ -57,6 +57,11 @@ ifeq (yes,$(build-shared))
8597553
 tests += \
8597553
   tst-resolv-canonname \
8597553
 
8597553
+# uses DEPRECATED_RES_USE_INET6 from <resolv-internal.h>.
8597553
+tests += \
8597553
+  tst-resolv-res_init \
8597553
+  tst-resolv-res_init-thread \
8597553
+
8597553
 endif
8597553
 
8597553
 # This test sends millions of packets and is rather slow.
8597553
@@ -136,6 +141,9 @@ $(objpfx)tst-res_use_inet6: $(objpfx)libresolv.so $(shared-thread-library)
8597553
 $(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library)
8597553
 $(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library)
8597553
 $(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
8597553
+$(objpfx)tst-resolv-res_init: $(libdl) $(objpfx)libresolv.so
8597553
+$(objpfx)tst-resolv-res_init-thread: $(libdl) $(objpfx)libresolv.so \
8597553
+  $(shared-thread-library)
8597553
 $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
8597553
 $(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library)
8597553
 $(objpfx)tst-resolv-canonname: \
8597553
diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
8597553
new file mode 100644
8597553
index 0000000000000000..1d2c475c4b233131
8597553
--- /dev/null
8597553
+++ b/resolv/tst-resolv-res_init-skeleton.c
8597553
@@ -0,0 +1,601 @@
8597553
+/* Test parsing of /etc/resolv.conf.  Genric version.
8597553
+   Copyright (C) 2017 Free Software Foundation, Inc.
8597553
+   This file is part of the GNU C Library.
8597553
+
8597553
+   The GNU C Library is free software; you can redistribute it and/or
8597553
+   modify it under the terms of the GNU Lesser General Public
8597553
+   License as published by the Free Software Foundation; either
8597553
+   version 2.1 of the License, or (at your option) any later version.
8597553
+
8597553
+   The GNU C Library is distributed in the hope that it will be useful,
8597553
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
8597553
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
8597553
+   Lesser General Public License for more details.
8597553
+
8597553
+   You should have received a copy of the GNU Lesser General Public
8597553
+   License along with the GNU C Library; if not, see
8597553
+   <http://www.gnu.org/licenses/>.  */
8597553
+
8597553
+/* Before including this file, TEST_THREAD has to be defined to 0 or
8597553
+   1, depending on whether the threading tests should be compiled
8597553
+   in.  */
8597553
+
8597553
+#include <arpa/inet.h>
8597553
+#include <gnu/lib-names.h>
8597553
+#include <netdb.h>
8597553
+#include <resolv/resolv-internal.h> /* For DEPRECATED_RES_USE_INET6.  */
8597553
+#include <stdio.h>
8597553
+#include <stdlib.h>
8597553
+#include <support/capture_subprocess.h>
8597553
+#include <support/check.h>
8597553
+#include <support/namespace.h>
8597553
+#include <support/run_diff.h>
8597553
+#include <support/support.h>
8597553
+#include <support/temp_file.h>
8597553
+#include <support/test-driver.h>
8597553
+#include <support/xstdio.h>
8597553
+#include <support/xunistd.h>
8597553
+
8597553
+#if TEST_THREAD
8597553
+# include <support/xthread.h>
8597553
+#endif
8597553
+
8597553
+/* This is the host name used to ensure predictable behavior of
8597553
+   res_init.  */
8597553
+static const char *const test_hostname = "www.example.com";
8597553
+
8597553
+/* Path to the test root directory.  */
8597553
+static char *path_chroot;
8597553
+
8597553
+/* Path to resolv.conf under path_chroot (outside the chroot).  */
8597553
+static char *path_resolv_conf;
8597553
+
8597553
+static void
8597553
+prepare (int argc, char **argv)
8597553
+{
8597553
+  path_chroot = xasprintf ("%s/tst-resolv-res_init-XXXXXX", test_dir);
8597553
+  if (mkdtemp (path_chroot) == NULL)
8597553
+    FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", path_chroot);
8597553
+  add_temp_file (path_chroot);
8597553
+
8597553
+  /* Create the /etc directory in the chroot environment.  */
8597553
+  char *path_etc = xasprintf ("%s/etc", path_chroot);
8597553
+  xmkdir (path_etc, 0777);
8597553
+  add_temp_file (path_etc);
8597553
+
8597553
+  /* Create an empty resolv.conf file.  */
8597553
+  path_resolv_conf = xasprintf ("%s/resolv.conf", path_etc);
8597553
+  add_temp_file (path_resolv_conf);
8597553
+  support_write_file_string (path_resolv_conf, "");
8597553
+
8597553
+  free (path_etc);
8597553
+
8597553
+  /* valgrind needs a temporary directory in the chroot.  */
8597553
+  {
8597553
+    char *path_tmp = xasprintf ("%s/tmp", path_chroot);
8597553
+    xmkdir (path_tmp, 0777);
8597553
+    add_temp_file (path_tmp);
8597553
+    free (path_tmp);
8597553
+  }
8597553
+}
8597553
+
8597553
+/* Verify that the chroot environment has been set up.  */
8597553
+static void
8597553
+check_chroot_working (void *closure)
8597553
+{
8597553
+  xchroot (path_chroot);
8597553
+  FILE *fp = xfopen (_PATH_RESCONF, "r");
8597553
+  xfclose (fp);
8597553
+
8597553
+  TEST_VERIFY_EXIT (res_init () == 0);
8597553
+  TEST_VERIFY (_res.options & RES_INIT);
8597553
+
8597553
+  char buf[100];
8597553
+  if (gethostname (buf, sizeof (buf)) < 0)
8597553
+    FAIL_EXIT1 ("gethostname: %m");
8597553
+  if (strcmp (buf, test_hostname) != 0)
8597553
+    FAIL_EXIT1 ("unexpected host name: %s", buf);
8597553
+}
8597553
+
8597553
+/* If FLAG is set in *OPTIONS, write NAME to FP, and clear it in
8597553
+   *OPTIONS.  */
8597553
+static void
8597553
+print_option_flag (FILE *fp, int *options, int flag, const char *name)
8597553
+{
8597553
+  if (*options & flag)
8597553
+    {
8597553
+      fprintf (fp, " %s", name);
8597553
+      *options &= ~flag;
8597553
+    }
8597553
+}
8597553
+
8597553
+/* Write a decoded version of the resolver configuration *RESP to the
8597553
+   stream FP.  */
8597553
+static void
8597553
+print_resp (FILE *fp, res_state resp)
8597553
+{
8597553
+  /* The options directive.  */
8597553
+  {
8597553
+    /* RES_INIT is used internally for tracking initialization.  */
8597553
+    TEST_VERIFY (resp->options & RES_INIT);
8597553
+    /* Also mask out other default flags which cannot be set through
8597553
+       the options directive.  */
8597553
+    int options
8597553
+      = resp->options & ~(RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH);
8597553
+    if (options != 0
8597553
+        || resp->ndots != 1
8597553
+        || resp->retrans != RES_TIMEOUT
8597553
+        || resp->retry != RES_DFLRETRY)
8597553
+      {
8597553
+        fputs ("options", fp);
8597553
+        if (resp->ndots != 1)
8597553
+          fprintf (fp, " ndots:%d", resp->ndots);
8597553
+        if (resp->retrans != RES_TIMEOUT)
8597553
+          fprintf (fp, " timeout:%d", resp->retrans);
8597553
+        if (resp->retry != RES_DFLRETRY)
8597553
+          fprintf (fp, " attempts:%d", resp->retry);
8597553
+        print_option_flag (fp, &options, RES_USEVC, "use-vc");
8597553
+        print_option_flag (fp, &options, DEPRECATED_RES_USE_INET6, "inet6");
8597553
+        print_option_flag (fp, &options, RES_ROTATE, "rotate");
8597553
+        print_option_flag (fp, &options, RES_USE_EDNS0, "edns0");
8597553
+        print_option_flag (fp, &options, RES_SNGLKUP,
8597553
+                           "single-request");
8597553
+        print_option_flag (fp, &options, RES_SNGLKUPREOP,
8597553
+                           "single-request-reopen");
8597553
+        print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
8597553
+        fputc ('\n', fp);
8597553
+        if (options != 0)
8597553
+          fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
8597553
+      }
8597553
+  }
8597553
+
8597553
+  /* The search and domain directives.  */
8597553
+  if (resp->dnsrch[0] != NULL)
8597553
+    {
8597553
+      fputs ("search", fp);
8597553
+      for (int i = 0; i < MAXDNSRCH && resp->dnsrch[i] != NULL; ++i)
8597553
+        {
8597553
+          fputc (' ', fp);
8597553
+          fputs (resp->dnsrch[i], fp);
8597553
+        }
8597553
+      fputc ('\n', fp);
8597553
+    }
8597553
+  else if (resp->defdname[0] != '\0')
8597553
+    fprintf (fp, "domain %s\n", resp->defdname);
8597553
+
8597553
+  /* The sortlist directive.  */
8597553
+  if (resp->nsort > 0)
8597553
+    {
8597553
+      fputs ("sortlist", fp);
8597553
+      for (int i = 0; i < resp->nsort && i < MAXRESOLVSORT; ++i)
8597553
+        {
8597553
+          char net[20];
8597553
+          if (inet_ntop (AF_INET, &resp->sort_list[i].addr,
8597553
+                         net, sizeof (net)) == NULL)
8597553
+            FAIL_EXIT1 ("inet_ntop: %m\n");
8597553
+          char mask[20];
8597553
+          if (inet_ntop (AF_INET, &resp->sort_list[i].mask,
8597553
+                         mask, sizeof (mask)) == NULL)
8597553
+            FAIL_EXIT1 ("inet_ntop: %m\n");
8597553
+          fprintf (fp, " %s/%s", net, mask);
8597553
+        }
8597553
+      fputc ('\n', fp);
8597553
+    }
8597553
+
8597553
+  /* The nameserver directives.  */
8597553
+  for (size_t i = 0; i < resp->nscount; ++i)
8597553
+    {
8597553
+      char host[NI_MAXHOST];
8597553
+      char service[NI_MAXSERV];
8597553
+
8597553
+      /* See get_nsaddr in res_send.c.  */
8597553
+      void *addr;
8597553
+      size_t addrlen;
8597553
+      if (resp->nsaddr_list[i].sin_family == 0
8597553
+          && resp->_u._ext.nsaddrs[i] != NULL)
8597553
+        {
8597553
+          addr = resp->_u._ext.nsaddrs[i];
8597553
+          addrlen = sizeof (*resp->_u._ext.nsaddrs[i]);
8597553
+        }
8597553
+      else
8597553
+        {
8597553
+          addr = &resp->nsaddr_list[i];
8597553
+          addrlen = sizeof (resp->nsaddr_list[i]);
8597553
+        }
8597553
+
8597553
+      int ret = getnameinfo (addr, addrlen,
8597553
+                             host, sizeof (host), service, sizeof (service),
8597553
+                             NI_NUMERICHOST | NI_NUMERICSERV);
8597553
+      if (ret != 0)
8597553
+        {
8597553
+          if (ret == EAI_SYSTEM)
8597553
+            fprintf (fp, "; error: getnameinfo: %m\n");
8597553
+          else
8597553
+            fprintf (fp, "; error: getnameinfo: %s\n", gai_strerror (ret));
8597553
+        }
8597553
+      else
8597553
+        {
8597553
+          fprintf (fp, "nameserver %s\n", host);
8597553
+          if (strcmp (service, "53") != 0)
8597553
+            fprintf (fp, "; unrepresentable port number %s\n\n", service);
8597553
+        }
8597553
+    }
8597553
+
8597553
+  TEST_VERIFY (!ferror (fp));
8597553
+}
8597553
+
8597553
+/* Parameters of one test case.  */
8597553
+struct test_case
8597553
+{
8597553
+  /* A short, descriptive name of the test.  */
8597553
+  const char *name;
8597553
+
8597553
+  /* The contents of the /etc/resolv.conf file.  */
8597553
+  const char *conf;
8597553
+
8597553
+  /* The expected output from print_resp.  */
8597553
+  const char *expected;
8597553
+
8597553
+  /* Setting for the LOCALDOMAIN environment variable.  NULL if the
8597553
+     variable is not to be set.  */
8597553
+  const char *localdomain;
8597553
+
8597553
+  /* Setting for the RES_OPTIONS environment variable.  NULL if the
8597553
+     variable is not to be set.  */
8597553
+  const char *res_options;
8597553
+};
8597553
+
8597553
+enum test_init
8597553
+{
8597553
+  test_init,
8597553
+  test_ninit,
8597553
+  test_mkquery,
8597553
+  test_gethostbyname,
8597553
+  test_getaddrinfo,
8597553
+  test_init_method_last = test_getaddrinfo
8597553
+};
8597553
+
8597553
+/* Closure argument for run_res_init.  */
8597553
+struct test_context
8597553
+{
8597553
+  enum test_init init;
8597553
+  const struct test_case *t;
8597553
+};
8597553
+
8597553
+static void
8597553
+setup_nss_dns_and_chroot (void)
8597553
+{
8597553
+  /* Load nss_dns outside of the chroot.  */
8597553
+  if (dlopen (LIBNSS_DNS_SO, RTLD_LAZY) == NULL)
8597553
+    FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s", dlerror ());
8597553
+  xchroot (path_chroot);
8597553
+  /* Force the use of nss_dns.  */
8597553
+  __nss_configure_lookup ("hosts", "dns");
8597553
+}
8597553
+
8597553
+/* Run res_ninit or res_init in a subprocess and dump the parsed
8597553
+   resolver state to standard output.  */
8597553
+static void
8597553
+run_res_init (void *closure)
8597553
+{
8597553
+  struct test_context *ctx = closure;
8597553
+  TEST_VERIFY (getenv ("LOCALDOMAIN") == NULL);
8597553
+  TEST_VERIFY (getenv ("RES_OPTIONS") == NULL);
8597553
+  if (ctx->t->localdomain != NULL)
8597553
+    setenv ("LOCALDOMAIN", ctx->t->localdomain, 1);
8597553
+  if (ctx->t->res_options != NULL)
8597553
+    setenv ("RES_OPTIONS", ctx->t->res_options, 1);
8597553
+
8597553
+  switch (ctx->init)
8597553
+    {
8597553
+    case test_init:
8597553
+      xchroot (path_chroot);
8597553
+      TEST_VERIFY (res_init () == 0);
8597553
+      print_resp (stdout, &_res);
8597553
+      return;
8597553
+
8597553
+    case test_ninit:
8597553
+      xchroot (path_chroot);
8597553
+      res_state resp = xmalloc (sizeof (*resp));
8597553
+      memset (resp, 0, sizeof (*resp));
8597553
+      TEST_VERIFY (res_ninit (resp) == 0);
8597553
+      print_resp (stdout, resp);
8597553
+      res_nclose (resp);
8597553
+      free (resp);
8597553
+      return;
8597553
+
8597553
+    case test_mkquery:
8597553
+      xchroot (path_chroot);
8597553
+      unsigned char buf[512];
8597553
+      TEST_VERIFY (res_mkquery (QUERY, "www.example",
8597553
+                                C_IN, ns_t_a, NULL, 0,
8597553
+                                NULL, buf, sizeof (buf)) > 0);
8597553
+      print_resp (stdout, &_res);
8597553
+      return;
8597553
+
8597553
+    case test_gethostbyname:
8597553
+      setup_nss_dns_and_chroot ();
8597553
+      /* Trigger implicit initialization of the _res structure.  The
8597553
+         actual lookup result is immaterial.  */
8597553
+      (void )gethostbyname ("www.example");
8597553
+      print_resp (stdout, &_res);
8597553
+      return;
8597553
+
8597553
+    case test_getaddrinfo:
8597553
+      setup_nss_dns_and_chroot ();
8597553
+      /* Trigger implicit initialization of the _res structure.  The
8597553
+         actual lookup result is immaterial.  */
8597553
+      struct addrinfo *ai;
8597553
+      (void) getaddrinfo ("www.example", NULL, NULL, &ai;;
8597553
+      print_resp (stdout, &_res);
8597553
+      return;
8597553
+    }
8597553
+
8597553
+  FAIL_EXIT1 ("invalid init method %d", ctx->init);
8597553
+}
8597553
+
8597553
+#if TEST_THREAD
8597553
+/* Helper function which calls run_res_init from a thread.  */
8597553
+static void *
8597553
+run_res_init_thread_func (void *closure)
8597553
+{
8597553
+  run_res_init (closure);
8597553
+  return NULL;
8597553
+}
8597553
+
8597553
+/* Variant of res_run_init which runs the function on a non-main
8597553
+   thread.  */
8597553
+static void
8597553
+run_res_init_on_thread (void *closure)
8597553
+{
8597553
+  xpthread_join (xpthread_create (NULL, run_res_init_thread_func, closure));
8597553
+}
8597553
+#endif /* TEST_THREAD */
8597553
+
8597553
+struct test_case test_cases[] =
8597553
+  {
8597553
+    {.name = "empty file",
8597553
+     .conf = "",
8597553
+     .expected = "search example.com\n"
8597553
+     "nameserver 127.0.0.1\n"
8597553
+    },
8597553
+    {.name = "empty file with LOCALDOMAIN",
8597553
+     .conf = "",
8597553
+     .expected = "search example.net\n"
8597553
+     "nameserver 127.0.0.1\n",
8597553
+     .localdomain = "example.net",
8597553
+    },
8597553
+    {.name = "empty file with RES_OPTIONS",
8597553
+     .conf = "",
8597553
+     .expected = "options attempts:5 edns0\n"
8597553
+     "search example.com\n"
8597553
+     "nameserver 127.0.0.1\n",
8597553
+     .res_options = "edns0 attempts:5",
8597553
+    },
8597553
+    {.name = "empty file with RES_OPTIONS and LOCALDOMAIN",
8597553
+     .conf = "",
8597553
+     .expected = "options attempts:5 edns0\n"
8597553
+     "search example.org\n"
8597553
+     "nameserver 127.0.0.1\n",
8597553
+     .localdomain = "example.org",
8597553
+     .res_options = "edns0 attempts:5",
8597553
+    },
8597553
+    {.name = "basic",
8597553
+     .conf = "domain example.net\n"
8597553
+     "search corp.example.com example.com\n"
8597553
+     "nameserver 192.0.2.1\n",
8597553
+     .expected = "search corp.example.com example.com\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+    },
8597553
+    {.name = "whitespace",
8597553
+     .conf = "# This test covers comment and whitespace processing "
8597553
+     " (trailing whitespace,\n"
8597553
+     "# missing newline at end of file).\n"
8597553
+     "\n"
8597553
+     "domain  example.net\n"
8597553
+     ";search commented out\n"
8597553
+     "search corp.example.com\texample.com\n"
8597553
+     "#nameserver 192.0.2.3\n"
8597553
+     "nameserver 192.0.2.1 \n"
8597553
+     "nameserver 192.0.2.2",    /* No \n at end of file.  */
8597553
+     .expected = "search corp.example.com example.com\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver 192.0.2.2\n"
8597553
+    },
8597553
+    {.name = "option values, multiple servers",
8597553
+     .conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
8597553
+     "domain  example.net\n"
8597553
+     ";domain comment\n"
8597553
+     "search corp.example.com\texample.com\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver ::1\n"
8597553
+     "nameserver 192.0.2.2\n",
8597553
+     .expected = "options ndots:3 timeout:19 attempts:5 inet6 edns0\n"
8597553
+     "search corp.example.com example.com\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver ::1\n"
8597553
+     "nameserver 192.0.2.2\n"
8597553
+    },
8597553
+    {.name = "out-of-range option vales",
8597553
+     .conf = "options use-vc timeout:999 attempts:999 ndots:99\n"
8597553
+     "search example.com\n",
8597553
+     .expected = "options ndots:15 timeout:30 attempts:5 use-vc\n"
8597553
+     "search example.com\n"
8597553
+     "nameserver 127.0.0.1\n"
8597553
+    },
8597553
+    {.name = "repeated directives",
8597553
+     .conf = "options ndots:3 use-vc\n"
8597553
+     "options edns0 ndots:2\n"
8597553
+     "domain corp.example\n"
8597553
+     "search example.net corp.example.com example.com\n"
8597553
+     "search example.org\n"
8597553
+     "search\n",
8597553
+     .expected = "options ndots:2 use-vc edns0\n"
8597553
+     "search example.org\n"
8597553
+     "nameserver 127.0.0.1\n"
8597553
+    },
8597553
+    {.name = "many name servers, sortlist",
8597553
+     .conf = "options single-request\n"
8597553
+     "search example.org example.com example.net corp.example.com\n"
8597553
+     "sortlist 192.0.2.0/255.255.255.0\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver 192.0.2.2\n"
8597553
+     "nameserver 192.0.2.3\n"
8597553
+     "nameserver 192.0.2.4\n"
8597553
+     "nameserver 192.0.2.5\n"
8597553
+     "nameserver 192.0.2.6\n"
8597553
+     "nameserver 192.0.2.7\n"
8597553
+     "nameserver 192.0.2.8\n",
8597553
+     .expected = "options single-request\n"
8597553
+     "search example.org example.com example.net corp.example.com\n"
8597553
+     "sortlist 192.0.2.0/255.255.255.0\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver 192.0.2.2\n"
8597553
+     "nameserver 192.0.2.3\n"
8597553
+    },
8597553
+    {.name = "IPv4 and IPv6 nameservers",
8597553
+     .conf = "options single-request\n"
8597553
+     "search example.org example.com example.net corp.example.com"
8597553
+     " legacy.example.com\n"
8597553
+     "sortlist 192.0.2.0\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver 2001:db8::2\n"
8597553
+     "nameserver 192.0.2.3\n"
8597553
+     "nameserver 2001:db8::4\n"
8597553
+     "nameserver 192.0.2.5\n"
8597553
+     "nameserver 2001:db8::6\n"
8597553
+     "nameserver 192.0.2.7\n"
8597553
+     "nameserver 2001:db8::8\n",
8597553
+     .expected = "options single-request\n"
8597553
+     "search example.org example.com example.net corp.example.com"
8597553
+     " legacy.example.com\n"
8597553
+     "sortlist 192.0.2.0/255.255.255.0\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver 2001:db8::2\n"
8597553
+     "nameserver 192.0.2.3\n"
8597553
+    },
8597553
+    {.name = "garbage after nameserver",
8597553
+     .conf = "nameserver 192.0.2.1 garbage\n"
8597553
+     "nameserver 192.0.2.2:5353\n"
8597553
+     "nameserver 192.0.2.3 5353\n",
8597553
+     .expected = "search example.com\n"
8597553
+     "nameserver 192.0.2.1\n"
8597553
+     "nameserver 192.0.2.3\n"
8597553
+    },
8597553
+    {.name = "RES_OPTIONS is cummulative",
8597553
+     .conf = "options timeout:7 ndots:2 use-vc\n"
8597553
+     "nameserver 192.0.2.1\n",
8597553
+     .expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n"
8597553
+     "search example.com\n"
8597553
+     "nameserver 192.0.2.1\n",
8597553
+     .res_options = "attempts:5 ndots:3 edns0 ",
8597553
+    },
8597553
+    { NULL }
8597553
+  };
8597553
+
8597553
+/* Run the indicated test case.  This function assumes that the chroot
8597553
+   contents has already been set up.  */
8597553
+static void
8597553
+test_file_contents (const struct test_case *t)
8597553
+{
8597553
+#if TEST_THREAD
8597553
+  for (int do_thread = 0; do_thread < 2; ++do_thread)
8597553
+#endif
8597553
+    for (int init_method = 0; init_method <= test_init_method_last;
8597553
+         ++init_method)
8597553
+      {
8597553
+        if (test_verbose > 0)
8597553
+          printf ("info:  testing init method %d\n", init_method);
8597553
+        struct test_context ctx = { .init = init_method, .t = t };
8597553
+        void (*func) (void *) = run_res_init;
8597553
+#if TEST_THREAD
8597553
+        if (do_thread)
8597553
+          func = run_res_init_on_thread;
8597553
+#endif
8597553
+        struct support_capture_subprocess proc
8597553
+          = support_capture_subprocess (func, &ctx;;
8597553
+        if (strcmp (proc.out.buffer, t->expected) != 0)
8597553
+          {
8597553
+            support_record_failure ();
8597553
+            printf ("error: output mismatch for %s\n", t->name);
8597553
+            support_run_diff ("expected", t->expected,
8597553
+                              "actual", proc.out.buffer);
8597553
+          }
8597553
+        support_capture_subprocess_check (&proc, t->name, 0,
8597553
+                                          sc_allow_stdout);
8597553
+        support_capture_subprocess_free (&proc;;
8597553
+      }
8597553
+}
8597553
+
8597553
+static int
8597553
+do_test (void)
8597553
+{
8597553
+  support_become_root ();
8597553
+  support_enter_network_namespace ();
8597553
+  if (!support_in_uts_namespace () || !support_can_chroot ())
8597553
+    return EXIT_UNSUPPORTED;
8597553
+
8597553
+  /* We are in an UTS namespace, so we can set the host name without
8597553
+     altering the state of the entire system.  */
8597553
+  if (sethostname (test_hostname, strlen (test_hostname)) != 0)
8597553
+    FAIL_EXIT1 ("sethostname: %m");
8597553
+
8597553
+  /* These environment variables affect resolv.conf parsing.  */
8597553
+  unsetenv ("LOCALDOMAIN");
8597553
+  unsetenv ("RES_OPTIONS");
8597553
+
8597553
+  /* Ensure that the chroot setup worked.  */
8597553
+  {
8597553
+    struct support_capture_subprocess proc
8597553
+      = support_capture_subprocess (check_chroot_working, NULL);
8597553
+    support_capture_subprocess_check (&proc, "chroot", 0, sc_allow_none);
8597553
+    support_capture_subprocess_free (&proc;;
8597553
+  }
8597553
+
8597553
+  for (size_t i = 0; test_cases[i].name != NULL; ++i)
8597553
+    {
8597553
+      if (test_verbose > 0)
8597553
+        printf ("info: running test: %s\n", test_cases[i].name);
8597553
+      TEST_VERIFY (test_cases[i].conf != NULL);
8597553
+      TEST_VERIFY (test_cases[i].expected != NULL);
8597553
+
8597553
+      support_write_file_string (path_resolv_conf, test_cases[i].conf);
8597553
+
8597553
+      test_file_contents (&test_cases[i]);
8597553
+
8597553
+      /* The expected output from the empty file test is used for
8597553
+         further tests.  */
8597553
+      if (test_cases[i].conf[0] == '\0')
8597553
+        {
8597553
+          if (test_verbose > 0)
8597553
+            printf ("info:  special test: missing file\n");
8597553
+          TEST_VERIFY (unlink (path_resolv_conf) == 0);
8597553
+          test_file_contents (&test_cases[i]);
8597553
+
8597553
+          if (test_verbose > 0)
8597553
+            printf ("info:  special test: dangling symbolic link\n");
8597553
+          TEST_VERIFY (symlink ("does-not-exist", path_resolv_conf) == 0);
8597553
+          test_file_contents (&test_cases[i]);
8597553
+          TEST_VERIFY (unlink (path_resolv_conf) == 0);
8597553
+
8597553
+          if (test_verbose > 0)
8597553
+            printf ("info:  special test: unreadable file\n");
8597553
+          support_write_file_string (path_resolv_conf, "");
8597553
+          TEST_VERIFY (chmod (path_resolv_conf, 0) == 0);
8597553
+          test_file_contents (&test_cases[i]);
8597553
+
8597553
+          /* Restore the empty file.  */
8597553
+          TEST_VERIFY (unlink (path_resolv_conf) == 0);
8597553
+          support_write_file_string (path_resolv_conf, "");
8597553
+        }
8597553
+    }
8597553
+
8597553
+  free (path_chroot);
8597553
+  path_chroot = NULL;
8597553
+  free (path_resolv_conf);
8597553
+  path_resolv_conf = NULL;
8597553
+  return 0;
8597553
+}
8597553
+
8597553
+#define PREPARE prepare
8597553
+#include <support/test-driver.c>
8597553
diff --git a/resolv/tst-resolv-res_init-thread.c b/resolv/tst-resolv-res_init-thread.c
8597553
new file mode 100644
8597553
index 0000000000000000..f47ac34f7fc7a6c8
8597553
--- /dev/null
8597553
+++ b/resolv/tst-resolv-res_init-thread.c
8597553
@@ -0,0 +1,20 @@
8597553
+/* Test parsing of /etc/resolv.conf, threading version.
8597553
+   Copyright (C) 2017 Free Software Foundation, Inc.
8597553
+   This file is part of the GNU C Library.
8597553
+
8597553
+   The GNU C Library is free software; you can redistribute it and/or
8597553
+   modify it under the terms of the GNU Lesser General Public
8597553
+   License as published by the Free Software Foundation; either
8597553
+   version 2.1 of the License, or (at your option) any later version.
8597553
+
8597553
+   The GNU C Library is distributed in the hope that it will be useful,
8597553
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
8597553
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
8597553
+   Lesser General Public License for more details.
8597553
+
8597553
+   You should have received a copy of the GNU Lesser General Public
8597553
+   License along with the GNU C Library; if not, see
8597553
+   <http://www.gnu.org/licenses/>.  */
8597553
+
8597553
+#define TEST_THREAD 1
8597553
+#include "tst-resolv-res_init-skeleton.c"
8597553
diff --git a/resolv/tst-resolv-res_init.c b/resolv/tst-resolv-res_init.c
8597553
new file mode 100644
8597553
index 0000000000000000..40c0154eca7363cb
8597553
--- /dev/null
8597553
+++ b/resolv/tst-resolv-res_init.c
8597553
@@ -0,0 +1,20 @@
8597553
+/* Test parsing of /etc/resolv.conf, non-threading version.
8597553
+   Copyright (C) 2017 Free Software Foundation, Inc.
8597553
+   This file is part of the GNU C Library.
8597553
+
8597553
+   The GNU C Library is free software; you can redistribute it and/or
8597553
+   modify it under the terms of the GNU Lesser General Public
8597553
+   License as published by the Free Software Foundation; either
8597553
+   version 2.1 of the License, or (at your option) any later version.
8597553
+
8597553
+   The GNU C Library is distributed in the hope that it will be useful,
8597553
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
8597553
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
8597553
+   Lesser General Public License for more details.
8597553
+
8597553
+   You should have received a copy of the GNU Lesser General Public
8597553
+   License along with the GNU C Library; if not, see
8597553
+   <http://www.gnu.org/licenses/>.  */
8597553
+
8597553
+#define TEST_THREAD 0
8597553
+#include "tst-resolv-res_init-skeleton.c"