From 2714c5f3c95f90977167c1d21326d907fb76b419 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Fri, 2 Jun 2017 15:50:36 +0200 Subject: resolv: Tests for various versions of res_init --- ChangeLog | 32 ++ resolv/Makefile | 8 + resolv/tst-resolv-res_init-skeleton.c | 601 +++++++++++++++++++++++++++++ resolv/tst-resolv-res_init-thread.c | 20 + resolv/tst-resolv-res_init.c | 20 + support/Makefile | 9 + support/capture_subprocess.h | 19 + support/namespace.h | 12 + support/support-xstat.c | 30 ++ support/support.h | 15 + support/support_can_chroot.c | 65 ++++ support/support_capture_subprocess_check.c | 67 ++++ support/support_isolate_in_subprocess.c | 38 ++ support/support_shared_allocate.c | 57 +++ support/support_write_file_string.c | 39 ++ support/xchroot.c | 28 ++ support/xmkdir.c | 28 ++ support/xopen.c | 30 ++ support/xunistd.h | 9 +- 19 files changed, 1126 insertions(+), 1 deletion(-) create mode 100644 resolv/tst-resolv-res_init-skeleton.c create mode 100644 resolv/tst-resolv-res_init-thread.c create mode 100644 resolv/tst-resolv-res_init.c create mode 100644 support/support-xstat.c create mode 100644 support/support_can_chroot.c create mode 100644 support/support_capture_subprocess_check.c create mode 100644 support/support_isolate_in_subprocess.c create mode 100644 support/support_shared_allocate.c create mode 100644 support/support_write_file_string.c create mode 100644 support/xchroot.c create mode 100644 support/xmkdir.c create mode 100644 support/xopen.c diff --git a/ChangeLog b/ChangeLog index 579258f02a..6b32e3192c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +2017-06-02 Florian Weimer + + 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 * sysdeps/posix/getaddrinfo.c (getcanonname): New function. 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 . +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 + . */ + +/* 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 +#include +#include +#include /* For DEPRECATED_RES_USE_INET6. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if TEST_THREAD +# include +#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 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 + . */ + +#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 + . */ + +#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 + . */ + +/* NB: Non-standard file name to avoid sysdeps override for xstat. */ + +#include +#include +#include + +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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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 + . */ + +#include +#include +#include +#include + +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 + . */ + +#include +#include + +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 + . */ + +#include +#include +#include +#include +#include + +/* 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 + . */ + +#include +#include +#include +#include + +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 + . */ + +#include +#include +#include + +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 + . */ + +#include +#include +#include + +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 + . */ + +#include +#include +#include + +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 #include +#include +#include __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. */ -- cgit v1.2.3