]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - resolv/tst-resolv-res_init-skeleton.c
elf: Refuse to dlopen PIE objects [BZ #24323]
[thirdparty/glibc.git] / resolv / tst-resolv-res_init-skeleton.c
index 2b68c5ff9a69a291f30c1f9a79ca6b56012ba7db..678f396c4c3596deaa8fed4093e412b222b54953 100644 (file)
@@ -1,5 +1,5 @@
 /* Test parsing of /etc/resolv.conf.  Genric version.
-   Copyright (C) 2017 Free Software Foundation, Inc.
+   Copyright (C) 2017-2019 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
@@ -24,7 +24,7 @@
 #include <errno.h>
 #include <gnu/lib-names.h>
 #include <netdb.h>
-#include <resolv/resolv-internal.h> /* For DEPRECATED_RES_USE_INET6.  */
+#include <resolv/resolv_context.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <support/capture_subprocess.h>
    res_init.  */
 static const char *const test_hostname = "www.example.com";
 
-/* Path to the test root directory.  */
-static char *path_chroot;
-
-/* Path to resolv.conf under path_chroot (outside the chroot).  */
-static char *path_resolv_conf;
+struct support_chroot *chroot_env;
 
 static void
 prepare (int argc, char **argv)
 {
-  path_chroot = xasprintf ("%s/tst-resolv-res_init-XXXXXX", test_dir);
-  if (mkdtemp (path_chroot) == NULL)
-    FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", path_chroot);
-  add_temp_file (path_chroot);
-
-  /* Create the /etc directory in the chroot environment.  */
-  char *path_etc = xasprintf ("%s/etc", path_chroot);
-  xmkdir (path_etc, 0777);
-  add_temp_file (path_etc);
-
-  /* Create an empty resolv.conf file.  */
-  path_resolv_conf = xasprintf ("%s/resolv.conf", path_etc);
-  add_temp_file (path_resolv_conf);
-  support_write_file_string (path_resolv_conf, "");
-
-  free (path_etc);
-
-  /* valgrind needs a temporary directory in the chroot.  */
-  {
-    char *path_tmp = xasprintf ("%s/tmp", path_chroot);
-    xmkdir (path_tmp, 0777);
-    add_temp_file (path_tmp);
-    free (path_tmp);
-  }
+  chroot_env = support_chroot_create
+    ((struct support_chroot_configuration)
+     {
+       .resolv_conf = "",
+     });
 }
 
 /* Verify that the chroot environment has been set up.  */
 static void
 check_chroot_working (void *closure)
 {
-  xchroot (path_chroot);
+  xchroot (chroot_env->path_chroot);
   FILE *fp = xfopen (_PATH_RESCONF, "r");
   xfclose (fp);
 
@@ -116,6 +93,11 @@ print_option_flag (FILE *fp, int *options, int flag, const char *name)
 static void
 print_resp (FILE *fp, res_state resp)
 {
+  struct resolv_context *ctx = __resolv_context_get_override (resp);
+  TEST_VERIFY_EXIT (ctx != NULL);
+  if (ctx->conf == NULL)
+    fprintf (fp, "; extended resolver state missing\n");
+
   /* The options directive.  */
   {
     /* RES_INIT is used internally for tracking initialization.  */
@@ -137,7 +119,6 @@ print_resp (FILE *fp, res_state resp)
         if (resp->retry != RES_DFLRETRY)
           fprintf (fp, " attempts:%d", resp->retry);
         print_option_flag (fp, &options, RES_USEVC, "use-vc");
-        print_option_flag (fp, &options, DEPRECATED_RES_USE_INET6, "inet6");
         print_option_flag (fp, &options, RES_ROTATE, "rotate");
         print_option_flag (fp, &options, RES_USE_EDNS0, "edns0");
         print_option_flag (fp, &options, RES_SNGLKUP,
@@ -145,6 +126,7 @@ print_resp (FILE *fp, res_state resp)
         print_option_flag (fp, &options, RES_SNGLKUPREOP,
                            "single-request-reopen");
         print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
+        print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
         fputc ('\n', fp);
         if (options != 0)
           fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
@@ -165,6 +147,19 @@ print_resp (FILE *fp, res_state resp)
   else if (resp->defdname[0] != '\0')
     fprintf (fp, "domain %s\n", resp->defdname);
 
+  /* The extended search path.  */
+  {
+    size_t i = 0;
+    while (true)
+      {
+        const char *name = __resolv_context_search_list (ctx, i);
+        if (name == NULL)
+          break;
+        fprintf (fp, "; search[%zu]: %s\n", i, name);
+        ++i;
+      }
+  }
+
   /* The sortlist directive.  */
   if (resp->nsort > 0)
     {
@@ -223,7 +218,49 @@ print_resp (FILE *fp, res_state resp)
         }
     }
 
+  /* The extended name server list.  */
+  {
+    size_t i = 0;
+    while (true)
+      {
+        const struct sockaddr *addr = __resolv_context_nameserver (ctx, i);
+        if (addr == NULL)
+          break;
+        size_t addrlen;
+        switch (addr->sa_family)
+          {
+          case AF_INET:
+            addrlen = sizeof (struct sockaddr_in);
+            break;
+          case AF_INET6:
+            addrlen = sizeof (struct sockaddr_in6);
+            break;
+          default:
+            FAIL_EXIT1 ("invalid address family %d", addr->sa_family);
+          }
+
+        char host[NI_MAXHOST];
+        char service[NI_MAXSERV];
+        int ret = getnameinfo (addr, addrlen,
+                               host, sizeof (host), service, sizeof (service),
+                               NI_NUMERICHOST | NI_NUMERICSERV);
+
+        if (ret != 0)
+          {
+            if (ret == EAI_SYSTEM)
+              fprintf (fp, "; error: getnameinfo: %m\n");
+            else
+              fprintf (fp, "; error: getnameinfo: %s\n", gai_strerror (ret));
+          }
+        else
+          fprintf (fp, "; nameserver[%zu]: [%s]:%s\n", i, host, service);
+        ++i;
+      }
+  }
+
   TEST_VERIFY (!ferror (fp));
+
+  __resolv_context_put (ctx);
 }
 
 /* Parameters of one test case.  */
@@ -245,6 +282,10 @@ struct test_case
   /* Setting for the RES_OPTIONS environment variable.  NULL if the
      variable is not to be set.  */
   const char *res_options;
+
+  /* Override the system host name.  NULL means that no change is made
+     and the default is used (test_hostname).  */
+  const char *hostname;
 };
 
 enum test_init
@@ -257,6 +298,15 @@ enum test_init
   test_init_method_last = test_getaddrinfo
 };
 
+static const char *const test_init_names[] =
+  {
+    [test_init] = "res_init",
+    [test_ninit] = "res_ninit",
+    [test_mkquery] = "res_mkquery",
+    [test_gethostbyname] = "gethostbyname",
+    [test_getaddrinfo] = "getaddrinfo",
+  };
+
 /* Closure argument for run_res_init.  */
 struct test_context
 {
@@ -270,7 +320,7 @@ setup_nss_dns_and_chroot (void)
   /* Load nss_dns outside of the chroot.  */
   if (dlopen (LIBNSS_DNS_SO, RTLD_LAZY) == NULL)
     FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s", dlerror ());
-  xchroot (path_chroot);
+  xchroot (chroot_env->path_chroot);
   /* Force the use of nss_dns.  */
   __nss_configure_lookup ("hosts", "dns");
 }
@@ -287,17 +337,29 @@ run_res_init (void *closure)
     setenv ("LOCALDOMAIN", ctx->t->localdomain, 1);
   if (ctx->t->res_options != NULL)
     setenv ("RES_OPTIONS", ctx->t->res_options, 1);
+  if (ctx->t->hostname != NULL)
+    {
+#ifdef CLONE_NEWUTS
+      /* This test needs its own namespace, to avoid changing the host
+         name for the parent, too.  */
+      TEST_VERIFY_EXIT (unshare (CLONE_NEWUTS) == 0);
+      if (sethostname (ctx->t->hostname, strlen (ctx->t->hostname)) != 0)
+        FAIL_EXIT1 ("sethostname (\"%s\"): %m", ctx->t->hostname);
+#else
+      FAIL_UNSUPPORTED ("clone (CLONE_NEWUTS) not supported");
+#endif
+    }
 
   switch (ctx->init)
     {
     case test_init:
-      xchroot (path_chroot);
+      xchroot (chroot_env->path_chroot);
       TEST_VERIFY (res_init () == 0);
       print_resp (stdout, &_res);
       return;
 
     case test_ninit:
-      xchroot (path_chroot);
+      xchroot (chroot_env->path_chroot);
       res_state resp = xmalloc (sizeof (*resp));
       memset (resp, 0, sizeof (*resp));
       TEST_VERIFY (res_ninit (resp) == 0);
@@ -307,7 +369,7 @@ run_res_init (void *closure)
       return;
 
     case test_mkquery:
-      xchroot (path_chroot);
+      xchroot (chroot_env->path_chroot);
       unsigned char buf[512];
       TEST_VERIFY (res_mkquery (QUERY, "www.example",
                                 C_IN, ns_t_a, NULL, 0,
@@ -359,50 +421,134 @@ struct test_case test_cases[] =
     {.name = "empty file",
      .conf = "",
      .expected = "search example.com\n"
+     "; search[0]: example.com\n"
      "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n"
+    },
+    {.name = "empty file, no-dot hostname",
+     .conf = "",
+     .expected = "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n",
+     .hostname = "example",
     },
     {.name = "empty file with LOCALDOMAIN",
      .conf = "",
      .expected = "search example.net\n"
-     "nameserver 127.0.0.1\n",
+     "; search[0]: example.net\n"
+     "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n",
      .localdomain = "example.net",
     },
     {.name = "empty file with RES_OPTIONS",
      .conf = "",
      .expected = "options attempts:5 edns0\n"
      "search example.com\n"
-     "nameserver 127.0.0.1\n",
+     "; search[0]: example.com\n"
+     "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n",
      .res_options = "edns0 attempts:5",
     },
     {.name = "empty file with RES_OPTIONS and LOCALDOMAIN",
      .conf = "",
      .expected = "options attempts:5 edns0\n"
      "search example.org\n"
-     "nameserver 127.0.0.1\n",
+     "; search[0]: example.org\n"
+     "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n",
      .localdomain = "example.org",
      .res_options = "edns0 attempts:5",
     },
     {.name = "basic",
-     .conf = "domain example.net\n"
-     "search corp.example.com example.com\n"
+     .conf =  "search corp.example.com example.com\n"
+     "nameserver 192.0.2.1\n",
+     .expected = "search corp.example.com example.com\n"
+     "; search[0]: corp.example.com\n"
+     "; search[1]: example.com\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
+    {.name = "basic with no-dot hostname",
+     .conf = "search corp.example.com example.com\n"
      "nameserver 192.0.2.1\n",
      .expected = "search corp.example.com example.com\n"
+     "; search[0]: corp.example.com\n"
+     "; search[1]: example.com\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n",
+     .hostname = "example",
+    },
+    {.name = "basic no-reload",
+     .conf = "options no-reload\n"
+     "search corp.example.com example.com\n"
+     "nameserver 192.0.2.1\n",
+     .expected = "options no-reload\n"
+     "search corp.example.com example.com\n"
+     "; search[0]: corp.example.com\n"
+     "; search[1]: example.com\n"
      "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
+    {.name = "basic no-reload via RES_OPTIONS",
+     .conf = "search corp.example.com example.com\n"
+     "nameserver 192.0.2.1\n",
+     .expected = "options no-reload\n"
+     "search corp.example.com example.com\n"
+     "; search[0]: corp.example.com\n"
+     "; search[1]: example.com\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n",
+     .res_options = "no-reload"
     },
     {.name = "whitespace",
      .conf = "# This test covers comment and whitespace processing "
      " (trailing whitespace,\n"
      "# missing newline at end of file).\n"
      "\n"
-     "domain  example.net\n"
      ";search commented out\n"
-     "search corp.example.com\texample.com\n"
+     "search corp.example.com\texample.com \n"
      "#nameserver 192.0.2.3\n"
      "nameserver 192.0.2.1 \n"
      "nameserver 192.0.2.2",    /* No \n at end of file.  */
      .expected = "search corp.example.com example.com\n"
+     "; search[0]: corp.example.com\n"
+     "; search[1]: example.com\n"
      "nameserver 192.0.2.1\n"
      "nameserver 192.0.2.2\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [192.0.2.2]:53\n"
+    },
+    {.name = "domain",
+     .conf = "domain example.net\n"
+     "nameserver 192.0.2.1\n",
+     .expected = "search example.net\n"
+     "; search[0]: example.net\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
+    {.name = "domain space",
+     .conf = "domain example.net \n"
+     "nameserver 192.0.2.1\n",
+     .expected = "search example.net\n"
+     "; search[0]: example.net\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
+    {.name = "domain tab",
+     .conf = "domain example.net\t\n"
+     "nameserver 192.0.2.1\n",
+     .expected = "search example.net\n"
+     "; search[0]: example.net\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
+    {.name = "domain override",
+     .conf = "search example.com example.org\n"
+     "nameserver 192.0.2.1\n"
+     "domain example.net",      /* No \n at end of file.  */
+     .expected = "search example.net\n"
+     "; search[0]: example.net\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     {.name = "option values, multiple servers",
      .conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
@@ -412,18 +558,25 @@ struct test_case test_cases[] =
      "nameserver 192.0.2.1\n"
      "nameserver ::1\n"
      "nameserver 192.0.2.2\n",
-     .expected = "options ndots:3 timeout:19 attempts:5 inet6 edns0\n"
+     .expected = "options ndots:3 timeout:19 attempts:5 edns0\n"
      "search corp.example.com example.com\n"
+     "; search[0]: corp.example.com\n"
+     "; search[1]: example.com\n"
      "nameserver 192.0.2.1\n"
      "nameserver ::1\n"
      "nameserver 192.0.2.2\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [::1]:53\n"
+     "; nameserver[2]: [192.0.2.2]:53\n"
     },
     {.name = "out-of-range option vales",
      .conf = "options use-vc timeout:999 attempts:999 ndots:99\n"
      "search example.com\n",
      .expected = "options ndots:15 timeout:30 attempts:5 use-vc\n"
      "search example.com\n"
+     "; search[0]: example.com\n"
      "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n"
     },
     {.name = "repeated directives",
      .conf = "options ndots:3 use-vc\n"
@@ -434,7 +587,9 @@ struct test_case test_cases[] =
      "search\n",
      .expected = "options ndots:2 use-vc edns0\n"
      "search example.org\n"
+     "; search[0]: example.org\n"
      "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n"
     },
     {.name = "many name servers, sortlist",
      .conf = "options single-request\n"
@@ -450,10 +605,22 @@ struct test_case test_cases[] =
      "nameserver 192.0.2.8\n",
      .expected = "options single-request\n"
      "search example.org example.com example.net corp.example.com\n"
+     "; search[0]: example.org\n"
+     "; search[1]: example.com\n"
+     "; search[2]: example.net\n"
+     "; search[3]: corp.example.com\n"
      "sortlist 192.0.2.0/255.255.255.0\n"
      "nameserver 192.0.2.1\n"
      "nameserver 192.0.2.2\n"
      "nameserver 192.0.2.3\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [192.0.2.2]:53\n"
+     "; nameserver[2]: [192.0.2.3]:53\n"
+     "; nameserver[3]: [192.0.2.4]:53\n"
+     "; nameserver[4]: [192.0.2.5]:53\n"
+     "; nameserver[5]: [192.0.2.6]:53\n"
+     "; nameserver[6]: [192.0.2.7]:53\n"
+     "; nameserver[7]: [192.0.2.8]:53\n"
     },
     {.name = "IPv4 and IPv6 nameservers",
      .conf = "options single-request\n"
@@ -471,27 +638,79 @@ struct test_case test_cases[] =
      .expected = "options single-request\n"
      "search example.org example.com example.net corp.example.com"
      " legacy.example.com\n"
+     "; search[0]: example.org\n"
+     "; search[1]: example.com\n"
+     "; search[2]: example.net\n"
+     "; search[3]: corp.example.com\n"
+     "; search[4]: legacy.example.com\n"
      "sortlist 192.0.2.0/255.255.255.0\n"
      "nameserver 192.0.2.1\n"
      "nameserver 2001:db8::2\n"
      "nameserver 192.0.2.3\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [2001:db8::2]:53\n"
+     "; nameserver[2]: [192.0.2.3]:53\n"
+     "; nameserver[3]: [2001:db8::4]:53\n"
+     "; nameserver[4]: [192.0.2.5]:53\n"
+     "; nameserver[5]: [2001:db8::6]:53\n"
+     "; nameserver[6]: [192.0.2.7]:53\n"
+     "; nameserver[7]: [2001:db8::8]:53\n",
     },
     {.name = "garbage after nameserver",
      .conf = "nameserver 192.0.2.1 garbage\n"
      "nameserver 192.0.2.2:5353\n"
      "nameserver 192.0.2.3 5353\n",
      .expected = "search example.com\n"
+     "; search[0]: example.com\n"
      "nameserver 192.0.2.1\n"
      "nameserver 192.0.2.3\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [192.0.2.3]:53\n"
     },
     {.name = "RES_OPTIONS is cummulative",
      .conf = "options timeout:7 ndots:2 use-vc\n"
      "nameserver 192.0.2.1\n",
      .expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n"
      "search example.com\n"
-     "nameserver 192.0.2.1\n",
+     "; search[0]: example.com\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n",
      .res_options = "attempts:5 ndots:3 edns0 ",
     },
+    {.name = "many search list entries (bug 19569)",
+     .conf = "nameserver 192.0.2.1\n"
+     "search corp.example.com support.example.com"
+     " community.example.org wan.example.net vpn.example.net"
+     " example.com example.org example.net\n",
+     .expected = "search corp.example.com support.example.com"
+     " community.example.org wan.example.net vpn.example.net example.com\n"
+     "; search[0]: corp.example.com\n"
+     "; search[1]: support.example.com\n"
+     "; search[2]: community.example.org\n"
+     "; search[3]: wan.example.net\n"
+     "; search[4]: vpn.example.net\n"
+     "; search[5]: example.com\n"
+     "; search[6]: example.org\n"
+     "; search[7]: example.net\n"
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
+    {.name = "very long search list entries (bug 21475)",
+     .conf = "nameserver 192.0.2.1\n"
+     "search example.com "
+#define H63 "this-host-name-is-longer-than-yours-yes-I-really-really-mean-it"
+#define D63 "this-domain-name-is-as-long-as-the-previous-name--63-characters"
+     " " H63 "." D63 ".example.org"
+     " " H63 "." D63 ".example.net\n",
+     .expected = "search example.com " H63 "." D63 ".example.org\n"
+     "; search[0]: example.com\n"
+     "; search[1]: " H63 "." D63 ".example.org\n"
+     "; search[2]: " H63 "." D63 ".example.net\n"
+#undef H63
+#undef D63
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+    },
     { NULL }
   };
 
@@ -507,7 +726,8 @@ test_file_contents (const struct test_case *t)
          ++init_method)
       {
         if (test_verbose > 0)
-          printf ("info:  testing init method %d\n", init_method);
+          printf ("info:  testing init method %s\n",
+                  test_init_names[init_method]);
         struct test_context ctx = { .init = init_method, .t = t };
         void (*func) (void *) = run_res_init;
 #if TEST_THREAD
@@ -519,7 +739,8 @@ test_file_contents (const struct test_case *t)
         if (strcmp (proc.out.buffer, t->expected) != 0)
           {
             support_record_failure ();
-            printf ("error: output mismatch for %s\n", t->name);
+            printf ("error: output mismatch for %s (init method %s)\n",
+                    t->name, test_init_names[init_method]);
             support_run_diff ("expected", t->expected,
                               "actual", proc.out.buffer);
           }
@@ -529,6 +750,197 @@ test_file_contents (const struct test_case *t)
       }
 }
 
+/* Special tests which do not follow the general pattern.  */
+enum { special_tests_count = 11 };
+
+/* Implementation of special tests.  */
+static void
+special_test_callback (void *closure)
+{
+  unsigned int *test_indexp = closure;
+  unsigned test_index = *test_indexp;
+  TEST_VERIFY (test_index < special_tests_count);
+  if (test_verbose > 0)
+    printf ("info: special test %u\n", test_index);
+  xchroot (chroot_env->path_chroot);
+
+  switch (test_index)
+    {
+    case 0:
+    case 1:
+      /* Second res_init with missing or empty file preserves
+         flags.  */
+      if (test_index == 1)
+        TEST_VERIFY (unlink (_PATH_RESCONF) == 0);
+      _res.options = RES_USE_EDNS0;
+      TEST_VERIFY (res_init () == 0);
+      /* First res_init clears flag.  */
+      TEST_VERIFY (!(_res.options & RES_USE_EDNS0));
+      _res.options |= RES_USE_EDNS0;
+      TEST_VERIFY (res_init () == 0);
+      /* Second res_init preserves flag.  */
+      TEST_VERIFY (_res.options & RES_USE_EDNS0);
+      if (test_index == 1)
+        /* Restore empty file.  */
+        support_write_file_string (_PATH_RESCONF, "");
+      break;
+
+    case 2:
+      /* Second res_init is cumulative.  */
+      support_write_file_string (_PATH_RESCONF,
+                                 "options rotate\n"
+                                 "nameserver 192.0.2.1\n");
+      _res.options = RES_USE_EDNS0;
+      TEST_VERIFY (res_init () == 0);
+      /* First res_init clears flag.  */
+      TEST_VERIFY (!(_res.options & RES_USE_EDNS0));
+      /* And sets RES_ROTATE.  */
+      TEST_VERIFY (_res.options & RES_ROTATE);
+      _res.options |= RES_USE_EDNS0;
+      TEST_VERIFY (res_init () == 0);
+      /* Second res_init preserves flag.  */
+      TEST_VERIFY (_res.options & RES_USE_EDNS0);
+      TEST_VERIFY (_res.options & RES_ROTATE);
+      /* Reloading the configuration does not clear the explicitly set
+         flag.  */
+      support_write_file_string (_PATH_RESCONF,
+                                 "nameserver 192.0.2.1\n"
+                                 "nameserver 192.0.2.2\n");
+      TEST_VERIFY (res_init () == 0);
+      TEST_VERIFY (_res.nscount == 2);
+      TEST_VERIFY (_res.options & RES_USE_EDNS0);
+      /* Whether RES_ROTATE (originally in resolv.conf, now removed)
+         should be preserved is subject to debate.  See bug 21701.  */
+      /* TEST_VERIFY (!(_res.options & RES_ROTATE)); */
+      break;
+
+    case 3:
+    case 4:
+    case 5:
+    case 6:
+      support_write_file_string (_PATH_RESCONF,
+                                 "options edns0\n"
+                                 "nameserver 192.0.2.1\n");
+      goto reload_tests;
+    case 7: /* 7 and the following tests are with no-reload.  */
+    case 8:
+    case 9:
+    case 10:
+        support_write_file_string (_PATH_RESCONF,
+                                   "options edns0 no-reload\n"
+                                   "nameserver 192.0.2.1\n");
+        /* Fall through.  */
+    reload_tests:
+      for (int iteration = 0; iteration < 2; ++iteration)
+        {
+          switch (test_index)
+            {
+            case 3:
+            case 7:
+              TEST_VERIFY (res_init () == 0);
+              break;
+            case 4:
+            case 8:
+              {
+                unsigned char buf[512];
+                TEST_VERIFY
+                  (res_mkquery (QUERY, test_hostname, C_IN, T_A,
+                                NULL, 0, NULL, buf, sizeof (buf)) > 0);
+              }
+              break;
+            case 5:
+            case 9:
+              gethostbyname (test_hostname);
+              break;
+            case 6:
+            case 10:
+              {
+                struct addrinfo *ai;
+                (void) getaddrinfo (test_hostname, NULL, NULL, &ai);
+              }
+              break;
+            }
+          /* test_index == 7 is res_init and performs a reload even
+             with no-reload.  */
+          if (iteration == 0 || test_index > 7)
+            {
+              TEST_VERIFY (_res.options & RES_USE_EDNS0);
+              TEST_VERIFY (!(_res.options & RES_ROTATE));
+              if (test_index < 7)
+                TEST_VERIFY (!(_res.options & RES_NORELOAD));
+              else
+                TEST_VERIFY (_res.options & RES_NORELOAD);
+              TEST_VERIFY (_res.nscount == 1);
+              /* File change triggers automatic reloading.  */
+              support_write_file_string (_PATH_RESCONF,
+                                         "options rotate\n"
+                                         "nameserver 192.0.2.1\n"
+                                         "nameserver 192.0.2.2\n");
+            }
+          else
+            {
+              if (test_index != 3 && test_index != 7)
+                /* test_index 3, 7 are res_init; this function does
+                   not reset flags.  See bug 21701.  */
+                TEST_VERIFY (!(_res.options & RES_USE_EDNS0));
+              TEST_VERIFY (_res.options & RES_ROTATE);
+              TEST_VERIFY (_res.nscount == 2);
+            }
+        }
+      break;
+    }
+}
+
+#if TEST_THREAD
+/* Helper function which calls special_test_callback from a
+   thread.  */
+static void *
+special_test_thread_func (void *closure)
+{
+  special_test_callback (closure);
+  return NULL;
+}
+
+/* Variant of special_test_callback which runs the function on a
+   non-main thread.  */
+static void
+run_special_test_on_thread (void *closure)
+{
+  xpthread_join (xpthread_create (NULL, special_test_thread_func, closure));
+}
+#endif /* TEST_THREAD */
+
+/* Perform the requested special test in a subprocess using
+   special_test_callback.  */
+static void
+special_test (unsigned int test_index)
+{
+#if TEST_THREAD
+  for (int do_thread = 0; do_thread < 2; ++do_thread)
+#endif
+    {
+      void (*func) (void *) = special_test_callback;
+#if TEST_THREAD
+      if (do_thread)
+        func = run_special_test_on_thread;
+#endif
+      struct support_capture_subprocess proc
+        = support_capture_subprocess (func, &test_index);
+      char *test_name = xasprintf ("special test %u", test_index);
+      if (strcmp (proc.out.buffer, "") != 0)
+        {
+          support_record_failure ();
+          printf ("error: output mismatch for %s\n", test_name);
+          support_run_diff ("expected", "",
+                            "actual", proc.out.buffer);
+        }
+      support_capture_subprocess_check (&proc, test_name, 0, sc_allow_stdout);
+      free (test_name);
+      support_capture_subprocess_free (&proc);
+    }
+}
+
+
 /* Dummy DNS server.  It ensures that the probe queries sent by
    gethostbyname and getaddrinfo receive a reply even if the system
    applies a very strict rate limit to localhost.  */
@@ -630,7 +1042,8 @@ do_test (void)
       TEST_VERIFY (test_cases[i].conf != NULL);
       TEST_VERIFY (test_cases[i].expected != NULL);
 
-      support_write_file_string (path_resolv_conf, test_cases[i].conf);
+      support_write_file_string (chroot_env->path_resolv_conf,
+                                 test_cases[i].conf);
 
       test_file_contents (&test_cases[i]);
 
@@ -640,27 +1053,32 @@ do_test (void)
         {
           if (test_verbose > 0)
             printf ("info:  special test: missing file\n");
-          TEST_VERIFY (unlink (path_resolv_conf) == 0);
+          TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0);
           test_file_contents (&test_cases[i]);
 
           if (test_verbose > 0)
             printf ("info:  special test: dangling symbolic link\n");
-          TEST_VERIFY (symlink ("does-not-exist", path_resolv_conf) == 0);
+          TEST_VERIFY (symlink ("does-not-exist", chroot_env->path_resolv_conf) == 0);
           test_file_contents (&test_cases[i]);
-          TEST_VERIFY (unlink (path_resolv_conf) == 0);
+          TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0);
 
           if (test_verbose > 0)
             printf ("info:  special test: unreadable file\n");
-          support_write_file_string (path_resolv_conf, "");
-          TEST_VERIFY (chmod (path_resolv_conf, 0) == 0);
+          support_write_file_string (chroot_env->path_resolv_conf, "");
+          TEST_VERIFY (chmod (chroot_env->path_resolv_conf, 0) == 0);
           test_file_contents (&test_cases[i]);
 
           /* Restore the empty file.  */
-          TEST_VERIFY (unlink (path_resolv_conf) == 0);
-          support_write_file_string (path_resolv_conf, "");
+          TEST_VERIFY (unlink (chroot_env->path_resolv_conf) == 0);
+          support_write_file_string (chroot_env->path_resolv_conf, "");
         }
     }
 
+  /* The tests which do not follow a regular pattern.  */
+  for (unsigned int test_index = 0;
+       test_index < special_tests_count; ++test_index)
+    special_test (test_index);
+
   if (server > 0)
     {
       if (kill (server, SIGTERM) < 0)
@@ -668,10 +1086,7 @@ do_test (void)
       xwaitpid (server, NULL, 0);
     }
 
-  free (path_chroot);
-  path_chroot = NULL;
-  free (path_resolv_conf);
-  path_resolv_conf = NULL;
+  support_chroot_free (chroot_env);
   return 0;
 }