summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-06-02 15:50:36 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-06-02 15:50:36 +0200
commit2714c5f3c95f90977167c1d21326d907fb76b419 (patch)
tree5c3fc58f8177144973b0714aed382f8b32dadd84
parent673cb072a4710bd4bf6029a062d2867cca929c43 (diff)
downloadglibc-2714c5f3c95f90977167c1d21326d907fb76b419.tar.gz
glibc-2714c5f3c95f90977167c1d21326d907fb76b419.tar.xz
glibc-2714c5f3c95f90977167c1d21326d907fb76b419.zip
resolv: Tests for various versions of res_init
-rw-r--r--ChangeLog32
-rw-r--r--resolv/Makefile8
-rw-r--r--resolv/tst-resolv-res_init-skeleton.c601
-rw-r--r--resolv/tst-resolv-res_init-thread.c20
-rw-r--r--resolv/tst-resolv-res_init.c20
-rw-r--r--support/Makefile9
-rw-r--r--support/capture_subprocess.h19
-rw-r--r--support/namespace.h12
-rw-r--r--support/support-xstat.c30
-rw-r--r--support/support.h15
-rw-r--r--support/support_can_chroot.c65
-rw-r--r--support/support_capture_subprocess_check.c67
-rw-r--r--support/support_isolate_in_subprocess.c38
-rw-r--r--support/support_shared_allocate.c57
-rw-r--r--support/support_write_file_string.c39
-rw-r--r--support/xchroot.c28
-rw-r--r--support/xmkdir.c28
-rw-r--r--support/xopen.c30
-rw-r--r--support/xunistd.h9
19 files changed, 1126 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index 579258f02a..6b32e3192c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,37 @@
 2017-06-02  Florian Weimer  <fweimer@redhat.com>
 
+	Test res_init with several configuration files.
+	* resolv/Makefile [build-shared] (tests-internal): Add
+	tst-resolv-res_init, tst-resolv-res_init-thread.
+	(tst-resolv-res_init): Link against libdl, libresolv.
+	(tst-resolv-res_init-thread): Link against libdl, libresolv,
+	libpthread.
+	* resolv/tst-resolv-res_init.c: New file.
+	* resolv/tst-resolv-res_init-skeleton.c: Likewise.
+	* resolv/tst-resolv-res_init-thread.c: Likewise.
+	* support/Makefile (libsupport-routines): Add support-xstat,
+	support_can_chroot, support_capture_subprocess_check,
+	support_isolate_in_subprocess, support_shared_allocate,
+	support_write_file_string, xchroot, xmkdir, xopen.
+	* support/capture_subprocess.h (enum support_capture_allow): Define.
+	(support_capture_subprocess_check): Declare.
+	* support/namespace.h (support_can_chroot)
+	(support_isolate_in_subprocess): Declare.
+	* support/support-xstat.c: New file.
+	* support/support.h (support_shared_allocate, support_shared_free)
+	(support_write_file_string): Declare.
+	* support/support_can_chroot.c: New file.
+	* support/support_capture_subprocess_check.c: Likewise.
+	* support/support_isolate_in_subprocess.c: Likewise.
+	* support/support_shared_allocate.c: Likewise.
+	* support/support_write_file_string.c: Likewise.
+	* support/xchroot.c: Likwise.
+	* support/xmkdir.c: Likwise.
+	* support/xopen.c: Likwise.
+	* support/xunistd.h (xopen, xstat, xmkdir, xchroot): Declare.
+
+2017-06-02  Florian Weimer  <fweimer@redhat.com>
+
 	* sysdeps/posix/getaddrinfo.c (getcanonname): New function.
 	(gaih_inet): Remove malloc_canonbuf variable.  Call getcanonname.
 
diff --git a/resolv/Makefile b/resolv/Makefile
index 70b6f4ffe8..dc597ca097 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -57,6 +57,11 @@ ifeq (yes,$(build-shared))
 tests += \
   tst-resolv-canonname \
 
+# uses DEPRECATED_RES_USE_INET6 from <resolv-internal.h>.
+tests-internal += \
+  tst-resolv-res_init \
+  tst-resolv-res_init-thread \
+
 endif
 
 # This test sends millions of packets and is rather slow.
@@ -136,6 +141,9 @@ $(objpfx)tst-res_use_inet6: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-res_init: $(libdl) $(objpfx)libresolv.so
+$(objpfx)tst-resolv-res_init-thread: $(libdl) $(objpfx)libresolv.so \
+  $(shared-thread-library)
 $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-canonname: \
diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
new file mode 100644
index 0000000000..1d2c475c4b
--- /dev/null
+++ b/resolv/tst-resolv-res_init-skeleton.c
@@ -0,0 +1,601 @@
+/* Test parsing of /etc/resolv.conf.  Genric version.
+   Copyright (C) 2017 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/>.  */
+
+/* Before including this file, TEST_THREAD has to be defined to 0 or
+   1, depending on whether the threading tests should be compiled
+   in.  */
+
+#include <arpa/inet.h>
+#include <gnu/lib-names.h>
+#include <netdb.h>
+#include <resolv/resolv-internal.h> /* For DEPRECATED_RES_USE_INET6.  */
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/run_diff.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/test-driver.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+#if TEST_THREAD
+# include <support/xthread.h>
+#endif
+
+/* This is the host name used to ensure predictable behavior of
+   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;
+
+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);
+  }
+}
+
+/* Verify that the chroot environment has been set up.  */
+static void
+check_chroot_working (void *closure)
+{
+  xchroot (path_chroot);
+  FILE *fp = xfopen (_PATH_RESCONF, "r");
+  xfclose (fp);
+
+  TEST_VERIFY_EXIT (res_init () == 0);
+  TEST_VERIFY (_res.options & RES_INIT);
+
+  char buf[100];
+  if (gethostname (buf, sizeof (buf)) < 0)
+    FAIL_EXIT1 ("gethostname: %m");
+  if (strcmp (buf, test_hostname) != 0)
+    FAIL_EXIT1 ("unexpected host name: %s", buf);
+}
+
+/* If FLAG is set in *OPTIONS, write NAME to FP, and clear it in
+   *OPTIONS.  */
+static void
+print_option_flag (FILE *fp, int *options, int flag, const char *name)
+{
+  if (*options & flag)
+    {
+      fprintf (fp, " %s", name);
+      *options &= ~flag;
+    }
+}
+
+/* Write a decoded version of the resolver configuration *RESP to the
+   stream FP.  */
+static void
+print_resp (FILE *fp, res_state resp)
+{
+  /* The options directive.  */
+  {
+    /* RES_INIT is used internally for tracking initialization.  */
+    TEST_VERIFY (resp->options & RES_INIT);
+    /* Also mask out other default flags which cannot be set through
+       the options directive.  */
+    int options
+      = resp->options & ~(RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH);
+    if (options != 0
+        || resp->ndots != 1
+        || resp->retrans != RES_TIMEOUT
+        || resp->retry != RES_DFLRETRY)
+      {
+        fputs ("options", fp);
+        if (resp->ndots != 1)
+          fprintf (fp, " ndots:%d", resp->ndots);
+        if (resp->retrans != RES_TIMEOUT)
+          fprintf (fp, " timeout:%d", resp->retrans);
+        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,
+                           "single-request");
+        print_option_flag (fp, &options, RES_SNGLKUPREOP,
+                           "single-request-reopen");
+        print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
+        fputc ('\n', fp);
+        if (options != 0)
+          fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
+      }
+  }
+
+  /* The search and domain directives.  */
+  if (resp->dnsrch[0] != NULL)
+    {
+      fputs ("search", fp);
+      for (int i = 0; i < MAXDNSRCH && resp->dnsrch[i] != NULL; ++i)
+        {
+          fputc (' ', fp);
+          fputs (resp->dnsrch[i], fp);
+        }
+      fputc ('\n', fp);
+    }
+  else if (resp->defdname[0] != '\0')
+    fprintf (fp, "domain %s\n", resp->defdname);
+
+  /* The sortlist directive.  */
+  if (resp->nsort > 0)
+    {
+      fputs ("sortlist", fp);
+      for (int i = 0; i < resp->nsort && i < MAXRESOLVSORT; ++i)
+        {
+          char net[20];
+          if (inet_ntop (AF_INET, &resp->sort_list[i].addr,
+                         net, sizeof (net)) == NULL)
+            FAIL_EXIT1 ("inet_ntop: %m\n");
+          char mask[20];
+          if (inet_ntop (AF_INET, &resp->sort_list[i].mask,
+                         mask, sizeof (mask)) == NULL)
+            FAIL_EXIT1 ("inet_ntop: %m\n");
+          fprintf (fp, " %s/%s", net, mask);
+        }
+      fputc ('\n', fp);
+    }
+
+  /* The nameserver directives.  */
+  for (size_t i = 0; i < resp->nscount; ++i)
+    {
+      char host[NI_MAXHOST];
+      char service[NI_MAXSERV];
+
+      /* See get_nsaddr in res_send.c.  */
+      void *addr;
+      size_t addrlen;
+      if (resp->nsaddr_list[i].sin_family == 0
+          && resp->_u._ext.nsaddrs[i] != NULL)
+        {
+          addr = resp->_u._ext.nsaddrs[i];
+          addrlen = sizeof (*resp->_u._ext.nsaddrs[i]);
+        }
+      else
+        {
+          addr = &resp->nsaddr_list[i];
+          addrlen = sizeof (resp->nsaddr_list[i]);
+        }
+
+      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 %s\n", host);
+          if (strcmp (service, "53") != 0)
+            fprintf (fp, "; unrepresentable port number %s\n\n", service);
+        }
+    }
+
+  TEST_VERIFY (!ferror (fp));
+}
+
+/* Parameters of one test case.  */
+struct test_case
+{
+  /* A short, descriptive name of the test.  */
+  const char *name;
+
+  /* The contents of the /etc/resolv.conf file.  */
+  const char *conf;
+
+  /* The expected output from print_resp.  */
+  const char *expected;
+
+  /* Setting for the LOCALDOMAIN environment variable.  NULL if the
+     variable is not to be set.  */
+  const char *localdomain;
+
+  /* Setting for the RES_OPTIONS environment variable.  NULL if the
+     variable is not to be set.  */
+  const char *res_options;
+};
+
+enum test_init
+{
+  test_init,
+  test_ninit,
+  test_mkquery,
+  test_gethostbyname,
+  test_getaddrinfo,
+  test_init_method_last = test_getaddrinfo
+};
+
+/* Closure argument for run_res_init.  */
+struct test_context
+{
+  enum test_init init;
+  const struct test_case *t;
+};
+
+static void
+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);
+  /* Force the use of nss_dns.  */
+  __nss_configure_lookup ("hosts", "dns");
+}
+
+/* Run res_ninit or res_init in a subprocess and dump the parsed
+   resolver state to standard output.  */
+static void
+run_res_init (void *closure)
+{
+  struct test_context *ctx = closure;
+  TEST_VERIFY (getenv ("LOCALDOMAIN") == NULL);
+  TEST_VERIFY (getenv ("RES_OPTIONS") == NULL);
+  if (ctx->t->localdomain != NULL)
+    setenv ("LOCALDOMAIN", ctx->t->localdomain, 1);
+  if (ctx->t->res_options != NULL)
+    setenv ("RES_OPTIONS", ctx->t->res_options, 1);
+
+  switch (ctx->init)
+    {
+    case test_init:
+      xchroot (path_chroot);
+      TEST_VERIFY (res_init () == 0);
+      print_resp (stdout, &_res);
+      return;
+
+    case test_ninit:
+      xchroot (path_chroot);
+      res_state resp = xmalloc (sizeof (*resp));
+      memset (resp, 0, sizeof (*resp));
+      TEST_VERIFY (res_ninit (resp) == 0);
+      print_resp (stdout, resp);
+      res_nclose (resp);
+      free (resp);
+      return;
+
+    case test_mkquery:
+      xchroot (path_chroot);
+      unsigned char buf[512];
+      TEST_VERIFY (res_mkquery (QUERY, "www.example",
+                                C_IN, ns_t_a, NULL, 0,
+                                NULL, buf, sizeof (buf)) > 0);
+      print_resp (stdout, &_res);
+      return;
+
+    case test_gethostbyname:
+      setup_nss_dns_and_chroot ();
+      /* Trigger implicit initialization of the _res structure.  The
+         actual lookup result is immaterial.  */
+      (void )gethostbyname ("www.example");
+      print_resp (stdout, &_res);
+      return;
+
+    case test_getaddrinfo:
+      setup_nss_dns_and_chroot ();
+      /* Trigger implicit initialization of the _res structure.  The
+         actual lookup result is immaterial.  */
+      struct addrinfo *ai;
+      (void) getaddrinfo ("www.example", NULL, NULL, &ai);
+      print_resp (stdout, &_res);
+      return;
+    }
+
+  FAIL_EXIT1 ("invalid init method %d", ctx->init);
+}
+
+#if TEST_THREAD
+/* Helper function which calls run_res_init from a thread.  */
+static void *
+run_res_init_thread_func (void *closure)
+{
+  run_res_init (closure);
+  return NULL;
+}
+
+/* Variant of res_run_init which runs the function on a non-main
+   thread.  */
+static void
+run_res_init_on_thread (void *closure)
+{
+  xpthread_join (xpthread_create (NULL, run_res_init_thread_func, closure));
+}
+#endif /* TEST_THREAD */
+
+struct test_case test_cases[] =
+  {
+    {.name = "empty file",
+     .conf = "",
+     .expected = "search example.com\n"
+     "nameserver 127.0.0.1\n"
+    },
+    {.name = "empty file with LOCALDOMAIN",
+     .conf = "",
+     .expected = "search example.net\n"
+     "nameserver 127.0.0.1\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",
+     .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",
+     .localdomain = "example.org",
+     .res_options = "edns0 attempts:5",
+    },
+    {.name = "basic",
+     .conf = "domain example.net\n"
+     "search corp.example.com example.com\n"
+     "nameserver 192.0.2.1\n",
+     .expected = "search corp.example.com example.com\n"
+     "nameserver 192.0.2.1\n"
+    },
+    {.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"
+     "#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"
+     "nameserver 192.0.2.1\n"
+     "nameserver 192.0.2.2\n"
+    },
+    {.name = "option values, multiple servers",
+     .conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
+     "domain  example.net\n"
+     ";domain comment\n"
+     "search corp.example.com\texample.com\n"
+     "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"
+     "search corp.example.com example.com\n"
+     "nameserver 192.0.2.1\n"
+     "nameserver ::1\n"
+     "nameserver 192.0.2.2\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"
+     "nameserver 127.0.0.1\n"
+    },
+    {.name = "repeated directives",
+     .conf = "options ndots:3 use-vc\n"
+     "options edns0 ndots:2\n"
+     "domain corp.example\n"
+     "search example.net corp.example.com example.com\n"
+     "search example.org\n"
+     "search\n",
+     .expected = "options ndots:2 use-vc edns0\n"
+     "search example.org\n"
+     "nameserver 127.0.0.1\n"
+    },
+    {.name = "many name servers, sortlist",
+     .conf = "options single-request\n"
+     "search example.org example.com example.net 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 192.0.2.4\n"
+     "nameserver 192.0.2.5\n"
+     "nameserver 192.0.2.6\n"
+     "nameserver 192.0.2.7\n"
+     "nameserver 192.0.2.8\n",
+     .expected = "options single-request\n"
+     "search example.org example.com example.net 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"
+    },
+    {.name = "IPv4 and IPv6 nameservers",
+     .conf = "options single-request\n"
+     "search example.org example.com example.net corp.example.com"
+     " legacy.example.com\n"
+     "sortlist 192.0.2.0\n"
+     "nameserver 192.0.2.1\n"
+     "nameserver 2001:db8::2\n"
+     "nameserver 192.0.2.3\n"
+     "nameserver 2001:db8::4\n"
+     "nameserver 192.0.2.5\n"
+     "nameserver 2001:db8::6\n"
+     "nameserver 192.0.2.7\n"
+     "nameserver 2001:db8::8\n",
+     .expected = "options single-request\n"
+     "search example.org example.com example.net corp.example.com"
+     " 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"
+    },
+    {.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"
+     "nameserver 192.0.2.1\n"
+     "nameserver 192.0.2.3\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",
+     .res_options = "attempts:5 ndots:3 edns0 ",
+    },
+    { NULL }
+  };
+
+/* Run the indicated test case.  This function assumes that the chroot
+   contents has already been set up.  */
+static void
+test_file_contents (const struct test_case *t)
+{
+#if TEST_THREAD
+  for (int do_thread = 0; do_thread < 2; ++do_thread)
+#endif
+    for (int init_method = 0; init_method <= test_init_method_last;
+         ++init_method)
+      {
+        if (test_verbose > 0)
+          printf ("info:  testing init method %d\n", init_method);
+        struct test_context ctx = { .init = init_method, .t = t };
+        void (*func) (void *) = run_res_init;
+#if TEST_THREAD
+        if (do_thread)
+          func = run_res_init_on_thread;
+#endif
+        struct support_capture_subprocess proc
+          = support_capture_subprocess (func, &ctx);
+        if (strcmp (proc.out.buffer, t->expected) != 0)
+          {
+            support_record_failure ();
+            printf ("error: output mismatch for %s\n", t->name);
+            support_run_diff ("expected", t->expected,
+                              "actual", proc.out.buffer);
+          }
+        support_capture_subprocess_check (&proc, t->name, 0,
+                                          sc_allow_stdout);
+        support_capture_subprocess_free (&proc);
+      }
+}
+
+static int
+do_test (void)
+{
+  support_become_root ();
+  support_enter_network_namespace ();
+  if (!support_in_uts_namespace () || !support_can_chroot ())
+    return EXIT_UNSUPPORTED;
+
+  /* We are in an UTS namespace, so we can set the host name without
+     altering the state of the entire system.  */
+  if (sethostname (test_hostname, strlen (test_hostname)) != 0)
+    FAIL_EXIT1 ("sethostname: %m");
+
+  /* These environment variables affect resolv.conf parsing.  */
+  unsetenv ("LOCALDOMAIN");
+  unsetenv ("RES_OPTIONS");
+
+  /* Ensure that the chroot setup worked.  */
+  {
+    struct support_capture_subprocess proc
+      = support_capture_subprocess (check_chroot_working, NULL);
+    support_capture_subprocess_check (&proc, "chroot", 0, sc_allow_none);
+    support_capture_subprocess_free (&proc);
+  }
+
+  for (size_t i = 0; test_cases[i].name != NULL; ++i)
+    {
+      if (test_verbose > 0)
+        printf ("info: running test: %s\n", test_cases[i].name);
+      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);
+
+      test_file_contents (&test_cases[i]);
+
+      /* The expected output from the empty file test is used for
+         further tests.  */
+      if (test_cases[i].conf[0] == '\0')
+        {
+          if (test_verbose > 0)
+            printf ("info:  special test: missing file\n");
+          TEST_VERIFY (unlink (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_file_contents (&test_cases[i]);
+          TEST_VERIFY (unlink (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);
+          test_file_contents (&test_cases[i]);
+
+          /* Restore the empty file.  */
+          TEST_VERIFY (unlink (path_resolv_conf) == 0);
+          support_write_file_string (path_resolv_conf, "");
+        }
+    }
+
+  free (path_chroot);
+  path_chroot = NULL;
+  free (path_resolv_conf);
+  path_resolv_conf = NULL;
+  return 0;
+}
+
+#define PREPARE prepare
+#include <support/test-driver.c>
diff --git a/resolv/tst-resolv-res_init-thread.c b/resolv/tst-resolv-res_init-thread.c
new file mode 100644
index 0000000000..f47ac34f7f
--- /dev/null
+++ b/resolv/tst-resolv-res_init-thread.c
@@ -0,0 +1,20 @@
+/* Test parsing of /etc/resolv.conf, threading version.
+   Copyright (C) 2017 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/>.  */
+
+#define TEST_THREAD 1
+#include "tst-resolv-res_init-skeleton.c"
diff --git a/resolv/tst-resolv-res_init.c b/resolv/tst-resolv-res_init.c
new file mode 100644
index 0000000000..40c0154eca
--- /dev/null
+++ b/resolv/tst-resolv-res_init.c
@@ -0,0 +1,20 @@
+/* Test parsing of /etc/resolv.conf, non-threading version.
+   Copyright (C) 2017 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/>.  */
+
+#define TEST_THREAD 0
+#include "tst-resolv-res_init-skeleton.c"
diff --git a/support/Makefile b/support/Makefile
index 3ce73a6c76..20b0343ade 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -35,8 +35,11 @@ libsupport-routines = \
   oom_error \
   resolv_test \
   set_fortify_handler \
+  support-xstat \
   support_become_root \
+  support_can_chroot \
   support_capture_subprocess \
+  support_capture_subprocess_check \
   support_enter_network_namespace \
   support_format_address_family \
   support_format_addrinfo \
@@ -44,8 +47,11 @@ libsupport-routines = \
   support_format_herrno \
   support_format_hostent \
   support_format_netent \
+  support_isolate_in_subprocess \
   support_record_failure \
   support_run_diff \
+  support_shared_allocate \
+  support_write_file_string \
   support_test_main \
   support_test_verify_impl \
   temp_file \
@@ -55,6 +61,7 @@ libsupport-routines = \
   xasprintf \
   xbind \
   xcalloc \
+  xchroot \
   xclose \
   xconnect \
   xdup2 \
@@ -65,8 +72,10 @@ libsupport-routines = \
   xlisten \
   xmalloc \
   xmemstream \
+  xmkdir \
   xmmap \
   xmunmap \
+  xopen \
   xpipe \
   xpoll \
   xpthread_attr_destroy \
diff --git a/support/capture_subprocess.h b/support/capture_subprocess.h
index be5d34fbe2..43caf9bce4 100644
--- a/support/capture_subprocess.h
+++ b/support/capture_subprocess.h
@@ -39,4 +39,23 @@ struct support_capture_subprocess support_capture_subprocess
    support_capture_subprocess.  */
 void support_capture_subprocess_free (struct support_capture_subprocess *);
 
+enum support_capture_allow
+{
+  /* No output is allowed.  */
+  sc_allow_none = 0x01,
+  /* Output to stdout is permitted.  */
+  sc_allow_stdout = 0x02,
+  /* Output to standard error is permitted.  */
+  sc_allow_stderr = 0x04,
+};
+
+/* Check that the subprocess exited with STATUS and that only the
+   allowed outputs happened.  ALLOWED is a combination of
+   support_capture_allow flags.  Report errors under the CONTEXT
+   message.  */
+void support_capture_subprocess_check (struct support_capture_subprocess *,
+                                       const char *context, int status,
+                                       int allowed)
+  __attribute__ ((nonnull (1, 2)));
+
 #endif /* SUPPORT_CAPTURE_SUBPROCESS_H */
diff --git a/support/namespace.h b/support/namespace.h
index 6bc82d619b..e1ccaa1ef0 100644
--- a/support/namespace.h
+++ b/support/namespace.h
@@ -35,6 +35,13 @@ __BEGIN_DECLS
    single-threaded processes.  */
 bool support_become_root (void);
 
+/* Return true if this process can perform a chroot operation.  In
+   general, this is only possible if support_become_root has been
+   called.  Note that the actual test is performed in a subprocess,
+   after fork, so that the file system root of the original process is
+   not changed.  */
+bool support_can_chroot (void);
+
 /* Enter a network namespace (and a UTS namespace if possible) and
    configure the loopback interface.  Return true if a network
    namespace could be created.  Print diagnostics to standard output.
@@ -48,6 +55,11 @@ bool support_enter_network_namespace (void);
    UTS namespace.  */
 bool support_in_uts_namespace (void);
 
+/* Invoke CALLBACK (CLOSURE) in a subprocess created using fork.
+   Terminate the calling process if the subprocess exits with a
+   non-zero exit status.  */
+void support_isolate_in_subprocess (void (*callback) (void *), void *closure);
+
 __END_DECLS
 
 #endif
diff --git a/support/support-xstat.c b/support/support-xstat.c
new file mode 100644
index 0000000000..86a81ec601
--- /dev/null
+++ b/support/support-xstat.c
@@ -0,0 +1,30 @@
+/* stat64 with error checking.
+   Copyright (C) 2017 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/>.  */
+
+/* NB: Non-standard file name to avoid sysdeps override for xstat.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+#include <sys/stat.h>
+
+void
+xstat (const char *path, struct stat64 *result)
+{
+  if (stat64 (path, result) != 0)
+    FAIL_EXIT1 ("stat64 (\"%s\"): %m", path);
+}
diff --git a/support/support.h b/support/support.h
index 7292e2a564..4b5f04c2cc 100644
--- a/support/support.h
+++ b/support/support.h
@@ -44,6 +44,21 @@ void set_fortify_handler (void (*handler) (int sig));
 void oom_error (const char *function, size_t size)
   __attribute__ ((nonnull (1)));
 
+/* Return a pointer to a memory region of SIZE bytes.  The memory is
+   initialized to zero and will be shared with subprocesses (across
+   fork).  The returned pointer must be freed using
+   support_shared_free; it is not compatible with the malloc
+   functions.  */
+void *support_shared_allocate (size_t size);
+
+/* Deallocate a pointer returned by support_shared_allocate.  */
+void support_shared_free (void *);
+
+/* Write CONTENTS to the file PATH.  Create or truncate the file as
+   needed.  The file mode is 0666 masked by the umask.  Terminate the
+   process on error.  */
+void support_write_file_string (const char *path, const char *contents);
+
 /* Error-checking wrapper functions which terminate the process on
    error.  */
 
diff --git a/support/support_can_chroot.c b/support/support_can_chroot.c
new file mode 100644
index 0000000000..0dfd2deb54
--- /dev/null
+++ b/support/support_can_chroot.c
@@ -0,0 +1,65 @@
+/* Return true if the process can perform a chroot operation.
+   Copyright (C) 2017 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 <stdio.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <xunistd.h>
+
+static void
+callback (void *closure)
+{
+  int *result = closure;
+  struct stat64 before;
+  xstat ("/dev", &before);
+  if (chroot ("/dev") != 0)
+    {
+      *result = errno;
+      return;
+    }
+  struct stat64 after;
+  xstat ("/", &after);
+  TEST_VERIFY (before.st_dev == after.st_dev);
+  TEST_VERIFY (before.st_ino == after.st_ino);
+  *result = 0;
+}
+
+bool
+support_can_chroot (void)
+{
+  int *result = support_shared_allocate (sizeof (*result));
+  *result = 0;
+  support_isolate_in_subprocess (callback, result);
+  bool ok = *result == 0;
+  if (!ok)
+    {
+      static bool already_warned;
+      if (!already_warned)
+        {
+          already_warned = true;
+          errno = *result;
+          printf ("warning: this process does not support chroot: %m\n");
+        }
+    }
+  support_shared_free (result);
+  return ok;
+}
diff --git a/support/support_capture_subprocess_check.c b/support/support_capture_subprocess_check.c
new file mode 100644
index 0000000000..708c89f331
--- /dev/null
+++ b/support/support_capture_subprocess_check.c
@@ -0,0 +1,67 @@
+/* Verify capture output from a subprocess.
+   Copyright (C) 2017 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 <stdbool.h>
+#include <stdio.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+
+static void
+print_context (const char *context, bool *failed)
+{
+  if (*failed)
+    /* Do not duplicate message.  */
+    return;
+  support_record_failure ();
+  printf ("error: subprocess failed: %s\n", context);
+}
+
+void
+support_capture_subprocess_check (struct support_capture_subprocess *proc,
+                                  const char *context, int status,
+                                  int allowed)
+{
+  TEST_VERIFY ((allowed & sc_allow_none)
+               || (allowed & sc_allow_stdout)
+               || (allowed & sc_allow_stderr));
+  TEST_VERIFY (!((allowed & sc_allow_none)
+                 && ((allowed & sc_allow_stdout)
+                     || (allowed & sc_allow_stderr))));
+
+  bool failed = false;
+  if (proc->status != status)
+    {
+      print_context (context, &failed);
+      printf ("error:   expected exit status: %d\n", status);
+      printf ("error:   actual exit status:   %d\n", status);
+    }
+  if (!(allowed & sc_allow_stdout) && proc->out.length != 0)
+    {
+      print_context (context, &failed);
+      printf ("error:   unexpected output from subprocess\n");
+      fwrite (proc->out.buffer, proc->out.length, 1, stdout);
+      puts ("\n");
+    }
+  if (!(allowed & sc_allow_stderr) && proc->err.length != 0)
+    {
+      print_context (context, &failed);
+      printf ("error:   unexpected error output from subprocess\n");
+      fwrite (proc->err.buffer, proc->err.length, 1, stdout);
+      puts ("\n");
+    }
+}
diff --git a/support/support_isolate_in_subprocess.c b/support/support_isolate_in_subprocess.c
new file mode 100644
index 0000000000..cf48614383
--- /dev/null
+++ b/support/support_isolate_in_subprocess.c
@@ -0,0 +1,38 @@
+/* Run a function in a subprocess.
+   Copyright (C) 2017 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 <support/check.h>
+#include <support/xunistd.h>
+
+void
+support_isolate_in_subprocess (void (*callback) (void *), void *closure)
+{
+  pid_t pid = xfork ();
+  if (pid == 0)
+    {
+      /* Child process.  */
+      callback (closure);
+      _exit (0);
+    }
+
+  /* Parent process.  */
+  int status;
+  xwaitpid (pid, &status, 0);
+  if (status != 0)
+    FAIL_EXIT1 ("child process exited with status %d", status);
+}
diff --git a/support/support_shared_allocate.c b/support/support_shared_allocate.c
new file mode 100644
index 0000000000..61d088e8cf
--- /dev/null
+++ b/support/support_shared_allocate.c
@@ -0,0 +1,57 @@
+/* Allocate a memory region shared across processes.
+   Copyright (C) 2017 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 <stddef.h>
+#include <support/support.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+
+/* Header for the allocation.  It contains the size of the allocation
+   for subsequent unmapping.  */
+struct header
+{
+  size_t total_size;
+  char data[] __attribute__ ((aligned (__alignof__ (max_align_t))));
+};
+
+void *
+support_shared_allocate (size_t size)
+{
+  size_t total_size = size + offsetof (struct header, data);
+  if (total_size < size)
+    {
+      errno = ENOMEM;
+      oom_error (__func__, size);
+      return NULL;
+    }
+  else
+    {
+      struct header *result = xmmap (NULL, total_size, PROT_READ | PROT_WRITE,
+                                     MAP_ANONYMOUS | MAP_SHARED, -1);
+      result->total_size = total_size;
+      return &result->data;
+    }
+}
+
+void
+support_shared_free (void *data)
+{
+  struct header *header = data - offsetof (struct header, data);
+  xmunmap (header, header->total_size);
+}
diff --git a/support/support_write_file_string.c b/support/support_write_file_string.c
new file mode 100644
index 0000000000..48e89597f3
--- /dev/null
+++ b/support/support_write_file_string.c
@@ -0,0 +1,39 @@
+/* Write a string to a file.
+   Copyright (C) 2017 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 <fcntl.h>
+#include <string.h>
+#include <support/check.h>
+#include <xunistd.h>
+
+void
+support_write_file_string (const char *path, const char *contents)
+{
+  int fd = xopen (path, O_CREAT | O_TRUNC | O_WRONLY, 0666);
+  const char *end = contents + strlen (contents);
+  for (const char *p = contents; p < end; )
+    {
+      ssize_t ret = write (fd, p, end - p);
+      if (ret < 0)
+        FAIL_EXIT1 ("cannot write to \"%s\": %m", path);
+      if (ret == 0)
+        FAIL_EXIT1 ("zero-length write to \"%s\"", path);
+      p += ret;
+    }
+  xclose (fd);
+}
diff --git a/support/xchroot.c b/support/xchroot.c
new file mode 100644
index 0000000000..abcc299e00
--- /dev/null
+++ b/support/xchroot.c
@@ -0,0 +1,28 @@
+/* chroot with error checking.
+   Copyright (C) 2017 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 <support/check.h>
+#include <support/xunistd.h>
+#include <sys/stat.h>
+
+void
+xchroot (const char *path)
+{
+  if (chroot (path) != 0)
+    FAIL_EXIT1 ("chroot (\"%s\"): %m", path);
+}
diff --git a/support/xmkdir.c b/support/xmkdir.c
new file mode 100644
index 0000000000..ea17d49391
--- /dev/null
+++ b/support/xmkdir.c
@@ -0,0 +1,28 @@
+/* mkdir with error checking.
+   Copyright (C) 2017 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 <support/check.h>
+#include <support/xunistd.h>
+#include <sys/stat.h>
+
+void
+xmkdir (const char *path, mode_t mode)
+{
+  if (mkdir (path, mode) != 0)
+    FAIL_EXIT1 ("mkdir (\"%s\", 0%o): %m", path, mode);
+}
diff --git a/support/xopen.c b/support/xopen.c
new file mode 100644
index 0000000000..7f033a03a7
--- /dev/null
+++ b/support/xopen.c
@@ -0,0 +1,30 @@
+/* open64 with error checking.
+   Copyright (C) 2017 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 <support/check.h>
+#include <support/xunistd.h>
+#include <fcntl.h>
+
+int
+xopen (const char *path, int flags, mode_t mode)
+{
+  int ret = open64 (path, flags, mode);
+  if (ret < 0)
+    FAIL_EXIT1 ("open64 (\"%s\", 0x%x, 0%o): %m", path, flags, mode);
+  return ret;
+}
diff --git a/support/xunistd.h b/support/xunistd.h
index 7c14bda7be..151d743e1f 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -22,15 +22,22 @@
 #ifndef SUPPORT_XUNISTD_H
 #define SUPPORT_XUNISTD_H
 
-#include <unistd.h>
 #include <sys/cdefs.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 __BEGIN_DECLS
 
+struct stat64;
+
 pid_t xfork (void);
 pid_t xwaitpid (pid_t, int *status, int flags);
 void xpipe (int[2]);
 void xdup2 (int, int);
+int xopen (const char *path, int flags, mode_t);
+void xstat (const char *path, struct stat64 *);
+void xmkdir (const char *path, mode_t);
+void xchroot (const char *path);
 
 /* Close the file descriptor.  Ignore EINTR errors, but terminate the
    process on other errors.  */