From 571c531b3ba5e6ae848992cd4f6e19ab63eb564a Mon Sep 17 00:00:00 2001
From: Siddhesh Poyarekar <siddhesh@sourceware.org>
Date: Mon, 7 Mar 2022 14:08:51 +0530
Subject: [PATCH] gaih_inet: make numeric lookup a separate routine

Introduce the gaih_result structure and general paradigm for cleanups
that follow to process the lookup request and return a result.  A lookup
function (like text_to_binary_address), should return an integer error
code and set members of gaih_result based on what it finds.  If the
function does not have a result and no errors have occurred during the
lookup, it should return 0 and res.at should be set to NULL, allowing a
subsequent function to do the lookup until we run out of options.

Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Reviewed-by: DJ Delorie <dj@redhat.com>
(cherry picked from commit 26dea461191cca519b498890a9682fe4bc8e4c2f)
---
 sysdeps/posix/getaddrinfo.c | 891 ++++++++++++++++++------------------
 1 file changed, 452 insertions(+), 439 deletions(-)

diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c
index dae5e9f55f..19bb13db59 100644
--- a/sysdeps/posix/getaddrinfo.c
+++ b/sysdeps/posix/getaddrinfo.c
@@ -116,6 +116,12 @@ struct gaih_typeproto
     char name[8];
   };
 
+struct gaih_result
+{
+  struct gaih_addrtuple *at;
+  char *canon;
+};
+
 /* Values for `protoflag'.  */
 #define GAI_PROTO_NOSERVICE	1
 #define GAI_PROTO_PROTOANY	2
@@ -297,7 +303,7 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req,
 	}								      \
       *pat = addrmem;							      \
 									      \
-      if (localcanon != NULL && canon == NULL)				      \
+      if (localcanon != NULL && res.canon == NULL)			      \
 	{								      \
 	  char *canonbuf = __strdup (localcanon);			      \
 	  if (canonbuf == NULL)						      \
@@ -306,7 +312,7 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req,
 	      result = -EAI_SYSTEM;					      \
 	      goto free_and_return;					      \
 	    }								      \
-	  canon = canonbuf;						      \
+	  res.canon = canonbuf;						      \
 	}								      \
       if (_family == AF_INET6 && *pat != NULL)				      \
 	got_ipv6 = true;						      \
@@ -342,9 +348,9 @@ getcanonname (nss_action_list nip, struct gaih_addrtuple *at, const char *name)
 
 static int
 process_canonname (const struct addrinfo *req, const char *orig_name,
-		   char **canonp)
+		   struct gaih_result *res)
 {
-  char *canon = *canonp;
+  char *canon = res->canon;
 
   if ((req->ai_flags & AI_CANONNAME) != 0)
     {
@@ -368,7 +374,7 @@ process_canonname (const struct addrinfo *req, const char *orig_name,
 	return -EAI_MEMORY;
     }
 
-  *canonp = canon;
+  res->canon = canon;
   return 0;
 }
 
@@ -460,6 +466,105 @@ get_servtuples (const struct gaih_service *service, const struct addrinfo *req,
   return 0;
 }
 
+/* Convert numeric addresses to binary into RES.  On failure, RES->AT is set to
+   NULL and an error code is returned.  If AI_NUMERIC_HOST is not requested and
+   the function cannot determine a result, RES->AT is set to NULL and 0
+   returned.  */
+
+static int
+text_to_binary_address (const char *name, const struct addrinfo *req,
+			struct gaih_result *res)
+{
+  struct gaih_addrtuple *at = res->at;
+  int result = 0;
+
+  assert (at != NULL);
+
+  memset (at->addr, 0, sizeof (at->addr));
+  if (__inet_aton_exact (name, (struct in_addr *) at->addr) != 0)
+    {
+      if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET)
+	at->family = AF_INET;
+      else if (req->ai_family == AF_INET6 && (req->ai_flags & AI_V4MAPPED))
+	{
+	  at->addr[3] = at->addr[0];
+	  at->addr[2] = htonl (0xffff);
+	  at->addr[1] = 0;
+	  at->addr[0] = 0;
+	  at->family = AF_INET6;
+	}
+      else
+	{
+	  result = -EAI_ADDRFAMILY;
+	  goto out;
+	}
+
+      if (req->ai_flags & AI_CANONNAME)
+	{
+	  char *canonbuf = __strdup (name);
+	  if (canonbuf == NULL)
+	    {
+	      result = -EAI_MEMORY;
+	      goto out;
+	    }
+	  res->canon = canonbuf;
+	}
+      return 0;
+    }
+
+  char *scope_delim = strchr (name, SCOPE_DELIMITER);
+  int e;
+
+  if (scope_delim == NULL)
+    e = inet_pton (AF_INET6, name, at->addr);
+  else
+    e = __inet_pton_length (AF_INET6, name, scope_delim - name, at->addr);
+
+  if (e > 0)
+    {
+      if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET6)
+	at->family = AF_INET6;
+      else if (req->ai_family == AF_INET
+	       && IN6_IS_ADDR_V4MAPPED (at->addr))
+	{
+	  at->addr[0] = at->addr[3];
+	  at->family = AF_INET;
+	}
+      else
+	{
+	  result = -EAI_ADDRFAMILY;
+	  goto out;
+	}
+
+      if (scope_delim != NULL
+	  && __inet6_scopeid_pton ((struct in6_addr *) at->addr,
+				   scope_delim + 1, &at->scopeid) != 0)
+	{
+	  result = -EAI_NONAME;
+	  goto out;
+	}
+
+      if (req->ai_flags & AI_CANONNAME)
+	{
+	  char *canonbuf = __strdup (name);
+	  if (canonbuf == NULL)
+	    {
+	      result = -EAI_MEMORY;
+	      goto out;
+	    }
+	  res->canon = canonbuf;
+	}
+      return 0;
+    }
+
+  if ((req->ai_flags & AI_NUMERICHOST))
+    result = -EAI_NONAME;
+
+out:
+  res->at = NULL;
+  return result;
+}
+
 static int
 gaih_inet (const char *name, const struct gaih_service *service,
 	   const struct addrinfo *req, struct addrinfo **pai,
@@ -468,9 +573,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
   struct gaih_servtuple st[sizeof (gaih_inet_typeproto)
 			   / sizeof (struct gaih_typeproto)] = {0};
 
-  struct gaih_addrtuple *at = NULL;
   bool got_ipv6 = false;
-  char *canon = NULL;
   const char *orig_name = name;
 
   /* Reserve stack memory for the scratch buffer in the getaddrinfo
@@ -485,6 +588,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
   struct gaih_addrtuple *addrmem = NULL;
   int result = 0;
 
+  struct gaih_result res = {0};
   if (name != NULL)
     {
       if (req->ai_flags & AI_IDN)
@@ -497,533 +601,441 @@ gaih_inet (const char *name, const struct gaih_service *service,
 	  malloc_name = true;
 	}
 
-      uint32_t addr[4];
-      if (__inet_aton_exact (name, (struct in_addr *) addr) != 0)
+      res.at = alloca_account (sizeof (struct gaih_addrtuple), alloca_used);
+      res.at->scopeid = 0;
+      res.at->next = NULL;
+
+      if ((result = text_to_binary_address (name, req, &res)) != 0)
+	goto free_and_return;
+      else if (res.at != NULL)
+	goto process_list;
+
+      int no_data = 0;
+      int no_inet6_data = 0;
+      nss_action_list nip;
+      enum nss_status inet6_status = NSS_STATUS_UNAVAIL;
+      enum nss_status status = NSS_STATUS_UNAVAIL;
+      int no_more;
+      struct resolv_context *res_ctx = NULL;
+      bool do_merge = false;
+
+      /* If we do not have to look for IPv6 addresses or the canonical
+	 name, use the simple, old functions, which do not support
+	 IPv6 scope ids, nor retrieving the canonical name.  */
+      if (req->ai_family == AF_INET
+	  && (req->ai_flags & AI_CANONNAME) == 0)
 	{
-	  at = alloca_account (sizeof (struct gaih_addrtuple), alloca_used);
-	  at->scopeid = 0;
-	  at->next = NULL;
-
-	  if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET)
-	    {
-	      memcpy (at->addr, addr, sizeof (at->addr));
-	      at->family = AF_INET;
-	    }
-	  else if (req->ai_family == AF_INET6 && (req->ai_flags & AI_V4MAPPED))
-	    {
-	      at->addr[3] = addr[0];
-	      at->addr[2] = htonl (0xffff);
-	      at->addr[1] = 0;
-	      at->addr[0] = 0;
-	      at->family = AF_INET6;
-	    }
-	  else
-	    {
-	      result = -EAI_ADDRFAMILY;
-	      goto free_and_return;
-	    }
+	  int rc;
+	  struct hostent th;
+	  struct hostent *h;
 
-	  if (req->ai_flags & AI_CANONNAME)
+	  while (1)
 	    {
-	      char *canonbuf = __strdup (name);
-	      if (canonbuf == NULL)
+	      rc = __gethostbyname2_r (name, AF_INET, &th,
+				       tmpbuf->data, tmpbuf->length,
+				       &h, &h_errno);
+	      if (rc != ERANGE || h_errno != NETDB_INTERNAL)
+		break;
+	      if (!scratch_buffer_grow (tmpbuf))
 		{
 		  result = -EAI_MEMORY;
 		  goto free_and_return;
 		}
-	      canon = canonbuf;
 	    }
 
-	  goto process_list;
-	}
-
-      char *scope_delim = strchr (name, SCOPE_DELIMITER);
-      int e;
-
-      if (scope_delim == NULL)
-	e = inet_pton (AF_INET6, name, addr);
-      else
-	e = __inet_pton_length (AF_INET6, name, scope_delim - name, addr);
-
-      if (e > 0)
-	{
-	  at = alloca_account (sizeof (struct gaih_addrtuple),
-			       alloca_used);
-	  at->scopeid = 0;
-	  at->next = NULL;
-
-	  if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET6)
-	    {
-	      memcpy (at->addr, addr, sizeof (at->addr));
-	      at->family = AF_INET6;
-	    }
-	  else if (req->ai_family == AF_INET
-		   && IN6_IS_ADDR_V4MAPPED (addr))
+	  if (rc == 0)
 	    {
-	      at->addr[0] = addr[3];
-	      at->addr[1] = addr[1];
-	      at->addr[2] = addr[2];
-	      at->addr[3] = addr[3];
-	      at->family = AF_INET;
+	      if (h != NULL)
+		{
+		  /* We found data, convert it.  */
+		  if (!convert_hostent_to_gaih_addrtuple
+		      (req, AF_INET, h, &addrmem))
+		    {
+		      result = -EAI_MEMORY;
+		      goto free_and_return;
+		    }
+		  res.at = addrmem;
+		}
+	      else
+		{
+		  if (h_errno == NO_DATA)
+		    result = -EAI_NODATA;
+		  else
+		    result = -EAI_NONAME;
+		  goto free_and_return;
+		}
 	    }
 	  else
 	    {
-	      result = -EAI_ADDRFAMILY;
-	      goto free_and_return;
-	    }
+	      if (h_errno == NETDB_INTERNAL)
+		result = -EAI_SYSTEM;
+	      else if (h_errno == TRY_AGAIN)
+		result = -EAI_AGAIN;
+	      else
+		/* We made requests but they turned out no data.
+		   The name is known, though.  */
+		result = -EAI_NODATA;
 
-	  if (scope_delim != NULL
-	      && __inet6_scopeid_pton ((struct in6_addr *) at->addr,
-				       scope_delim + 1,
-				       &at->scopeid) != 0)
-	    {
-	      result = -EAI_NONAME;
 	      goto free_and_return;
 	    }
 
-	  if (req->ai_flags & AI_CANONNAME)
+	  goto process_list;
+	}
+
+#ifdef USE_NSCD
+      if (__nss_not_use_nscd_hosts > 0
+	  && ++__nss_not_use_nscd_hosts > NSS_NSCD_RETRY)
+	__nss_not_use_nscd_hosts = 0;
+
+      if (!__nss_not_use_nscd_hosts
+	  && !__nss_database_custom[NSS_DBSIDX_hosts])
+	{
+	  /* Try to use nscd.  */
+	  struct nscd_ai_result *air = NULL;
+	  int err = __nscd_getai (name, &air, &h_errno);
+	  if (air != NULL)
 	    {
-	      char *canonbuf = __strdup (name);
-	      if (canonbuf == NULL)
+	      /* Transform into gaih_addrtuple list.  */
+	      bool added_canon = (req->ai_flags & AI_CANONNAME) == 0;
+	      char *addrs = air->addrs;
+
+	      addrmem = calloc (air->naddrs, sizeof (*addrmem));
+	      if (addrmem == NULL)
 		{
 		  result = -EAI_MEMORY;
 		  goto free_and_return;
 		}
-	      canon = canonbuf;
-	    }
 
-	  goto process_list;
-	}
-
-      if ((req->ai_flags & AI_NUMERICHOST) == 0)
-	{
-	  int no_data = 0;
-	  int no_inet6_data = 0;
-	  nss_action_list nip;
-	  enum nss_status inet6_status = NSS_STATUS_UNAVAIL;
-	  enum nss_status status = NSS_STATUS_UNAVAIL;
-	  int no_more;
-	  struct resolv_context *res_ctx = NULL;
-	  bool do_merge = false;
-
-	  /* If we do not have to look for IPv6 addresses or the canonical
-	     name, use the simple, old functions, which do not support
-	     IPv6 scope ids, nor retrieving the canonical name.  */
-	  if (req->ai_family == AF_INET
-	      && (req->ai_flags & AI_CANONNAME) == 0)
-	    {
-	      int rc;
-	      struct hostent th;
-	      struct hostent *h;
+	      struct gaih_addrtuple *addrfree = addrmem;
+	      struct gaih_addrtuple **pat = &res.at;
 
-	      while (1)
+	      for (int i = 0; i < air->naddrs; ++i)
 		{
-		  rc = __gethostbyname2_r (name, AF_INET, &th,
-					   tmpbuf->data, tmpbuf->length,
-					   &h, &h_errno);
-		  if (rc != ERANGE || h_errno != NETDB_INTERNAL)
-		    break;
-		  if (!scratch_buffer_grow (tmpbuf))
+		  socklen_t size = (air->family[i] == AF_INET
+				    ? INADDRSZ : IN6ADDRSZ);
+
+		  if (!((air->family[i] == AF_INET
+			 && req->ai_family == AF_INET6
+			 && (req->ai_flags & AI_V4MAPPED) != 0)
+			|| req->ai_family == AF_UNSPEC
+			|| air->family[i] == req->ai_family))
 		    {
-		      result = -EAI_MEMORY;
-		      goto free_and_return;
+		      /* Skip over non-matching result.  */
+		      addrs += size;
+		      continue;
 		    }
-		}
 
-	      if (rc == 0)
-		{
-		  if (h != NULL)
+		  if (*pat == NULL)
+		    {
+		      *pat = addrfree++;
+		      (*pat)->scopeid = 0;
+		    }
+		  uint32_t *pataddr = (*pat)->addr;
+		  (*pat)->next = NULL;
+		  if (added_canon || air->canon == NULL)
+		    (*pat)->name = NULL;
+		  else if (res.canon == NULL)
 		    {
-		      /* We found data, convert it.  */
-		      if (!convert_hostent_to_gaih_addrtuple
-			  (req, AF_INET, h, &addrmem))
+		      char *canonbuf = __strdup (air->canon);
+		      if (canonbuf == NULL)
 			{
 			  result = -EAI_MEMORY;
 			  goto free_and_return;
 			}
-		      at = addrmem;
+		      res.canon = (*pat)->name = canonbuf;
 		    }
-		  else
+
+		  if (air->family[i] == AF_INET
+		      && req->ai_family == AF_INET6
+		      && (req->ai_flags & AI_V4MAPPED))
 		    {
-		      if (h_errno == NO_DATA)
-			result = -EAI_NODATA;
-		      else
-			result = -EAI_NONAME;
-		      goto free_and_return;
+		      (*pat)->family = AF_INET6;
+		      pataddr[3] = *(uint32_t *) addrs;
+		      pataddr[2] = htonl (0xffff);
+		      pataddr[1] = 0;
+		      pataddr[0] = 0;
+		      pat = &((*pat)->next);
+		      added_canon = true;
+		    }
+		  else if (req->ai_family == AF_UNSPEC
+			   || air->family[i] == req->ai_family)
+		    {
+		      (*pat)->family = air->family[i];
+		      memcpy (pataddr, addrs, size);
+		      pat = &((*pat)->next);
+		      added_canon = true;
+		      if (air->family[i] == AF_INET6)
+			got_ipv6 = true;
 		    }
+		  addrs += size;
 		}
-	      else
-		{
-		  if (h_errno == NETDB_INTERNAL)
-		    result = -EAI_SYSTEM;
-		  else if (h_errno == TRY_AGAIN)
-		    result = -EAI_AGAIN;
-		  else
-		    /* We made requests but they turned out no data.
-		       The name is known, though.  */
-		    result = -EAI_NODATA;
 
-		  goto free_and_return;
-		}
+	      free (air);
 
 	      goto process_list;
 	    }
+	  else if (err == 0)
+	    /* The database contains a negative entry.  */
+	    goto free_and_return;
+	  else if (__nss_not_use_nscd_hosts == 0)
+	    {
+	      if (h_errno == NETDB_INTERNAL && errno == ENOMEM)
+		result = -EAI_MEMORY;
+	      else if (h_errno == TRY_AGAIN)
+		result = -EAI_AGAIN;
+	      else
+		result = -EAI_SYSTEM;
 
-#ifdef USE_NSCD
-	  if (__nss_not_use_nscd_hosts > 0
-	      && ++__nss_not_use_nscd_hosts > NSS_NSCD_RETRY)
-	    __nss_not_use_nscd_hosts = 0;
+	      goto free_and_return;
+	    }
+	}
+#endif
+
+      no_more = !__nss_database_get (nss_database_hosts, &nip);
 
-	  if (!__nss_not_use_nscd_hosts
-	      && !__nss_database_custom[NSS_DBSIDX_hosts])
+      /* If we are looking for both IPv4 and IPv6 address we don't
+	 want the lookup functions to automatically promote IPv4
+	 addresses to IPv6 addresses, so we use the no_inet6
+	 function variant.  */
+      res_ctx = __resolv_context_get ();
+      if (res_ctx == NULL)
+	no_more = 1;
+
+      while (!no_more)
+	{
+	  /* Always start afresh; continue should discard previous results
+	     and the hosts database does not support merge.  */
+	  res.at = NULL;
+	  free (res.canon);
+	  free (addrmem);
+	  res.canon = NULL;
+	  addrmem = NULL;
+	  got_ipv6 = false;
+
+	  if (do_merge)
 	    {
-	      /* Try to use nscd.  */
-	      struct nscd_ai_result *air = NULL;
-	      int err = __nscd_getai (name, &air, &h_errno);
-	      if (air != NULL)
+	      __set_h_errno (NETDB_INTERNAL);
+	      __set_errno (EBUSY);
+	      break;
+	    }
+
+	  no_data = 0;
+	  nss_gethostbyname4_r *fct4 = NULL;
+
+	  /* gethostbyname4_r sends out parallel A and AAAA queries and
+	     is thus only suitable for PF_UNSPEC.  */
+	  if (req->ai_family == PF_UNSPEC)
+	    fct4 = __nss_lookup_function (nip, "gethostbyname4_r");
+
+	  if (fct4 != NULL)
+	    {
+	      while (1)
 		{
-		  /* Transform into gaih_addrtuple list.  */
-		  bool added_canon = (req->ai_flags & AI_CANONNAME) == 0;
-		  char *addrs = air->addrs;
+		  status = DL_CALL_FCT (fct4, (name, &res.at,
+					       tmpbuf->data, tmpbuf->length,
+					       &errno, &h_errno,
+					       NULL));
+		  if (status == NSS_STATUS_SUCCESS)
+		    break;
+		  /* gethostbyname4_r may write into AT, so reset it.  */
+		  res.at = NULL;
+		  if (status != NSS_STATUS_TRYAGAIN
+		      || errno != ERANGE || h_errno != NETDB_INTERNAL)
+		    {
+		      if (h_errno == TRY_AGAIN)
+			no_data = EAI_AGAIN;
+		      else
+			no_data = h_errno == NO_DATA;
+		      break;
+		    }
 
-		  addrmem = calloc (air->naddrs, sizeof (*addrmem));
-		  if (addrmem == NULL)
+		  if (!scratch_buffer_grow (tmpbuf))
 		    {
+		      __resolv_context_put (res_ctx);
 		      result = -EAI_MEMORY;
 		      goto free_and_return;
 		    }
+		}
 
-		  struct gaih_addrtuple *addrfree = addrmem;
-		  struct gaih_addrtuple **pat = &at;
+	      if (status == NSS_STATUS_SUCCESS)
+		{
+		  assert (!no_data);
+		  no_data = 1;
 
-		  for (int i = 0; i < air->naddrs; ++i)
+		  if ((req->ai_flags & AI_CANONNAME) != 0 && res.canon == NULL)
 		    {
-		      socklen_t size = (air->family[i] == AF_INET
-					? INADDRSZ : IN6ADDRSZ);
-
-		      if (!((air->family[i] == AF_INET
-			     && req->ai_family == AF_INET6
-			     && (req->ai_flags & AI_V4MAPPED) != 0)
-			    || req->ai_family == AF_UNSPEC
-			    || air->family[i] == req->ai_family))
+		      char *canonbuf = __strdup (res.at->name);
+		      if (canonbuf == NULL)
 			{
-			  /* Skip over non-matching result.  */
-			  addrs += size;
-			  continue;
+			  __resolv_context_put (res_ctx);
+			  result = -EAI_MEMORY;
+			  goto free_and_return;
 			}
+		      res.canon = canonbuf;
+		    }
 
-		      if (*pat == NULL)
-			{
-			  *pat = addrfree++;
-			  (*pat)->scopeid = 0;
-			}
-		      uint32_t *pataddr = (*pat)->addr;
-		      (*pat)->next = NULL;
-		      if (added_canon || air->canon == NULL)
-			(*pat)->name = NULL;
-		      else if (canon == NULL)
-			{
-			  char *canonbuf = __strdup (air->canon);
-			  if (canonbuf == NULL)
-			    {
-			      result = -EAI_MEMORY;
-			      goto free_and_return;
-			    }
-			  canon = (*pat)->name = canonbuf;
-			}
+		  struct gaih_addrtuple **pat = &res.at;
 
-		      if (air->family[i] == AF_INET
+		  while (*pat != NULL)
+		    {
+		      if ((*pat)->family == AF_INET
 			  && req->ai_family == AF_INET6
-			  && (req->ai_flags & AI_V4MAPPED))
+			  && (req->ai_flags & AI_V4MAPPED) != 0)
 			{
+			  uint32_t *pataddr = (*pat)->addr;
 			  (*pat)->family = AF_INET6;
-			  pataddr[3] = *(uint32_t *) addrs;
+			  pataddr[3] = pataddr[0];
 			  pataddr[2] = htonl (0xffff);
 			  pataddr[1] = 0;
 			  pataddr[0] = 0;
 			  pat = &((*pat)->next);
-			  added_canon = true;
+			  no_data = 0;
 			}
 		      else if (req->ai_family == AF_UNSPEC
-			       || air->family[i] == req->ai_family)
+			       || (*pat)->family == req->ai_family)
 			{
-			  (*pat)->family = air->family[i];
-			  memcpy (pataddr, addrs, size);
 			  pat = &((*pat)->next);
-			  added_canon = true;
-			  if (air->family[i] == AF_INET6)
+
+			  no_data = 0;
+			  if (req->ai_family == AF_INET6)
 			    got_ipv6 = true;
 			}
-		      addrs += size;
+		      else
+			*pat = ((*pat)->next);
 		    }
-
-		  free (air);
-
-		  goto process_list;
 		}
-	      else if (err == 0)
-		/* The database contains a negative entry.  */
-		goto free_and_return;
-	      else if (__nss_not_use_nscd_hosts == 0)
-		{
-		  if (h_errno == NETDB_INTERNAL && errno == ENOMEM)
-		    result = -EAI_MEMORY;
-		  else if (h_errno == TRY_AGAIN)
-		    result = -EAI_AGAIN;
-		  else
-		    result = -EAI_SYSTEM;
 
-		  goto free_and_return;
-		}
+	      no_inet6_data = no_data;
 	    }
-#endif
-
-	  no_more = !__nss_database_get (nss_database_hosts, &nip);
-
-	  /* If we are looking for both IPv4 and IPv6 address we don't
-	     want the lookup functions to automatically promote IPv4
-	     addresses to IPv6 addresses, so we use the no_inet6
-	     function variant.  */
-	  res_ctx = __resolv_context_get ();
-	  if (res_ctx == NULL)
-	    no_more = 1;
-
-	  while (!no_more)
+	  else
 	    {
-	      /* Always start afresh; continue should discard previous results
-		 and the hosts database does not support merge.  */
-	      at = NULL;
-	      free (canon);
-	      free (addrmem);
-	      canon = NULL;
-	      addrmem = NULL;
-	      got_ipv6 = false;
-
-	      if (do_merge)
+	      nss_gethostbyname3_r *fct = NULL;
+	      if (req->ai_flags & AI_CANONNAME)
+		/* No need to use this function if we do not look for
+		   the canonical name.  The function does not exist in
+		   all NSS modules and therefore the lookup would
+		   often fail.  */
+		fct = __nss_lookup_function (nip, "gethostbyname3_r");
+	      if (fct == NULL)
+		/* We are cheating here.  The gethostbyname2_r
+		   function does not have the same interface as
+		   gethostbyname3_r but the extra arguments the
+		   latter takes are added at the end.  So the
+		   gethostbyname2_r code will just ignore them.  */
+		fct = __nss_lookup_function (nip, "gethostbyname2_r");
+
+	      if (fct != NULL)
 		{
-		  __set_h_errno (NETDB_INTERNAL);
-		  __set_errno (EBUSY);
-		  break;
-		}
-
-	      no_data = 0;
-	      nss_gethostbyname4_r *fct4 = NULL;
-
-	      /* gethostbyname4_r sends out parallel A and AAAA queries and
-		 is thus only suitable for PF_UNSPEC.  */
-	      if (req->ai_family == PF_UNSPEC)
-		fct4 = __nss_lookup_function (nip, "gethostbyname4_r");
+		  struct gaih_addrtuple **pat = &res.at;
 
-	      if (fct4 != NULL)
-		{
-		  while (1)
+		  if (req->ai_family == AF_INET6
+		      || req->ai_family == AF_UNSPEC)
 		    {
-		      status = DL_CALL_FCT (fct4, (name, &at,
-						   tmpbuf->data, tmpbuf->length,
-						   &errno, &h_errno,
-						   NULL));
-		      if (status == NSS_STATUS_SUCCESS)
-			break;
-		      /* gethostbyname4_r may write into AT, so reset it.  */
-		      at = NULL;
-		      if (status != NSS_STATUS_TRYAGAIN
-			  || errno != ERANGE || h_errno != NETDB_INTERNAL)
-			{
-			  if (h_errno == TRY_AGAIN)
-			    no_data = EAI_AGAIN;
-			  else
-			    no_data = h_errno == NO_DATA;
-			  break;
-			}
+		      gethosts (AF_INET6);
+		      no_inet6_data = no_data;
+		      inet6_status = status;
+		    }
+		  if (req->ai_family == AF_INET
+		      || req->ai_family == AF_UNSPEC
+		      || (req->ai_family == AF_INET6
+			  && (req->ai_flags & AI_V4MAPPED)
+			  /* Avoid generating the mapped addresses if we
+			     know we are not going to need them.  */
+			  && ((req->ai_flags & AI_ALL) || !got_ipv6)))
+		    {
+		      gethosts (AF_INET);
 
-		      if (!scratch_buffer_grow (tmpbuf))
+		      if (req->ai_family == AF_INET)
 			{
-			  __resolv_context_put (res_ctx);
-			  result = -EAI_MEMORY;
-			  goto free_and_return;
+			  no_inet6_data = no_data;
+			  inet6_status = status;
 			}
 		    }
 
-		  if (status == NSS_STATUS_SUCCESS)
+		  /* If we found one address for AF_INET or AF_INET6,
+		     don't continue the search.  */
+		  if (inet6_status == NSS_STATUS_SUCCESS
+		      || status == NSS_STATUS_SUCCESS)
 		    {
-		      assert (!no_data);
-		      no_data = 1;
-
-		      if ((req->ai_flags & AI_CANONNAME) != 0 && canon == NULL)
+		      if ((req->ai_flags & AI_CANONNAME) != 0
+			  && res.canon == NULL)
 			{
-			  char *canonbuf = __strdup (at->name);
+			  char *canonbuf = getcanonname (nip, res.at, name);
 			  if (canonbuf == NULL)
 			    {
 			      __resolv_context_put (res_ctx);
 			      result = -EAI_MEMORY;
 			      goto free_and_return;
 			    }
-			  canon = canonbuf;
-			}
-
-		      struct gaih_addrtuple **pat = &at;
-
-		      while (*pat != NULL)
-			{
-			  if ((*pat)->family == AF_INET
-			      && req->ai_family == AF_INET6
-			      && (req->ai_flags & AI_V4MAPPED) != 0)
-			    {
-			      uint32_t *pataddr = (*pat)->addr;
-			      (*pat)->family = AF_INET6;
-			      pataddr[3] = pataddr[0];
-			      pataddr[2] = htonl (0xffff);
-			      pataddr[1] = 0;
-			      pataddr[0] = 0;
-			      pat = &((*pat)->next);
-			      no_data = 0;
-			    }
-			  else if (req->ai_family == AF_UNSPEC
-				   || (*pat)->family == req->ai_family)
-			    {
-			      pat = &((*pat)->next);
-
-			      no_data = 0;
-			      if (req->ai_family == AF_INET6)
-				got_ipv6 = true;
-			    }
-			  else
-			    *pat = ((*pat)->next);
-			}
-		    }
-
-		  no_inet6_data = no_data;
-		}
-	      else
-		{
-		  nss_gethostbyname3_r *fct = NULL;
-		  if (req->ai_flags & AI_CANONNAME)
-		    /* No need to use this function if we do not look for
-		       the canonical name.  The function does not exist in
-		       all NSS modules and therefore the lookup would
-		       often fail.  */
-		    fct = __nss_lookup_function (nip, "gethostbyname3_r");
-		  if (fct == NULL)
-		    /* We are cheating here.  The gethostbyname2_r
-		       function does not have the same interface as
-		       gethostbyname3_r but the extra arguments the
-		       latter takes are added at the end.  So the
-		       gethostbyname2_r code will just ignore them.  */
-		    fct = __nss_lookup_function (nip, "gethostbyname2_r");
-
-		  if (fct != NULL)
-		    {
-		      struct gaih_addrtuple **pat = &at;
-
-		      if (req->ai_family == AF_INET6
-			  || req->ai_family == AF_UNSPEC)
-			{
-			  gethosts (AF_INET6);
-			  no_inet6_data = no_data;
-			  inet6_status = status;
-			}
-		      if (req->ai_family == AF_INET
-			  || req->ai_family == AF_UNSPEC
-			  || (req->ai_family == AF_INET6
-			      && (req->ai_flags & AI_V4MAPPED)
-			      /* Avoid generating the mapped addresses if we
-				 know we are not going to need them.  */
-			      && ((req->ai_flags & AI_ALL) || !got_ipv6)))
-			{
-			  gethosts (AF_INET);
-
-			  if (req->ai_family == AF_INET)
-			    {
-			      no_inet6_data = no_data;
-			      inet6_status = status;
-			    }
-			}
-
-		      /* If we found one address for AF_INET or AF_INET6,
-			 don't continue the search.  */
-		      if (inet6_status == NSS_STATUS_SUCCESS
-			  || status == NSS_STATUS_SUCCESS)
-			{
-			  if ((req->ai_flags & AI_CANONNAME) != 0
-			      && canon == NULL)
-			    {
-			      char *canonbuf = getcanonname (nip, at, name);
-			      if (canonbuf == NULL)
-				{
-				  __resolv_context_put (res_ctx);
-				  result = -EAI_MEMORY;
-				  goto free_and_return;
-				}
-			      canon = canonbuf;
-			    }
-			  status = NSS_STATUS_SUCCESS;
-			}
-		      else
-			{
-			  /* We can have different states for AF_INET and
-			     AF_INET6.  Try to find a useful one for both.  */
-			  if (inet6_status == NSS_STATUS_TRYAGAIN)
-			    status = NSS_STATUS_TRYAGAIN;
-			  else if (status == NSS_STATUS_UNAVAIL
-				   && inet6_status != NSS_STATUS_UNAVAIL)
-			    status = inet6_status;
+			  res.canon = canonbuf;
 			}
+		      status = NSS_STATUS_SUCCESS;
 		    }
 		  else
 		    {
-		      /* Could not locate any of the lookup functions.
-			 The NSS lookup code does not consistently set
-			 errno, so we need to supply our own error
-			 code here.  The root cause could either be a
-			 resource allocation failure, or a missing
-			 service function in the DSO (so it should not
-			 be listed in /etc/nsswitch.conf).  Assume the
-			 former, and return EBUSY.  */
-		      status = NSS_STATUS_UNAVAIL;
-		     __set_h_errno (NETDB_INTERNAL);
-		     __set_errno (EBUSY);
+		      /* We can have different states for AF_INET and
+			 AF_INET6.  Try to find a useful one for both.  */
+		      if (inet6_status == NSS_STATUS_TRYAGAIN)
+			status = NSS_STATUS_TRYAGAIN;
+		      else if (status == NSS_STATUS_UNAVAIL
+			       && inet6_status != NSS_STATUS_UNAVAIL)
+			status = inet6_status;
 		    }
 		}
+	      else
+		{
+		  /* Could not locate any of the lookup functions.
+		     The NSS lookup code does not consistently set
+		     errno, so we need to supply our own error
+		     code here.  The root cause could either be a
+		     resource allocation failure, or a missing
+		     service function in the DSO (so it should not
+		     be listed in /etc/nsswitch.conf).  Assume the
+		     former, and return EBUSY.  */
+		  status = NSS_STATUS_UNAVAIL;
+		  __set_h_errno (NETDB_INTERNAL);
+		  __set_errno (EBUSY);
+		}
+	    }
 
-	      if (nss_next_action (nip, status) == NSS_ACTION_RETURN)
-		break;
+	  if (nss_next_action (nip, status) == NSS_ACTION_RETURN)
+	    break;
 
-	      /* The hosts database does not support MERGE.  */
-	      if (nss_next_action (nip, status) == NSS_ACTION_MERGE)
-		do_merge = true;
+	  /* The hosts database does not support MERGE.  */
+	  if (nss_next_action (nip, status) == NSS_ACTION_MERGE)
+	    do_merge = true;
 
-	      nip++;
-	      if (nip->module == NULL)
-		no_more = -1;
-	    }
+	  nip++;
+	  if (nip->module == NULL)
+	    no_more = -1;
+	}
 
-	  __resolv_context_put (res_ctx);
+      __resolv_context_put (res_ctx);
 
-	  /* If we have a failure which sets errno, report it using
-	     EAI_SYSTEM.  */
-	  if ((status == NSS_STATUS_TRYAGAIN || status == NSS_STATUS_UNAVAIL)
-	      && h_errno == NETDB_INTERNAL)
-	    {
-	      result = -EAI_SYSTEM;
-	      goto free_and_return;
-	    }
+      /* If we have a failure which sets errno, report it using
+	 EAI_SYSTEM.  */
+      if ((status == NSS_STATUS_TRYAGAIN || status == NSS_STATUS_UNAVAIL)
+	  && h_errno == NETDB_INTERNAL)
+	{
+	  result = -EAI_SYSTEM;
+	  goto free_and_return;
+	}
 
-	  if (no_data != 0 && no_inet6_data != 0)
-	    {
-	      /* If both requests timed out report this.  */
-	      if (no_data == EAI_AGAIN && no_inet6_data == EAI_AGAIN)
-		result = -EAI_AGAIN;
-	      else
-		/* We made requests but they turned out no data.  The name
-		   is known, though.  */
-		result = -EAI_NODATA;
+      if (no_data != 0 && no_inet6_data != 0)
+	{
+	  /* If both requests timed out report this.  */
+	  if (no_data == EAI_AGAIN && no_inet6_data == EAI_AGAIN)
+	    result = -EAI_AGAIN;
+	  else
+	    /* We made requests but they turned out no data.  The name
+	       is known, though.  */
+	    result = -EAI_NODATA;
 
-	      goto free_and_return;
-	    }
+	  goto free_and_return;
 	}
 
     process_list:
-      if (at == NULL)
+      if (res.at == NULL)
 	{
 	  result = -EAI_NONAME;
 	  goto free_and_return;
@@ -1032,21 +1044,22 @@ gaih_inet (const char *name, const struct gaih_service *service,
   else
     {
       struct gaih_addrtuple *atr;
-      atr = at = alloca_account (sizeof (struct gaih_addrtuple), alloca_used);
-      memset (at, '\0', sizeof (struct gaih_addrtuple));
+      atr = res.at = alloca_account (sizeof (struct gaih_addrtuple),
+				     alloca_used);
+      memset (res.at, '\0', sizeof (struct gaih_addrtuple));
 
       if (req->ai_family == AF_UNSPEC)
 	{
-	  at->next = __alloca (sizeof (struct gaih_addrtuple));
-	  memset (at->next, '\0', sizeof (struct gaih_addrtuple));
+	  res.at->next = __alloca (sizeof (struct gaih_addrtuple));
+	  memset (res.at->next, '\0', sizeof (struct gaih_addrtuple));
 	}
 
       if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET6)
 	{
-	  at->family = AF_INET6;
+	  res.at->family = AF_INET6;
 	  if ((req->ai_flags & AI_PASSIVE) == 0)
-	    memcpy (at->addr, &in6addr_loopback, sizeof (struct in6_addr));
-	  atr = at->next;
+	    memcpy (res.at->addr, &in6addr_loopback, sizeof (struct in6_addr));
+	  atr = res.at->next;
 	}
 
       if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET)
@@ -1059,10 +1072,10 @@ gaih_inet (const char *name, const struct gaih_service *service,
 
   {
     /* Set up the canonical name if we need it.  */
-    if ((result = process_canonname (req, orig_name, &canon)) != 0)
+    if ((result = process_canonname (req, orig_name, &res)) != 0)
       goto free_and_return;
 
-    struct gaih_addrtuple *at2 = at;
+    struct gaih_addrtuple *at2 = res.at;
     size_t socklen;
     sa_family_t family;
 
@@ -1105,8 +1118,8 @@ gaih_inet (const char *name, const struct gaih_service *service,
 	    ai->ai_addr = (void *) (ai + 1);
 
 	    /* We only add the canonical name once.  */
-	    ai->ai_canonname = (char *) canon;
-	    canon = NULL;
+	    ai->ai_canonname = res.canon;
+	    res.canon = NULL;
 
 #ifdef _HAVE_SA_LEN
 	    ai->ai_addr->sa_len = socklen;
@@ -1152,7 +1165,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
   if (malloc_name)
     free ((char *) name);
   free (addrmem);
-  free (canon);
+  free (res.canon);
 
   return result;
 }
-- 
2.39.3
