Blame resolv/resolv_conf.c

Packit 6c4009
/* Extended resolver state separate from struct __res_state.
Packit 6c4009
   Copyright (C) 2017-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <resolv_conf.h>
Packit 6c4009
Packit 6c4009
#include <alloc_buffer.h>
Packit 6c4009
#include <assert.h>
Packit 6c4009
#include <libc-lock.h>
Packit 6c4009
#include <resolv-internal.h>
Packit 6c4009
#include <sys/stat.h>
Packit 6c4009
#include <libc-symbols.h>
Packit Service 46b50c
#include <file_change_detection.h>
Packit 6c4009
Packit 6c4009
/* _res._u._ext.__glibc_extension_index is used as an index into a
Packit 6c4009
   struct resolv_conf_array object.  The intent of this construction
Packit 6c4009
   is to make reasonably sure that even if struct __res_state objects
Packit 6c4009
   are copied around and patched by applications, we can still detect
Packit 6c4009
   accesses to stale extended resolver state.  The array elements are
Packit 6c4009
   either struct resolv_conf * pointers (if the LSB is cleared) or
Packit 6c4009
   free list entries (if the LSB is set).  The free list is used to
Packit 6c4009
   speed up finding available entries in the array.  */
Packit 6c4009
#define DYNARRAY_STRUCT resolv_conf_array
Packit 6c4009
#define DYNARRAY_ELEMENT uintptr_t
Packit 6c4009
#define DYNARRAY_PREFIX resolv_conf_array_
Packit 6c4009
#define DYNARRAY_INITIAL_SIZE 0
Packit 6c4009
#include <malloc/dynarray-skeleton.c>
Packit 6c4009
Packit 6c4009
/* A magic constant for XORing the extension index
Packit 6c4009
   (_res._u._ext.__glibc_extension_index).  This makes it less likely
Packit 6c4009
   that a valid index is created by accident.  In particular, a zero
Packit 6c4009
   value leads to an invalid index.  */
Packit 6c4009
#define INDEX_MAGIC 0x26a8fa5e48af8061ULL
Packit 6c4009
Packit 6c4009
/* Global resolv.conf-related state.  */
Packit 6c4009
struct resolv_conf_global
Packit 6c4009
{
Packit 6c4009
  /* struct __res_state objects contain the extension index
Packit 6c4009
     (_res._u._ext.__glibc_extension_index ^ INDEX_MAGIC), which
Packit 6c4009
     refers to an element of this array.  When a struct resolv_conf
Packit 6c4009
     object (extended resolver state) is associated with a struct
Packit 6c4009
     __res_state object (legacy resolver state), its reference count
Packit 6c4009
     is increased and added to this array.  Conversely, if the
Packit 6c4009
     extended state is detached from the basic state (during
Packit 6c4009
     reinitialization or deallocation), the index is decremented, and
Packit 6c4009
     the array element is overwritten with NULL.  */
Packit 6c4009
  struct resolv_conf_array array;
Packit 6c4009
Packit 6c4009
  /* Start of the free list in the array.  Zero if the free list is
Packit 6c4009
     empty.  Otherwise, free_list_start >> 1 is the first element of
Packit 6c4009
     the free list (and the free list entries all have their LSB set
Packit 6c4009
     and are shifted one to the left).  */
Packit 6c4009
  uintptr_t free_list_start;
Packit 6c4009
Packit 6c4009
  /* Cached current configuration object for /etc/resolv.conf.  */
Packit 6c4009
  struct resolv_conf *conf_current;
Packit 6c4009
Packit Service 46b50c
  /* File system identification for /etc/resolv.conf.  */
Packit Service 46b50c
  struct file_change_detection file_resolve_conf;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
/* Lazily allocated storage for struct resolv_conf_global.  */
Packit 6c4009
static struct resolv_conf_global *global;
Packit 6c4009
Packit 6c4009
/* The lock synchronizes access to global and *global.  It also
Packit 6c4009
   protects the __refcount member of struct resolv_conf.  */
Packit 6c4009
__libc_lock_define_initialized (static, lock);
Packit 6c4009
Packit 6c4009
/* Ensure that GLOBAL is allocated and lock it.  Return NULL if
Packit 6c4009
   memory allocation failes.  */
Packit 6c4009
static struct resolv_conf_global *
Packit 6c4009
get_locked_global (void)
Packit 6c4009
{
Packit 6c4009
  __libc_lock_lock (lock);
Packit 6c4009
  /* Use relaxed MO through because of load outside the lock in
Packit 6c4009
     __resolv_conf_detach.  */
Packit 6c4009
  struct resolv_conf_global *global_copy = atomic_load_relaxed (&global);
Packit 6c4009
  if (global_copy == NULL)
Packit 6c4009
    {
Packit 6c4009
      global_copy = calloc (1, sizeof (*global));
Packit 6c4009
      if (global_copy == NULL)
Packit 6c4009
        return NULL;
Packit 6c4009
      atomic_store_relaxed (&global, global_copy);
Packit 6c4009
      resolv_conf_array_init (&global_copy->array);
Packit 6c4009
    }
Packit 6c4009
  return global_copy;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Relinquish the lock acquired by get_locked_global.  */
Packit 6c4009
static void
Packit 6c4009
put_locked_global (struct resolv_conf_global *global_copy)
Packit 6c4009
{
Packit 6c4009
  __libc_lock_unlock (lock);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Decrement the reference counter.  The caller must acquire the lock
Packit 6c4009
   around the function call.  */
Packit 6c4009
static void
Packit 6c4009
conf_decrement (struct resolv_conf *conf)
Packit 6c4009
{
Packit 6c4009
  assert (conf->__refcount > 0);
Packit 6c4009
  if (--conf->__refcount == 0)
Packit 6c4009
    free (conf);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
struct resolv_conf *
Packit 6c4009
__resolv_conf_get_current (void)
Packit 6c4009
{
Packit Service 46b50c
  struct file_change_detection initial;
Packit Service 9f560c
  if (!__file_change_detection_for_path (&initial, _PATH_RESCONF))
Packit Service 46b50c
    return NULL;
Packit 6c4009
Packit 6c4009
  struct resolv_conf_global *global_copy = get_locked_global ();
Packit 6c4009
  if (global_copy == NULL)
Packit 6c4009
    return NULL;
Packit 6c4009
  struct resolv_conf *conf;
Packit 6c4009
  if (global_copy->conf_current != NULL
Packit Service 9f560c
      && __file_is_unchanged (&initial, &global_copy->file_resolve_conf))
Packit 6c4009
    /* We can reuse the cached configuration object.  */
Packit 6c4009
    conf = global_copy->conf_current;
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      /* Parse configuration while holding the lock.  This avoids
Packit 6c4009
         duplicate work.  */
Packit Service 7d9acb
      struct file_change_detection after_load;
Packit Service 7d9acb
      conf = __resolv_conf_load (NULL, &after_load);
Packit 6c4009
      if (conf != NULL)
Packit 6c4009
        {
Packit 6c4009
          if (global_copy->conf_current != NULL)
Packit 6c4009
            conf_decrement (global_copy->conf_current);
Packit 6c4009
          global_copy->conf_current = conf; /* Takes ownership.  */
Packit 6c4009
Packit Service 7d9acb
          /* Update file change detection data, but only if it matches
Packit Service 7d9acb
             the initial measurement.  This avoids an ABA race in case
Packit Service 7d9acb
             /etc/resolv.conf is temporarily replaced while the file
Packit Service 7d9acb
             is read (after the initial measurement), and restored to
Packit Service 7d9acb
             the initial version later.  */
Packit Service 9f560c
          if (__file_is_unchanged (&initial, &after_load))
Packit Service 7d9acb
            global_copy->file_resolve_conf = after_load;
Packit Service 7d9acb
          else
Packit Service 7d9acb
            /* If there is a discrepancy, trigger a reload during the
Packit Service 7d9acb
               next use.  */
Packit Service 7d9acb
            global_copy->file_resolve_conf.size = -1;
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  if (conf != NULL)
Packit 6c4009
    {
Packit 6c4009
      /* Return an additional reference.  */
Packit 6c4009
      assert (conf->__refcount > 0);
Packit 6c4009
      ++conf->__refcount;
Packit 6c4009
      assert (conf->__refcount > 0);
Packit 6c4009
    }
Packit 6c4009
  put_locked_global (global_copy);
Packit 6c4009
  return conf;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Internal implementation of __resolv_conf_get, without validation
Packit 6c4009
   against *RESP.  */
Packit 6c4009
static struct resolv_conf *
Packit 6c4009
resolv_conf_get_1 (const struct __res_state *resp)
Packit 6c4009
{
Packit 6c4009
  /* Not initialized, and therefore no assoicated context.  */
Packit 6c4009
  if (!(resp->options & RES_INIT))
Packit 6c4009
    return NULL;
Packit 6c4009
Packit 6c4009
  struct resolv_conf_global *global_copy = get_locked_global ();
Packit 6c4009
  if (global_copy == NULL)
Packit 6c4009
    /* A memory allocation failure here means that no associated
Packit 6c4009
       contexts exists, so returning NULL is correct.  */
Packit 6c4009
    return NULL;
Packit 6c4009
  size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
Packit 6c4009
  struct resolv_conf *conf = NULL;
Packit 6c4009
  if (index < resolv_conf_array_size (&global_copy->array))
Packit 6c4009
    {
Packit 6c4009
      uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
Packit 6c4009
      if (!(*slot & 1))
Packit 6c4009
        {
Packit 6c4009
          conf = (struct resolv_conf *) *slot;
Packit 6c4009
          assert (conf->__refcount > 0);
Packit 6c4009
          ++conf->__refcount;
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
  put_locked_global (global_copy);
Packit 6c4009
  return conf;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Return true if both IPv4 addresses are equal.  */
Packit 6c4009
static bool
Packit 6c4009
same_address_v4 (const struct sockaddr_in *left,
Packit 6c4009
                 const struct sockaddr_in *right)
Packit 6c4009
{
Packit 6c4009
  return left->sin_addr.s_addr == right->sin_addr.s_addr
Packit 6c4009
    && left->sin_port == right->sin_port;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Return true if both IPv6 addresses are equal.  This ignores the
Packit 6c4009
   flow label.  */
Packit 6c4009
static bool
Packit 6c4009
same_address_v6 (const struct sockaddr_in6 *left,
Packit 6c4009
                 const struct sockaddr_in6 *right)
Packit 6c4009
{
Packit 6c4009
  return memcmp (&left->sin6_addr, &right->sin6_addr,
Packit 6c4009
                 sizeof (left->sin6_addr)) == 0
Packit 6c4009
    && left->sin6_port == right->sin6_port
Packit 6c4009
    && left->sin6_scope_id == right->sin6_scope_id;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static bool
Packit 6c4009
same_address (const struct sockaddr *left, const struct sockaddr *right)
Packit 6c4009
{
Packit 6c4009
  if (left->sa_family != right->sa_family)
Packit 6c4009
    return false;
Packit 6c4009
  switch (left->sa_family)
Packit 6c4009
    {
Packit 6c4009
    case AF_INET:
Packit 6c4009
      return same_address_v4 ((const struct sockaddr_in *) left,
Packit 6c4009
                              (const struct sockaddr_in *) right);
Packit 6c4009
    case AF_INET6:
Packit 6c4009
      return same_address_v6 ((const struct sockaddr_in6 *) left,
Packit 6c4009
                              (const struct sockaddr_in6 *) right);
Packit 6c4009
    }
Packit 6c4009
  return false;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Check that *RESP and CONF match.  Used by __resolv_conf_get.  */
Packit 6c4009
static bool
Packit 6c4009
resolv_conf_matches (const struct __res_state *resp,
Packit 6c4009
                     const struct resolv_conf *conf)
Packit 6c4009
{
Packit 6c4009
  /* NB: Do not compare the options, retrans, retry, ndots.  These can
Packit 6c4009
     be changed by applicaiton.  */
Packit 6c4009
Packit 6c4009
  /* Check that the name servers in *RESP have not been modified by
Packit 6c4009
     the application.  */
Packit 6c4009
  {
Packit 6c4009
    size_t nserv = conf->nameserver_list_size;
Packit 6c4009
    if (nserv > MAXNS)
Packit 6c4009
      nserv = MAXNS;
Packit 6c4009
    /* _ext.nscount is 0 until initialized by res_send.c.  */
Packit 6c4009
    if (resp->nscount != nserv
Packit 6c4009
        || (resp->_u._ext.nscount != 0 && resp->_u._ext.nscount != nserv))
Packit 6c4009
      return false;
Packit 6c4009
    for (size_t i = 0; i < nserv; ++i)
Packit 6c4009
      {
Packit 6c4009
        if (resp->nsaddr_list[i].sin_family == 0)
Packit 6c4009
          {
Packit 6c4009
            if (resp->_u._ext.nsaddrs[i]->sin6_family != AF_INET6)
Packit 6c4009
              return false;
Packit 6c4009
            if (!same_address ((struct sockaddr *) resp->_u._ext.nsaddrs[i],
Packit 6c4009
                               conf->nameserver_list[i]))
Packit 6c4009
              return false;
Packit 6c4009
          }
Packit 6c4009
        else if (resp->nsaddr_list[i].sin_family != AF_INET)
Packit 6c4009
          return false;
Packit 6c4009
        else if (!same_address ((struct sockaddr *) &resp->nsaddr_list[i],
Packit 6c4009
                                conf->nameserver_list[i]))
Packit 6c4009
          return false;
Packit 6c4009
      }
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* Check that the search list in *RESP has not been modified by the
Packit 6c4009
     application.  */
Packit 6c4009
  {
Packit 6c4009
    if (resp->dnsrch[0] == NULL)
Packit 6c4009
      {
Packit 6c4009
        /* Empty search list.  No default domain name.  */
Packit 6c4009
        return conf->search_list_size == 0 && resp->defdname[0] == '\0';
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
    if (resp->dnsrch[0] != resp->defdname)
Packit 6c4009
      /* If the search list is not empty, it must start with the
Packit 6c4009
         default domain name.  */
Packit 6c4009
      return false;
Packit 6c4009
Packit 6c4009
    size_t nsearch;
Packit 6c4009
    for (nsearch = 0; nsearch < MAXDNSRCH; ++nsearch)
Packit 6c4009
      if (resp->dnsrch[nsearch] == NULL)
Packit 6c4009
        break;
Packit 6c4009
    if (nsearch > MAXDNSRCH)
Packit 6c4009
      /* Search list is not null-terminated.  */
Packit 6c4009
      return false;
Packit 6c4009
Packit 6c4009
    size_t search_list_size = 0;
Packit 6c4009
    for (size_t i = 0; i < conf->search_list_size; ++i)
Packit 6c4009
      {
Packit 6c4009
        if (resp->dnsrch[i] != NULL)
Packit 6c4009
          {
Packit 6c4009
            search_list_size += strlen (resp->dnsrch[i]) + 1;
Packit 6c4009
            if (strcmp (resp->dnsrch[i], conf->search_list[i]) != 0)
Packit 6c4009
              return false;
Packit 6c4009
          }
Packit 6c4009
        else
Packit 6c4009
          {
Packit 6c4009
            /* resp->dnsrch is truncated if the number of elements
Packit 6c4009
               exceeds MAXDNSRCH, or if the combined storage space for
Packit 6c4009
               the search list exceeds what can be stored in
Packit 6c4009
               resp->defdname.  */
Packit 6c4009
            if (i == MAXDNSRCH || search_list_size > sizeof (resp->dnsrch))
Packit 6c4009
              break;
Packit 6c4009
            /* Otherwise, a mismatch indicates a match failure.  */
Packit 6c4009
            return false;
Packit 6c4009
          }
Packit 6c4009
      }
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* Check that the sort list has not been modified.  */
Packit 6c4009
  {
Packit 6c4009
    size_t nsort = conf->sort_list_size;
Packit 6c4009
    if (nsort > MAXRESOLVSORT)
Packit 6c4009
      nsort = MAXRESOLVSORT;
Packit 6c4009
    if (resp->nsort != nsort)
Packit 6c4009
      return false;
Packit 6c4009
    for (size_t i = 0; i < nsort; ++i)
Packit 6c4009
      if (resp->sort_list[i].addr.s_addr != conf->sort_list[i].addr.s_addr
Packit 6c4009
          || resp->sort_list[i].mask != conf->sort_list[i].mask)
Packit 6c4009
        return false;
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  return true;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
struct resolv_conf *
Packit 6c4009
__resolv_conf_get (struct __res_state *resp)
Packit 6c4009
{
Packit 6c4009
  struct resolv_conf *conf = resolv_conf_get_1 (resp);
Packit 6c4009
  if (conf == NULL)
Packit 6c4009
    return NULL;
Packit 6c4009
  if (resolv_conf_matches (resp, conf))
Packit 6c4009
    return conf;
Packit 6c4009
  __resolv_conf_put (conf);
Packit 6c4009
  return NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
__resolv_conf_put (struct resolv_conf *conf)
Packit 6c4009
{
Packit 6c4009
  if (conf == NULL)
Packit 6c4009
    return;
Packit 6c4009
Packit 6c4009
  __libc_lock_lock (lock);
Packit 6c4009
  conf_decrement (conf);
Packit 6c4009
  __libc_lock_unlock (lock);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
struct resolv_conf *
Packit 6c4009
__resolv_conf_allocate (const struct resolv_conf *init)
Packit 6c4009
{
Packit 6c4009
  /* Allocate in decreasing order of alignment.  */
Packit 6c4009
  _Static_assert (__alignof__ (const char *const *)
Packit 6c4009
                  <= __alignof__ (struct resolv_conf), "alignment");
Packit 6c4009
  _Static_assert (__alignof__ (struct sockaddr_in6)
Packit 6c4009
                  <= __alignof__ (const char *const *), "alignment");
Packit 6c4009
  _Static_assert (__alignof__ (struct sockaddr_in)
Packit 6c4009
                  ==  __alignof__ (struct sockaddr_in6), "alignment");
Packit 6c4009
  _Static_assert (__alignof__ (struct resolv_sortlist_entry)
Packit 6c4009
                  <= __alignof__ (struct sockaddr_in), "alignment");
Packit 6c4009
Packit 6c4009
  /* Space needed by the nameserver addresses.  */
Packit 6c4009
  size_t address_space = 0;
Packit 6c4009
  for (size_t i = 0; i < init->nameserver_list_size; ++i)
Packit 6c4009
    if (init->nameserver_list[i]->sa_family == AF_INET)
Packit 6c4009
      address_space += sizeof (struct sockaddr_in);
Packit 6c4009
    else
Packit 6c4009
      {
Packit 6c4009
        assert (init->nameserver_list[i]->sa_family == AF_INET6);
Packit 6c4009
        address_space += sizeof (struct sockaddr_in6);
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
  /* Space needed by the search list strings.  */
Packit 6c4009
  size_t string_space = 0;
Packit 6c4009
  for (size_t i = 0; i < init->search_list_size; ++i)
Packit 6c4009
    string_space += strlen (init->search_list[i]) + 1;
Packit 6c4009
Packit 6c4009
  /* Allocate the buffer.  */
Packit 6c4009
  void *ptr;
Packit 6c4009
  struct alloc_buffer buffer = alloc_buffer_allocate
Packit 6c4009
    (sizeof (struct resolv_conf)
Packit 6c4009
     + init->nameserver_list_size * sizeof (init->nameserver_list[0])
Packit 6c4009
     + address_space
Packit 6c4009
     + init->search_list_size * sizeof (init->search_list[0])
Packit 6c4009
     + init->sort_list_size * sizeof (init->sort_list[0])
Packit 6c4009
     + string_space,
Packit 6c4009
     &ptr);
Packit 6c4009
  struct resolv_conf *conf
Packit 6c4009
    = alloc_buffer_alloc (&buffer, struct resolv_conf);
Packit 6c4009
  if (conf == NULL)
Packit 6c4009
    /* Memory allocation failure.  */
Packit 6c4009
    return NULL;
Packit 6c4009
  assert (conf == ptr);
Packit 6c4009
Packit 6c4009
  /* Initialize the contents.  */
Packit 6c4009
  conf->__refcount = 1;
Packit 6c4009
  conf->retrans = init->retrans;
Packit 6c4009
  conf->retry = init->retry;
Packit 6c4009
  conf->options = init->options;
Packit 6c4009
  conf->ndots = init->ndots;
Packit 6c4009
Packit 6c4009
  /* Allocate the arrays with pointers.  These must come first because
Packit 6c4009
     they have the highets alignment.  */
Packit 6c4009
  conf->nameserver_list_size = init->nameserver_list_size;
Packit 6c4009
  const struct sockaddr **nameserver_array = alloc_buffer_alloc_array
Packit 6c4009
    (&buffer, const struct sockaddr *, init->nameserver_list_size);
Packit 6c4009
  conf->nameserver_list = nameserver_array;
Packit 6c4009
Packit 6c4009
  conf->search_list_size = init->search_list_size;
Packit 6c4009
  const char **search_array = alloc_buffer_alloc_array
Packit 6c4009
    (&buffer, const char *, init->search_list_size);
Packit 6c4009
  conf->search_list = search_array;
Packit 6c4009
Packit 6c4009
  /* Fill the name server list array.  */
Packit 6c4009
  for (size_t i = 0; i < init->nameserver_list_size; ++i)
Packit 6c4009
    if (init->nameserver_list[i]->sa_family == AF_INET)
Packit 6c4009
      {
Packit 6c4009
        struct sockaddr_in *sa = alloc_buffer_alloc
Packit 6c4009
          (&buffer, struct sockaddr_in);
Packit 6c4009
        *sa = *(struct sockaddr_in *) init->nameserver_list[i];
Packit 6c4009
        nameserver_array[i] = (struct sockaddr *) sa;
Packit 6c4009
      }
Packit 6c4009
    else
Packit 6c4009
      {
Packit 6c4009
        struct sockaddr_in6 *sa = alloc_buffer_alloc
Packit 6c4009
          (&buffer, struct sockaddr_in6);
Packit 6c4009
        *sa = *(struct sockaddr_in6 *) init->nameserver_list[i];
Packit 6c4009
        nameserver_array[i] = (struct sockaddr *) sa;
Packit 6c4009
      }
Packit 6c4009
Packit 6c4009
  /* Allocate and fill the sort list array.  */
Packit 6c4009
  {
Packit 6c4009
    conf->sort_list_size = init->sort_list_size;
Packit 6c4009
    struct resolv_sortlist_entry *array = alloc_buffer_alloc_array
Packit 6c4009
      (&buffer, struct resolv_sortlist_entry, init->sort_list_size);
Packit 6c4009
    conf->sort_list = array;
Packit 6c4009
    for (size_t i = 0; i < init->sort_list_size; ++i)
Packit 6c4009
      array[i] = init->sort_list[i];
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* Fill the search list array.  This must come last because the
Packit 6c4009
     strings are the least aligned part of the allocation.  */
Packit 6c4009
  {
Packit 6c4009
    for (size_t i = 0; i < init->search_list_size; ++i)
Packit 6c4009
      search_array[i] = alloc_buffer_copy_string
Packit 6c4009
        (&buffer, init->search_list[i]);
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  assert (!alloc_buffer_has_failed (&buffer));
Packit 6c4009
  return conf;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Update *RESP from the extended state.  */
Packit 6c4009
static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
Packit 6c4009
update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
Packit 6c4009
{
Packit 6c4009
  resp->defdname[0] = '\0';
Packit 6c4009
  resp->pfcode = 0;
Packit 6c4009
  resp->_vcsock = -1;
Packit 6c4009
  resp->_flags = 0;
Packit 6c4009
  resp->ipv6_unavail = false;
Packit 6c4009
  resp->__glibc_unused_qhook = NULL;
Packit 6c4009
  resp->__glibc_unused_rhook = NULL;
Packit 6c4009
Packit 6c4009
  resp->retrans = conf->retrans;
Packit 6c4009
  resp->retry = conf->retry;
Packit 6c4009
  resp->options = conf->options;
Packit 6c4009
  resp->ndots = conf->ndots;
Packit 6c4009
Packit 6c4009
  /* Copy the name server addresses.  */
Packit 6c4009
  {
Packit 6c4009
    resp->nscount = 0;
Packit 6c4009
    resp->_u._ext.nscount = 0;
Packit 6c4009
    size_t nserv = conf->nameserver_list_size;
Packit 6c4009
    if (nserv > MAXNS)
Packit 6c4009
      nserv = MAXNS;
Packit 6c4009
    for (size_t i = 0; i < nserv; i++)
Packit 6c4009
      {
Packit 6c4009
        if (conf->nameserver_list[i]->sa_family == AF_INET)
Packit 6c4009
          {
Packit 6c4009
            resp->nsaddr_list[i]
Packit 6c4009
              = *(struct sockaddr_in *)conf->nameserver_list[i];
Packit 6c4009
            resp->_u._ext.nsaddrs[i] = NULL;
Packit 6c4009
          }
Packit 6c4009
        else
Packit 6c4009
          {
Packit 6c4009
            assert (conf->nameserver_list[i]->sa_family == AF_INET6);
Packit 6c4009
            resp->nsaddr_list[i].sin_family = 0;
Packit 6c4009
            /* Make a defensive copy of the name server address, in
Packit 6c4009
               case the application overwrites it.  */
Packit 6c4009
            struct sockaddr_in6 *sa = malloc (sizeof (*sa));
Packit 6c4009
            if (sa == NULL)
Packit 6c4009
              {
Packit 6c4009
                for (size_t j = 0; j < i; ++j)
Packit 6c4009
                  free (resp->_u._ext.nsaddrs[j]);
Packit 6c4009
                return false;
Packit 6c4009
              }
Packit 6c4009
            *sa = *(struct sockaddr_in6 *)conf->nameserver_list[i];
Packit 6c4009
            resp->_u._ext.nsaddrs[i] = sa;
Packit 6c4009
          }
Packit 6c4009
        resp->_u._ext.nssocks[i] = -1;
Packit 6c4009
      }
Packit 6c4009
    resp->nscount = nserv;
Packit 6c4009
    /* Leave resp->_u._ext.nscount at 0.  res_send.c handles this.  */
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* Fill in the prefix of the search list.  It is truncated either at
Packit 6c4009
     MAXDNSRCH, or if reps->defdname has insufficient space.  */
Packit 6c4009
  {
Packit 6c4009
    struct alloc_buffer buffer
Packit 6c4009
      = alloc_buffer_create (resp->defdname, sizeof (resp->defdname));
Packit 6c4009
    size_t size = conf->search_list_size;
Packit 6c4009
    size_t i;
Packit 6c4009
    for (i = 0; i < size && i < MAXDNSRCH; ++i)
Packit 6c4009
      {
Packit 6c4009
        resp->dnsrch[i] = alloc_buffer_copy_string
Packit 6c4009
          (&buffer, conf->search_list[i]);
Packit 6c4009
        if (resp->dnsrch[i] == NULL)
Packit 6c4009
          /* No more space in resp->defdname.  Truncate.  */
Packit 6c4009
          break;
Packit 6c4009
      }
Packit 6c4009
    resp->dnsrch[i] = NULL;
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* Copy the sort list.  */
Packit 6c4009
  {
Packit 6c4009
    size_t nsort = conf->sort_list_size;
Packit 6c4009
    if (nsort > MAXRESOLVSORT)
Packit 6c4009
      nsort = MAXRESOLVSORT;
Packit 6c4009
    for (size_t i = 0; i < nsort; ++i)
Packit 6c4009
      {
Packit 6c4009
        resp->sort_list[i].addr = conf->sort_list[i].addr;
Packit 6c4009
        resp->sort_list[i].mask = conf->sort_list[i].mask;
Packit 6c4009
      }
Packit 6c4009
    resp->nsort = nsort;
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* The overlapping parts of both configurations should agree after
Packit 6c4009
     initialization.  */
Packit 6c4009
  assert (resolv_conf_matches (resp, conf));
Packit 6c4009
  return true;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Decrement the configuration object at INDEX and free it if the
Packit 6c4009
   reference counter reaches 0.  *GLOBAL_COPY must be locked and
Packit 6c4009
   remains so.  */
Packit 6c4009
static void
Packit 6c4009
decrement_at_index (struct resolv_conf_global *global_copy, size_t index)
Packit 6c4009
{
Packit 6c4009
  if (index < resolv_conf_array_size (&global_copy->array))
Packit 6c4009
    {
Packit 6c4009
      /* Index found.  */
Packit 6c4009
      uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
Packit 6c4009
      /* Check that the slot is not already part of the free list.  */
Packit 6c4009
      if (!(*slot & 1))
Packit 6c4009
        {
Packit 6c4009
          struct resolv_conf *conf = (struct resolv_conf *) *slot;
Packit 6c4009
          conf_decrement (conf);
Packit 6c4009
          /* Put the slot onto the free list.  */
Packit 6c4009
          *slot = global_copy->free_list_start;
Packit 6c4009
          global_copy->free_list_start = (index << 1) | 1;
Packit 6c4009
        }
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
bool
Packit 6c4009
__resolv_conf_attach (struct __res_state *resp, struct resolv_conf *conf)
Packit 6c4009
{
Packit 6c4009
  assert (conf->__refcount > 0);
Packit 6c4009
Packit 6c4009
  struct resolv_conf_global *global_copy = get_locked_global ();
Packit 6c4009
  if (global_copy == NULL)
Packit 6c4009
    return false;
Packit 6c4009
Packit 6c4009
  /* Try to find an unused index in the array.  */
Packit 6c4009
  size_t index;
Packit 6c4009
  {
Packit 6c4009
    if (global_copy->free_list_start & 1)
Packit 6c4009
      {
Packit 6c4009
        /* Unlink from the free list.  */
Packit 6c4009
        index = global_copy->free_list_start >> 1;
Packit 6c4009
        uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
Packit 6c4009
        global_copy->free_list_start = *slot;
Packit 6c4009
        assert (global_copy->free_list_start == 0
Packit 6c4009
                || global_copy->free_list_start & 1);
Packit 6c4009
        /* Install the configuration pointer.  */
Packit 6c4009
        *slot = (uintptr_t) conf;
Packit 6c4009
      }
Packit 6c4009
    else
Packit 6c4009
      {
Packit 6c4009
        size_t size = resolv_conf_array_size (&global_copy->array);
Packit 6c4009
        /* No usable index found.  Increase the array size.  */
Packit 6c4009
        resolv_conf_array_add (&global_copy->array, (uintptr_t) conf);
Packit 6c4009
        if (resolv_conf_array_has_failed (&global_copy->array))
Packit 6c4009
          {
Packit 6c4009
            put_locked_global (global_copy);
Packit 6c4009
            __set_errno (ENOMEM);
Packit 6c4009
            return false;
Packit 6c4009
          }
Packit 6c4009
        /* The new array element was added at the end.  */
Packit 6c4009
        index = size;
Packit 6c4009
      }
Packit 6c4009
  }
Packit 6c4009
Packit 6c4009
  /* We have added a new reference to the object.  */
Packit 6c4009
  ++conf->__refcount;
Packit 6c4009
  assert (conf->__refcount > 0);
Packit 6c4009
  put_locked_global (global_copy);
Packit 6c4009
Packit 6c4009
  if (!update_from_conf (resp, conf))
Packit 6c4009
    {
Packit 6c4009
      /* Drop the reference we acquired.  Reacquire the lock.  The
Packit 6c4009
         object has already been allocated, so it cannot be NULL this
Packit 6c4009
         time.  */
Packit 6c4009
      global_copy = get_locked_global ();
Packit 6c4009
      decrement_at_index (global_copy, index);
Packit 6c4009
      put_locked_global (global_copy);
Packit 6c4009
      return false;
Packit 6c4009
    }
Packit 6c4009
  resp->_u._ext.__glibc_extension_index = index ^ INDEX_MAGIC;
Packit 6c4009
Packit 6c4009
  return true;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
__resolv_conf_detach (struct __res_state *resp)
Packit 6c4009
{
Packit 6c4009
  if (atomic_load_relaxed (&global) == NULL)
Packit 6c4009
    /* Detach operation after a shutdown, or without any prior
Packit 6c4009
       attachment.  We cannot free the data (and there might not be
Packit 6c4009
       anything to free anyway).  */
Packit 6c4009
    return;
Packit 6c4009
Packit 6c4009
  struct resolv_conf_global *global_copy = get_locked_global ();
Packit 6c4009
  size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
Packit 6c4009
  decrement_at_index (global_copy, index);
Packit 6c4009
Packit 6c4009
  /* Clear the index field, so that accidental reuse is less
Packit 6c4009
     likely.  */
Packit 6c4009
  resp->_u._ext.__glibc_extension_index = 0;
Packit 6c4009
Packit 6c4009
  put_locked_global (global_copy);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Deallocate the global data.  */
Packit 6c4009
libc_freeres_fn (freeres)
Packit 6c4009
{
Packit 6c4009
  /* No locking because this function is supposed to be called when
Packit 6c4009
     the process has turned single-threaded.  */
Packit 6c4009
  if (global == NULL)
Packit 6c4009
    return;
Packit 6c4009
Packit 6c4009
  if (global->conf_current != NULL)
Packit 6c4009
    {
Packit 6c4009
      conf_decrement (global->conf_current);
Packit 6c4009
      global->conf_current = NULL;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Note that this frees only the array itself.  The pointed-to
Packit 6c4009
     configuration objects should have been deallocated by res_nclose
Packit 6c4009
     and per-thread cleanup functions.  */
Packit 6c4009
  resolv_conf_array_free (&global->array);
Packit 6c4009
Packit 6c4009
  free (global);
Packit 6c4009
Packit 6c4009
  /* Stop potential future __resolv_conf_detach calls from accessing
Packit 6c4009
     deallocated memory.  */
Packit 6c4009
  global = NULL;
Packit 6c4009
}