This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[PATCH] resolv: Extract __res_init_fp function
- From: Florian Weimer <fweimer at redhat dot com>
- To: GNU C Library <libc-alpha at sourceware dot org>
- Date: Sun, 14 Feb 2016 12:27:48 +0100
- Subject: [PATCH] resolv: Extract __res_init_fp function
- Authentication-results: sourceware.org; auth=none
This function helps with resolver testing.
The new test shows a memory leak under valgrind, which is due to bug 19257.
Florian
2016-02-14 Florian Weimer <fweimer@redhat.com>
* resolv/res_init.c (init_defdname): Extracted from _res_vinit.
(__res_init_fp): Likewise. Use parameters instead of environment
variables, and have the caller supply the input stream. Use
__getline to read lines from the input file.
(__res_vinit): Use __res_init_fp.
* resolv/Versions (GLIBC_PRIVATE): Export __res_init_fp.
* resolv/tst-res_init.c: New test.
* resolv/Makefile (tests): Add it.
diff --git a/include/resolv.h b/include/resolv.h
index 4c61476..fc05484 100644
--- a/include/resolv.h
+++ b/include/resolv.h
@@ -22,6 +22,7 @@ extern __thread struct __res_state *__resp attribute_tls_model_ie;
/* Now define the internal interfaces. */
extern int __res_vinit (res_state, int);
+extern void __res_init_fp (res_state, FILE *, bool, const char *);
extern int __res_maybe_init (res_state, int);
extern void _sethtent (int);
extern void _endhtent (void);
@@ -41,6 +42,7 @@ extern void __res_iclose (res_state statp, bool free_addr);
extern int __res_nopt(res_state statp, int n0, u_char *buf, int buflen,
int anslen);
libc_hidden_proto (__res_ninit)
+libc_hidden_proto (__res_init_fp)
libc_hidden_proto (__res_maybe_init)
libc_hidden_proto (__res_nclose)
libc_hidden_proto (__res_iclose)
diff --git a/resolv/Makefile b/resolv/Makefile
index 8be41d3..08503e0 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -30,7 +30,7 @@ headers := resolv.h \
routines := herror inet_addr inet_ntop inet_pton nsap_addr res_init \
res_hconf res_libc res-state
-tests = tst-aton tst-leaks tst-inet_ntop
+tests = tst-aton tst-leaks tst-inet_ntop tst-res_init
xtests = tst-leaks2
generate := mtrace-tst-leaks.out tst-leaks.mtrace tst-leaks2.mtrace
diff --git a/resolv/Versions b/resolv/Versions
index e561bce..e3226cd 100644
--- a/resolv/Versions
+++ b/resolv/Versions
@@ -27,6 +27,9 @@ libc {
__h_errno; __resp;
__res_maybe_init; __res_iclose;
+
+ # For testing.
+ __res_init_fp;
}
}
diff --git a/resolv/res_init.c b/resolv/res_init.c
index 128004a..6fefa6d 100644
--- a/resolv/res_init.c
+++ b/resolv/res_init.c
@@ -1,3 +1,21 @@
+/* Resolver structure initialization and resolv.conf parsing.
+ Copyright (C) 1995-2016 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
/*
* Copyright (c) 1985, 1989, 1993
* The Regents of the University of California. All rights reserved.
@@ -132,16 +150,57 @@ res_ninit(res_state statp) {
libc_hidden_def (__res_ninit)
#endif
-/* This function has to be reachable by res_data.c but not publically. */
int
-__res_vinit(res_state statp, int preinit) {
- FILE *fp;
+__res_vinit (res_state statp, int preinit)
+{
+ FILE *fp = fopen (_PATH_RESCONF, "rce");
+ /* LOCALDOMAIN is listed in unsecvars.h and removed in AT_SECURE
+ mode. */
+ const char *localdomain = getenv ("LOCALDOMAIN");
+ __res_init_fp (statp, fp, preinit, localdomain);
+ if (fp != NULL)
+ fclose (fp);
+ /* RES_OPTIONS is listed in unsecvars.h, too. */
+ const char *options = getenv ("RES_OPTIONS");
+ if (options != NULL)
+ res_setoptions (statp, options, "env");
+ /* __res_init_fp currently ignores all errors. */
+ return 0;
+}
+
+/* Initialize the defdname member of *STATP if it is empty. The
+ default is derived from the system host name. */
+static void
+init_defdname (res_state statp)
+{
+ if (statp->defdname[0] != '\0')
+ return;
+ if (__gethostname (statp->defdname, sizeof (statp->defdname) - 1) != 0)
+ return;
+ const char *dot = strchr (statp->defdname, '.');
+ if (dot == NULL)
+ /* Host name does not contain a dot. Use the root as the
+ default domain name. */
+ statp->defdname[0] = '\0';
+ else
+ /* Use the remaining part of the host name as the domain name. */
+ memmove (statp->defdname, dot + 1, strlen (dot + 1) + 1);
+}
+
+/* Initialize *STATP from the resolv.conf-style file FP. If PREINIT,
+ do not set default values for the *STATP members retrans, retry,
+ options, id. If LOCALDOMAIN is not NULL, use it to initialize the
+ search path instead of the contents of the file. If FP is NULL,
+ just write the default values (and, possibly, the contents of
+ LOCALDOMAIN) to *STATP. */
+void
+__res_init_fp (res_state statp, FILE *fp, bool preinit,
+ const char *localdomain)
+{
char *cp, **pp;
int n;
- char buf[BUFSIZ];
int nserv = 0; /* number of nameservers read from file */
int have_serv6 = 0;
- int haveenv = 0;
int havesearch = 0;
#ifdef RESOLVSORT
int nsort = 0;
@@ -174,10 +233,10 @@ __res_vinit(res_state statp, int preinit) {
statp->_u._ext.nsaddrs[n] = NULL;
/* Allow user to override the local domain definition */
- if ((cp = getenv("LOCALDOMAIN")) != NULL) {
- (void)strncpy(statp->defdname, cp, sizeof(statp->defdname) - 1);
+ if (localdomain != NULL) {
+ strncpy (statp->defdname, localdomain,
+ sizeof (statp->defdname) - 1);
statp->defdname[sizeof(statp->defdname) - 1] = '\0';
- haveenv++;
/*
* Set search list to be blank-separated strings
@@ -213,17 +272,17 @@ __res_vinit(res_state statp, int preinit) {
(line[sizeof(name) - 1] == ' ' || \
line[sizeof(name) - 1] == '\t'))
- if ((fp = fopen(_PATH_RESCONF, "rce")) != NULL) {
- /* No threads use this stream. */
- __fsetlocking (fp, FSETLOCKING_BYCALLER);
- /* read the config file */
- while (__fgets_unlocked(buf, sizeof(buf), fp) != NULL) {
+ if (fp != NULL) {
+ char *buf = NULL;
+ size_t buf_len = 0;
+ while (__getline (&buf, &buf_len, fp) >= 0) {
/* skip comments */
if (*buf == ';' || *buf == '#')
continue;
/* read default domain name */
if (MATCH(buf, "domain")) {
- if (haveenv) /* skip if have from environ */
+ /* Do not override the environment setting. */
+ if (localdomain != NULL)
continue;
cp = buf + sizeof("domain") - 1;
while (*cp == ' ' || *cp == '\t')
@@ -239,8 +298,9 @@ __res_vinit(res_state statp, int preinit) {
}
/* set search list */
if (MATCH(buf, "search")) {
- if (haveenv) /* skip if have from environ */
- continue;
+ /* Do not override the environment setting. */
+ if (localdomain != NULL)
+ continue;
cp = buf + sizeof("search") - 1;
while (*cp == ' ' || *cp == '\t')
cp++;
@@ -399,7 +459,7 @@ __res_vinit(res_state statp, int preinit) {
#ifdef RESOLVSORT
statp->nsort = nsort;
#endif
- (void) fclose(fp);
+ free (buf);
}
if (__builtin_expect(statp->nscount == 0, 0)) {
statp->nsaddr.sin_addr = __inet_makeaddr(IN_LOOPBACKNET, 1);
@@ -407,10 +467,7 @@ __res_vinit(res_state statp, int preinit) {
statp->nsaddr.sin_port = htons(NAMESERVER_PORT);
statp->nscount = 1;
}
- if (statp->defdname[0] == 0 &&
- __gethostname(buf, sizeof(statp->defdname) - 1) == 0 &&
- (cp = strchr(buf, '.')) != NULL)
- strcpy(statp->defdname, cp + 1);
+ init_defdname (statp);
/* find components of local domain that might be searched */
if (havesearch == 0) {
@@ -443,11 +500,9 @@ __res_vinit(res_state statp, int preinit) {
#endif /* !RFC1535 */
}
- if ((cp = getenv("RES_OPTIONS")) != NULL)
- res_setoptions(statp, cp, "env");
statp->options |= RES_INIT;
- return (0);
}
+libc_hidden_def (__res_init_fp)
static void
internal_function
diff --git a/resolv/tst-res_init.c b/resolv/tst-res_init.c
new file mode 100644
index 0000000..c1e5e03
--- /dev/null
+++ b/resolv/tst-res_init.c
@@ -0,0 +1,236 @@
+/* Test resolv.conf parsing.
+ Copyright (C) 1995-2016 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <arpa/inet.h>
+#include <resolv.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static bool errors;
+
+static char hostname[HOST_NAME_MAX + 1];
+static const char *domainname;
+
+/* Allocate a new resolver state. */
+static res_state
+new_state (void)
+{
+ res_state statp = malloc (sizeof (*statp));
+ if (statp == NULL)
+ {
+ printf ("error: malloc: %m\n");
+ abort ();
+ }
+ return statp;
+}
+
+/* Opens string for reading. Does not make a copy of the string. */
+static FILE *
+open_string (const char *str)
+{
+ FILE *fp = fmemopen ((char *) str, strlen (str), "r");
+ if (fp == NULL)
+ {
+ printf ("error: fmemopen: %m\n");
+ abort ();
+ }
+ return fp;
+}
+
+/* Tests with an empty configuration. If positive, FILL is used to
+ initialize the allocated resolver structure. */
+static void
+test_empty (int fill)
+{
+ res_state statp = new_state ();
+ if (fill >= 0)
+ memset (statp, fill, sizeof (*statp));
+
+ {
+ FILE *fp = open_string ("");
+ __res_init_fp (statp, fp, false, NULL);
+ fclose (fp);
+ }
+
+ if ((statp->options & RES_INIT) == 0)
+ {
+ printf ("error: RES_INIT not set\n");
+ errors = true;
+ }
+ if (domainname == NULL)
+ /* System does not have a domain name as part of the host name. */
+ {
+ if (statp->defdname[0] != '\0')
+ {
+ printf ("error: incorrect default domain name: %s\n", statp->defdname);
+ errors = true;
+ }
+ if (statp->dnsrch[0][0] != '\0')
+ {
+ printf ("error: invalid search entry 0: %s\n", statp->dnsrch[0]);
+ errors = true;
+ }
+ }
+ else
+ /* System has a domain name as part of the host name. */
+ {
+ if (strcmp (statp->defdname, domainname) != 0)
+ {
+ printf ("info: system host name: %s\n", hostname);
+ printf ("error: incorrect default domain name: %s\n", statp->defdname);
+ errors = true;
+ }
+ if (strcmp (statp->dnsrch[0], domainname) != 0)
+ {
+ printf ("error: incorrect search entry 0: %s\n", statp->dnsrch[0]);
+ errors = true;
+ }
+ }
+ if (statp->dnsrch[1] != NULL)
+ {
+ printf ("error: invalid search entry 1: %s\n", statp->dnsrch[1]);
+ errors = true;
+ }
+
+ res_nclose (statp);
+ free (statp);
+}
+
+static void
+test_ipv6 (void)
+{
+ res_state statp = new_state ();
+
+ {
+ const char *conf
+ = "domain .\n"
+ "nameserver 2001:db8::1\n";
+ FILE *fp = open_string (conf);
+ __res_init_fp (statp, fp, false, NULL);
+ fclose (fp);
+ }
+
+ if (statp->nscount != 1)
+ {
+ printf ("error: expected one name server, got %d\n", statp->nscount);
+ errors = true;
+ }
+ else if (statp->_u._ext.nsaddrs[0] == NULL)
+ {
+ printf ("error: IPv6 name server is missing\n");
+ errors = true;
+ }
+ else
+ {
+ char buf[128];
+ if (inet_ntop (AF_INET6, statp->_u._ext.nsaddrs[0]->sin6_addr.s6_addr,
+ buf, sizeof (buf)) == NULL)
+ {
+ printf ("error: inet_ntop: %m\n");
+ errors = true;
+ }
+ else if (strcmp (buf, "2001:db8::1") != 0)
+ {
+ printf ("error: incorrect name server %s\n", buf);
+ errors = true;
+ }
+ }
+
+ res_nclose (statp);
+ free (statp);
+}
+
+/* Check that the original settings are restored on reinitialization
+ of a resolver structure with an empty configuration. */
+static void
+bug19369 (const char *localdomain)
+{
+ res_state statp_reference = new_state ();
+ {
+ FILE *fp = open_string ("");
+ __res_init_fp (statp_reference, fp, false, localdomain);
+ fclose (fp);
+ }
+
+ res_state statp = new_state ();
+ {
+ /* NB: No IPv6 name servers. They would trigger memory
+ allocation, and reinitialization without an intervening
+ res_nclose leads to a memory leak. */
+ const char *conf
+ = "search example.org\n"
+ "nameserver 192.0.2.1\n"
+ "nameserver 192.0.2.2\n";
+ FILE *fp = open_string (conf);
+ __res_init_fp (statp, fp, false, NULL);
+ fclose (fp);
+ }
+ if (statp->nscount != 2)
+ {
+ printf ("error: invalid name server count: %d\n", statp->nscount);
+ errors = true;
+ }
+ {
+ FILE *fp = open_string ("");
+ __res_init_fp (statp, fp, false, localdomain);
+ fclose (fp);
+ }
+ if (statp->nscount != 1)
+ {
+ printf ("error: invalid name server count: %d\n", statp->nscount);
+ errors = true;
+ }
+ if (ntohl (statp->nsaddr_list[0].sin_addr.s_addr) != INADDR_LOOPBACK)
+ {
+ printf ("error: incorrect loopback name server address\n");
+ errors = true;
+ }
+ if (strcmp (statp->defdname, statp_reference->defdname) != 0)
+ {
+ printf ("info: expected defdname: %s\n", statp_reference->defdname);
+ printf ("error: actual defdname: %s\n", statp->defdname);
+ errors = true;
+ }
+
+ free (statp);
+ free (statp_reference);
+}
+
+static int
+do_test (void)
+{
+ if (gethostname (hostname, sizeof (hostname) - 1) != 0)
+ hostname[0] = '\0';
+ domainname = strchr (hostname, '.');
+ if (domainname != NULL)
+ ++domainname;
+
+ for (int i = -1; i <= 255; ++i)
+ test_empty (i);
+
+ test_ipv6 ();
+ bug19369 (NULL);
+ bug19369 ("");
+
+ return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"