mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 10:08:56 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			484 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			484 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 | 
						|
 | 
						|
/* GIO - GLib Input, Output and Streaming Library
 | 
						|
 * 
 | 
						|
 * Copyright (C) 2008 Red Hat, Inc.
 | 
						|
 *
 | 
						|
 * This 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 of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This 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 this library; if not, write to the
 | 
						|
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 | 
						|
 * Boston, MA 02111-1307, USA.
 | 
						|
 */
 | 
						|
 | 
						|
#include "config.h"
 | 
						|
#include <glib.h>
 | 
						|
#include "glibintl.h"
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include "gwin32resolver.h"
 | 
						|
#include "gnetworkingprivate.h"
 | 
						|
 | 
						|
#include "gcancellable.h"
 | 
						|
#include "gsimpleasyncresult.h"
 | 
						|
#include "gsocketaddress.h"
 | 
						|
 | 
						|
 | 
						|
G_DEFINE_TYPE (GWin32Resolver, g_win32_resolver, G_TYPE_THREADED_RESOLVER)
 | 
						|
 | 
						|
static void
 | 
						|
g_win32_resolver_init (GWin32Resolver *gwr)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
/* This is simpler than GThreadedResolver since we don't have to worry
 | 
						|
 * about multiple application-level threads, but more complicated than
 | 
						|
 * GUnixResolver, since we do have to deal with multiple threads of
 | 
						|
 * our own.
 | 
						|
 *
 | 
						|
 * The various request possibilities:
 | 
						|
 *
 | 
						|
 * 1. Synchronous: handed off to the base class (GThreadedResolver);
 | 
						|
 *    since it's never possible to cancel a synchronous request in a
 | 
						|
 *    single-threaded program, the request is done in the calling
 | 
						|
 *    thread.
 | 
						|
 *
 | 
						|
 * 2. Asynchronous: A GWin32ResolverRequest is created with
 | 
						|
 *    appropriate query-specific information, a Windows event handle,
 | 
						|
 *    and a GSimpleAsyncResult. This is then handed to the
 | 
						|
 *    Windows-internal thread pool, which does the raw DNS query part
 | 
						|
 *    of the operation (being careful to not call any glib methods
 | 
						|
 *    that might fail when called from another thread when
 | 
						|
 *    g_thread_init() has not been called). The main thread sets up a
 | 
						|
 *    GSource to asynchronously poll the event handle. There are two
 | 
						|
 *    sub-possibilities:
 | 
						|
 *
 | 
						|
 *      a. The resolution completes: the threadpool function calls
 | 
						|
 *         SetEvent() on the event handle and then returns.
 | 
						|
 *
 | 
						|
 *      b. The resolution is cancelled: request_cancelled()
 | 
						|
 *         disconnects the "cancelled" signal handler, and queues an
 | 
						|
 *         idle handler to complete the async_result.
 | 
						|
 *
 | 
						|
 *    Since we can't free the request from the threadpool thread
 | 
						|
 *    (because of glib locking issues), we *always* have to have it
 | 
						|
 *    call SetEvent and trigger the callback to indicate that it is
 | 
						|
 *    done. But this means that it's possible for the request to be
 | 
						|
 *    cancelled (queuing an idle handler to return that result) and
 | 
						|
 *    then have the resolution thread complete before the idle handler
 | 
						|
 *    runs. So the event callback and the idle handler need to both
 | 
						|
 *    watch out for this, making sure we don't complete the same
 | 
						|
 *    result twice.
 | 
						|
 */
 | 
						|
 | 
						|
typedef struct GWin32ResolverRequest GWin32ResolverRequest;
 | 
						|
typedef void (*GWin32ResolverRequestFreeFunc) (GWin32ResolverRequest *);
 | 
						|
 | 
						|
struct GWin32ResolverRequest {
 | 
						|
  GWin32ResolverRequestFreeFunc free_func;
 | 
						|
 | 
						|
  GCancellable *cancellable;
 | 
						|
  GError *error;
 | 
						|
 | 
						|
  HANDLE *event;
 | 
						|
  GSimpleAsyncResult *async_result;
 | 
						|
  gboolean complete;
 | 
						|
  GSource *cancelled_idle;
 | 
						|
 | 
						|
  union {
 | 
						|
    struct {
 | 
						|
      gchar *name;
 | 
						|
      gint retval;
 | 
						|
      struct addrinfo *res;
 | 
						|
    } name;
 | 
						|
 | 
						|
    struct {
 | 
						|
      GInetAddress *iaddr;
 | 
						|
      struct sockaddr_storage addr;
 | 
						|
      gsize addrlen;
 | 
						|
      gint retval;
 | 
						|
      gchar *namebuf;
 | 
						|
    } address;
 | 
						|
 | 
						|
    struct {
 | 
						|
      gchar *rrname;
 | 
						|
      DNS_STATUS retval;
 | 
						|
      DNS_RECORD *results;
 | 
						|
    } service;
 | 
						|
  } u;
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
static GSource *g_win32_handle_source_add (HANDLE      handle,
 | 
						|
                                           GSourceFunc callback,
 | 
						|
                                           gpointer    user_data);
 | 
						|
 | 
						|
static gboolean request_completed (gpointer      user_data);
 | 
						|
static void     request_cancelled (GCancellable *cancellable,
 | 
						|
                                   gpointer      user_data);
 | 
						|
 | 
						|
GWin32ResolverRequest *
 | 
						|
g_win32_resolver_request_new (GResolver                     *resolver,
 | 
						|
                              GWin32ResolverRequestFreeFunc  free_func,
 | 
						|
                              GCancellable                  *cancellable,
 | 
						|
                              GAsyncReadyCallback            callback,
 | 
						|
                              gpointer                       user_data,
 | 
						|
                              gpointer                       tag)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req;
 | 
						|
 | 
						|
  req = g_slice_new0 (GWin32ResolverRequest);
 | 
						|
  req->free_func = free_func;
 | 
						|
 | 
						|
  req->async_result = g_simple_async_result_new (G_OBJECT (resolver), callback,
 | 
						|
                                                 user_data, tag);
 | 
						|
  g_simple_async_result_set_op_res_gpointer (req->async_result, req, NULL);
 | 
						|
 | 
						|
  req->event = CreateEvent (NULL, FALSE, FALSE, NULL);
 | 
						|
  g_win32_handle_source_add (req->event, request_completed, req);  
 | 
						|
 | 
						|
  if (cancellable)
 | 
						|
    {
 | 
						|
      req->cancellable = g_object_ref (cancellable);
 | 
						|
      g_signal_connect (cancellable, "cancelled",
 | 
						|
                        G_CALLBACK (request_cancelled), req);
 | 
						|
    }
 | 
						|
 | 
						|
  return req;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
request_completed (gpointer user_data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req = user_data;
 | 
						|
 | 
						|
  /* Clean up cancellation-related stuff first */
 | 
						|
  if (req->cancelled_idle)
 | 
						|
    {
 | 
						|
      g_source_destroy (req->cancelled_idle);
 | 
						|
      g_source_unref (req->cancelled_idle);
 | 
						|
      req->cancelled_idle = NULL;
 | 
						|
    }
 | 
						|
  if (req->cancellable)
 | 
						|
    {
 | 
						|
      g_signal_handlers_disconnect_by_func (req->cancellable, request_cancelled, req);
 | 
						|
      g_object_unref (req->cancellable);
 | 
						|
    }
 | 
						|
 | 
						|
  /* Now complete the result (assuming it wasn't already completed) */
 | 
						|
  if (req->async_result)
 | 
						|
    {
 | 
						|
      g_simple_async_result_complete (req->async_result);
 | 
						|
      g_object_unref (req->async_result);
 | 
						|
    }
 | 
						|
 | 
						|
  /* And free req */
 | 
						|
  CloseHandle (req->event);
 | 
						|
  req->free_func (req);
 | 
						|
  g_slice_free (GWin32ResolverRequest, req);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
request_cancelled_idle (gpointer user_data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req = user_data;
 | 
						|
  GError *error = NULL;
 | 
						|
 | 
						|
  g_source_unref (req->cancelled_idle);
 | 
						|
  req->cancelled_idle = NULL;
 | 
						|
 | 
						|
  g_cancellable_set_error_if_cancelled (req->cancellable, &error);
 | 
						|
  g_simple_async_result_set_from_error (req->async_result, error);
 | 
						|
  g_simple_async_result_complete (req->async_result);
 | 
						|
 | 
						|
  g_object_unref (req->async_result);
 | 
						|
  req->async_result = NULL;
 | 
						|
 | 
						|
  /* request_completed will eventually be called to free req */
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
request_cancelled (GCancellable *cancellable,
 | 
						|
                   gpointer      user_data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req = user_data;
 | 
						|
 | 
						|
  if (req->cancellable)
 | 
						|
    {
 | 
						|
      g_signal_handlers_disconnect_by_func (req->cancellable, request_cancelled, req);
 | 
						|
      g_object_unref (req->cancellable);
 | 
						|
      req->cancellable = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
  /* We need to wait until main-loop-time to actually complete the
 | 
						|
   * result; we don't use _complete_in_idle() here because we need to
 | 
						|
   * keep track of the source so we can potentially cancel it before
 | 
						|
   * it runs.
 | 
						|
   */
 | 
						|
  req->cancelled_idle = g_idle_source_new ();
 | 
						|
  g_source_set_callback (req->cancelled_idle,
 | 
						|
                         (GSourceFunc)request_cancelled_idle, req, NULL);
 | 
						|
  g_source_attach (req->cancelled_idle, g_main_context_get_thread_default ());
 | 
						|
}
 | 
						|
 | 
						|
static DWORD WINAPI
 | 
						|
lookup_by_name_in_thread (LPVOID data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req = data;
 | 
						|
 | 
						|
  req->u.name.retval = getaddrinfo (req->u.name.name, NULL,
 | 
						|
                                    &_g_resolver_addrinfo_hints,
 | 
						|
                                    &req->u.name.res);
 | 
						|
  SetEvent (req->event);
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
free_lookup_by_name (GWin32ResolverRequest *req)
 | 
						|
{
 | 
						|
  g_free (req->u.name.name);
 | 
						|
  if (req->u.name.res)
 | 
						|
    freeaddrinfo (req->u.name.res);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
lookup_by_name_async (GResolver           *resolver,
 | 
						|
                      const gchar         *hostname,
 | 
						|
                      GCancellable        *cancellable,
 | 
						|
                      GAsyncReadyCallback  callback,
 | 
						|
                      gpointer             user_data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req;
 | 
						|
 | 
						|
  req = g_win32_resolver_request_new (resolver, free_lookup_by_name,
 | 
						|
                                      cancellable, callback, user_data,
 | 
						|
                                      lookup_by_name_async);
 | 
						|
  req->u.name.name = g_strdup (hostname);
 | 
						|
 | 
						|
  QueueUserWorkItem (lookup_by_name_in_thread, req, 0);
 | 
						|
}
 | 
						|
 | 
						|
static GList *
 | 
						|
lookup_by_name_finish (GResolver     *resolver,
 | 
						|
                       GAsyncResult  *result,
 | 
						|
                       GError       **error)
 | 
						|
{
 | 
						|
  GSimpleAsyncResult *simple;
 | 
						|
  GWin32ResolverRequest *req;
 | 
						|
 | 
						|
  g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_by_name_async), NULL);
 | 
						|
  simple = G_SIMPLE_ASYNC_RESULT (result);
 | 
						|
 | 
						|
  req = g_simple_async_result_get_op_res_gpointer (simple);
 | 
						|
  return _g_resolver_addresses_from_addrinfo (req->u.name.name, req->u.name.res,
 | 
						|
                                              req->u.name.retval, error);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static DWORD WINAPI
 | 
						|
lookup_by_addresses_in_thread (LPVOID data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req = data;
 | 
						|
 | 
						|
  req->u.address.retval =
 | 
						|
    getnameinfo ((struct sockaddr *)&req->u.address.addr,
 | 
						|
                 req->u.address.addrlen,
 | 
						|
                 req->u.address.namebuf, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
 | 
						|
  SetEvent (req->event);
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
free_lookup_by_address (GWin32ResolverRequest *req)
 | 
						|
{
 | 
						|
  g_object_unref (req->u.address.iaddr);
 | 
						|
  g_free (req->u.address.namebuf);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
lookup_by_address_async (GResolver           *resolver,
 | 
						|
                         GInetAddress        *address,
 | 
						|
                         GCancellable        *cancellable,
 | 
						|
                         GAsyncReadyCallback  callback,
 | 
						|
                         gpointer             user_data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req;
 | 
						|
 | 
						|
  req = g_win32_resolver_request_new (resolver, free_lookup_by_address,
 | 
						|
                                      cancellable, callback, user_data,
 | 
						|
                                      lookup_by_address_async);
 | 
						|
 | 
						|
  req->u.address.iaddr = g_object_ref (address);
 | 
						|
  _g_resolver_address_to_sockaddr (address, &req->u.address.addr,
 | 
						|
                                   &req->u.address.addrlen);
 | 
						|
  req->u.address.namebuf = g_malloc (NI_MAXHOST);
 | 
						|
 | 
						|
  QueueUserWorkItem (lookup_by_addresses_in_thread, req, 0);
 | 
						|
}
 | 
						|
 | 
						|
static char *
 | 
						|
lookup_by_address_finish (GResolver     *resolver,
 | 
						|
                          GAsyncResult  *result,
 | 
						|
                          GError       **error)
 | 
						|
{
 | 
						|
  GSimpleAsyncResult *simple;
 | 
						|
  GWin32ResolverRequest *req;
 | 
						|
 | 
						|
  g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_by_address_async), NULL);
 | 
						|
  simple = G_SIMPLE_ASYNC_RESULT (result);
 | 
						|
 | 
						|
  req = g_simple_async_result_get_op_res_gpointer (simple);
 | 
						|
  return _g_resolver_name_from_nameinfo (req->u.address.iaddr,
 | 
						|
                                         req->u.address.namebuf,
 | 
						|
                                         req->u.address.retval, error);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static DWORD WINAPI
 | 
						|
lookup_service_in_thread (LPVOID data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req = data;
 | 
						|
 | 
						|
  req->u.service.retval =
 | 
						|
    DnsQuery_A (req->u.service.rrname, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
 | 
						|
                NULL, &req->u.service.results, NULL);
 | 
						|
  SetEvent (req->event);
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
free_lookup_service (GWin32ResolverRequest *req)
 | 
						|
{
 | 
						|
  g_free (req->u.service.rrname);
 | 
						|
  if (req->u.service.results)
 | 
						|
    DnsRecordListFree (req->u.service.results, DnsFreeRecordList);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
lookup_service_async (GResolver           *resolver,
 | 
						|
                      const char          *rrname,
 | 
						|
		      GCancellable        *cancellable,
 | 
						|
                      GAsyncReadyCallback  callback,
 | 
						|
		      gpointer             user_data)
 | 
						|
{
 | 
						|
  GWin32ResolverRequest *req;
 | 
						|
 | 
						|
  req = g_win32_resolver_request_new (resolver, free_lookup_service,
 | 
						|
                                      cancellable, callback, user_data,
 | 
						|
                                      lookup_service_async);
 | 
						|
  req->u.service.rrname = g_strdup (rrname);
 | 
						|
 | 
						|
  QueueUserWorkItem (lookup_service_in_thread, req, 0);
 | 
						|
}
 | 
						|
 | 
						|
static GList *
 | 
						|
lookup_service_finish (GResolver     *resolver,
 | 
						|
                       GAsyncResult  *result,
 | 
						|
		       GError       **error)
 | 
						|
{
 | 
						|
  GSimpleAsyncResult *simple;
 | 
						|
  GWin32ResolverRequest *req;
 | 
						|
 | 
						|
  g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_service_async), NULL);
 | 
						|
  simple = G_SIMPLE_ASYNC_RESULT (result);
 | 
						|
 | 
						|
  req = g_simple_async_result_get_op_res_gpointer (simple);
 | 
						|
  return _g_resolver_targets_from_DnsQuery (req->u.service.rrname,
 | 
						|
                                            req->u.service.retval,
 | 
						|
                                            req->u.service.results, error);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
g_win32_resolver_class_init (GWin32ResolverClass *win32_class)
 | 
						|
{
 | 
						|
  GResolverClass *resolver_class = G_RESOLVER_CLASS (win32_class);
 | 
						|
 | 
						|
  resolver_class->lookup_by_name_async     = lookup_by_name_async;
 | 
						|
  resolver_class->lookup_by_name_finish    = lookup_by_name_finish;
 | 
						|
  resolver_class->lookup_by_address_async  = lookup_by_address_async;
 | 
						|
  resolver_class->lookup_by_address_finish = lookup_by_address_finish;
 | 
						|
  resolver_class->lookup_service_async     = lookup_service_async;
 | 
						|
  resolver_class->lookup_service_finish    = lookup_service_finish;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* Windows HANDLE GSource */
 | 
						|
 | 
						|
typedef struct {
 | 
						|
  GSource source;
 | 
						|
  GPollFD pollfd;
 | 
						|
} GWin32HandleSource;
 | 
						|
 | 
						|
static gboolean
 | 
						|
g_win32_handle_source_prepare (GSource *source,
 | 
						|
                               gint    *timeout)
 | 
						|
{
 | 
						|
  *timeout = -1;
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
g_win32_handle_source_check (GSource *source)
 | 
						|
{
 | 
						|
  GWin32HandleSource *hsource = (GWin32HandleSource *)source;
 | 
						|
 | 
						|
  return hsource->pollfd.revents;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
g_win32_handle_source_dispatch (GSource     *source,
 | 
						|
                                GSourceFunc  callback,
 | 
						|
                                gpointer     user_data)
 | 
						|
{
 | 
						|
  return (*callback) (user_data);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
g_win32_handle_source_finalize (GSource *source)
 | 
						|
{
 | 
						|
  ;
 | 
						|
}
 | 
						|
 | 
						|
GSourceFuncs g_win32_handle_source_funcs = {
 | 
						|
  g_win32_handle_source_prepare,
 | 
						|
  g_win32_handle_source_check,
 | 
						|
  g_win32_handle_source_dispatch,
 | 
						|
  g_win32_handle_source_finalize
 | 
						|
};
 | 
						|
 | 
						|
static GSource *
 | 
						|
g_win32_handle_source_add (HANDLE      handle,
 | 
						|
                           GSourceFunc callback,
 | 
						|
                           gpointer    user_data)
 | 
						|
{
 | 
						|
  GWin32HandleSource *hsource;
 | 
						|
  GSource *source;
 | 
						|
 | 
						|
  source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource));
 | 
						|
  hsource = (GWin32HandleSource *)source;
 | 
						|
  hsource->pollfd.fd = (gint)handle;
 | 
						|
  hsource->pollfd.events = G_IO_IN;
 | 
						|
  hsource->pollfd.revents = 0;
 | 
						|
  g_source_add_poll (source, &hsource->pollfd);
 | 
						|
 | 
						|
  g_source_set_callback (source, callback, user_data, NULL);
 | 
						|
  g_source_attach (source, g_main_context_get_thread_default ());
 | 
						|
  return source;
 | 
						|
}
 |