about summary refs log tree commit diff
path: root/resolv/resolv_context.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-06-30 21:10:23 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-07-03 20:52:59 +0200
commit352f4ff9a268b81ef5d4b2413f582565806e4790 (patch)
treefb27056dfdeafe43c021f6127c9544c016e78019 /resolv/resolv_context.c
parent4e45d83c92dbb5b8dc20654f32395108d18cf739 (diff)
downloadglibc-352f4ff9a268b81ef5d4b2413f582565806e4790.tar.gz
glibc-352f4ff9a268b81ef5d4b2413f582565806e4790.tar.xz
glibc-352f4ff9a268b81ef5d4b2413f582565806e4790.zip
resolv: Introduce struct resolv_context [BZ #21668]
struct resolv_context objects provide a temporary resolver context
which does not change during a name lookup operation.  Only when the
outmost context is created, the stub resolver configuration is
verified to be current (at present, only against previous res_init
calls).  Subsequent attempts to obtain the context will reuse the
result of the initial verification operation.

struct resolv_context can also be extended in the future to store
data which needs to be deallocated during thread cancellation.
Diffstat (limited to 'resolv/resolv_context.c')
-rw-r--r--resolv/resolv_context.c201
1 files changed, 201 insertions, 0 deletions
diff --git a/resolv/resolv_context.c b/resolv/resolv_context.c
new file mode 100644
index 0000000000..5083a40419
--- /dev/null
+++ b/resolv/resolv_context.c
@@ -0,0 +1,201 @@
+/* Temporary, thread-local resolver state.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <resolv_context.h>
+#include <resolv-internal.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Currently active struct resolv_context object.  This pointer forms
+   the start of a single-linked list, using the __next member of
+   struct resolv_context.  This list serves two purposes:
+
+   (a) A subsequent call to __resolv_context_get will only increment
+       the reference counter and will not allocate a new object.  The
+       _res state freshness check is skipped in this case, too.
+
+   (b) The per-thread cleanup function defined by the resolver calls
+       __resolv_context_freeres, which will deallocate all the context
+       objects.  This avoids the need for cancellation handlers and
+       the complexity they bring, but it requires heap allocation of
+       the context object because the per-thread cleanup functions run
+       only after the stack has been fully unwound (and all on-stack
+       objects have been deallocated at this point).
+
+   The TLS variable current is updated even in
+   __resolv_context_get_override, to support case (b) above.  This does
+   not override the per-thread resolver state (as obtained by the
+   non-res_state function such as __resolv_context_get) in an
+   observable way because the wrapped context is only used to
+   implement the res_n* functions in the resolver, and those do not
+   call back into user code which could indirectly use the per-thread
+   resolver state.  */
+static __thread struct resolv_context *current attribute_tls_model_ie;
+
+/* Initialize *RESP if RES_INIT is not yet set in RESP->options, or if
+   res_init in some other thread requested re-initializing.  */
+static __attribute__ ((warn_unused_result)) bool
+maybe_init (struct __res_state *resp, bool preinit)
+{
+  if (resp->options & RES_INIT)
+    {
+      if (__res_initstamp != resp->_u._ext.initstamp)
+        {
+          if (resp->nscount > 0)
+            __res_iclose (resp, true);
+          return __res_vinit (resp, 1) == 0;
+        }
+      return true;
+    }
+
+  if (preinit)
+    {
+      if (!resp->retrans)
+        resp->retrans = RES_TIMEOUT;
+      if (!resp->retry)
+        resp->retry = RES_DFLRETRY;
+      resp->options = RES_DEFAULT;
+      if (!resp->id)
+        resp->id = res_randomid ();
+    }
+  return __res_vinit (resp, preinit) == 0;
+}
+
+/* Allocate a new context object and initialize it.  The object is put
+   on the current list.  */
+static struct resolv_context *
+context_alloc (struct __res_state *resp)
+{
+  struct resolv_context *ctx = malloc (sizeof (*ctx));
+  if (ctx == NULL)
+    return NULL;
+  ctx->resp = resp;
+  ctx->__refcount = 1;
+  ctx->__from_res = true;
+  ctx->__next = current;
+  current = ctx;
+  return ctx;
+}
+
+/* Deallocate the context object and all the state within.   */
+static void
+context_free (struct resolv_context *ctx)
+{
+  current = ctx->__next;
+  free (ctx);
+}
+
+/* Reuse the current context object.  */
+static struct resolv_context *
+context_reuse (void)
+{
+  /* A context object created by __resolv_context_get_override cannot
+     be reused.  */
+  assert (current->__from_res);
+
+  ++current->__refcount;
+
+  /* Check for reference counter wraparound.  This can only happen if
+     the get/put functions are not properly paired.  */
+  assert (current->__refcount > 0);
+
+  return current;
+}
+
+/* Backing function for the __resolv_context_get family of
+   functions.  */
+static struct resolv_context *
+context_get (bool preinit)
+{
+  if (current != NULL)
+    return context_reuse ();
+
+  struct resolv_context *ctx = context_alloc (&_res);
+  if (ctx == NULL)
+    return NULL;
+  if (!maybe_init (ctx->resp, preinit))
+    {
+      context_free (ctx);
+      return NULL;
+    }
+  return ctx;
+}
+
+struct resolv_context *
+__resolv_context_get (void)
+{
+  return context_get (false);
+}
+libc_hidden_def (__resolv_context_get)
+
+struct resolv_context *
+__resolv_context_get_preinit (void)
+{
+  return context_get (true);
+}
+libc_hidden_def (__resolv_context_get_preinit)
+
+struct resolv_context *
+__resolv_context_get_override (struct __res_state *resp)
+{
+  /* NB: As explained asbove, context_alloc will put the context on
+     the current list.  */
+  struct resolv_context *ctx = context_alloc (resp);
+  if (ctx == NULL)
+    return NULL;
+
+  ctx->__from_res = false;
+  return ctx;
+}
+libc_hidden_def (__resolv_context_get_override)
+
+void
+__resolv_context_put (struct resolv_context *ctx)
+{
+  if (ctx == NULL)
+    return;
+
+  /* NB: Callers assume that this function preserves errno and
+     h_errno.  */
+
+  assert (current == ctx);
+  assert (ctx->__refcount > 0);
+
+  if (ctx->__from_res && --ctx->__refcount > 0)
+    /* Do not pop this context yet.  */
+    return;
+
+  context_free (ctx);
+}
+libc_hidden_def (__resolv_context_put)
+
+void
+__resolv_context_freeres (void)
+{
+  /* Deallocate the entire chain of context objects.  */
+  struct resolv_context *ctx = current;
+  current = NULL;
+  while (ctx != NULL)
+    {
+      struct resolv_context *next = ctx->__next;
+      context_free (ctx);
+      ctx = next;
+    }
+}