From c94d3f92885456e1dc9e2fb27b709017f29d04ce Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Mon, 29 Dec 2008 12:53:47 -0500 Subject: [PATCH] Add GResolver, a glib-ish interface to DNS GResolver provides asynchronous (and synchronous-but-cancellable) APIs for resolving hostnames, reverse-resolving IP addresses back to hostnames, and resolving SRV records. Part of #548466. --- configure.in | 25 + docs/reference/gio/Makefile.am | 6 +- docs/reference/gio/gio-docs.xml | 2 + docs/reference/gio/gio-sections.txt | 52 + docs/reference/gio/gio.types | 1 + gio/Makefile.am | 18 +- gio/ginetaddress.c | 7 +- gio/gio.h | 2 + gio/gio.symbols | 57 + gio/gioenums.h | 14 + gio/giotypes.h | 4 +- gio/gnetworkingprivate.h | 32 + gio/gresolver.c | 855 +++++++++++++++ gio/gresolver.h | 159 +++ gio/gsrvtarget.c | 334 ++++++ gio/gsrvtarget.h | 52 + gio/gthreadedresolver.c | 617 +++++++++++ gio/gthreadedresolver.h | 50 + gio/gunixresolver.c | 433 ++++++++ gio/gunixresolver.h | 53 + gio/gwin32resolver.c | 481 +++++++++ gio/gwin32resolver.h | 49 + gio/libasyncns/Makefile.am | 15 + gio/libasyncns/README | 7 + gio/libasyncns/asyncns.c | 1498 +++++++++++++++++++++++++++ gio/libasyncns/asyncns.h | 163 +++ gio/libasyncns/g-asyncns.h | 28 + gio/libasyncns/update.sh | 20 + gio/pltcheck.sh | 2 +- gio/tests/.gitignore | 1 + gio/tests/Makefile.am | 8 +- gio/tests/resolver.c | 377 +++++++ 32 files changed, 5412 insertions(+), 10 deletions(-) create mode 100644 gio/gresolver.c create mode 100644 gio/gresolver.h create mode 100644 gio/gsrvtarget.c create mode 100644 gio/gsrvtarget.h create mode 100644 gio/gthreadedresolver.c create mode 100644 gio/gthreadedresolver.h create mode 100644 gio/gunixresolver.c create mode 100644 gio/gunixresolver.h create mode 100644 gio/gwin32resolver.c create mode 100644 gio/gwin32resolver.h create mode 100644 gio/libasyncns/Makefile.am create mode 100644 gio/libasyncns/README create mode 100644 gio/libasyncns/asyncns.c create mode 100644 gio/libasyncns/asyncns.h create mode 100644 gio/libasyncns/g-asyncns.h create mode 100644 gio/libasyncns/update.sh create mode 100644 gio/tests/resolver.c diff --git a/configure.in b/configure.in index 5c33ab951..31725a78d 100644 --- a/configure.in +++ b/configure.in @@ -974,6 +974,30 @@ if $glib_failed ; then AC_MSG_ERROR([Could not determine values for AF_INET* constants]) fi +# For gio/libasyncns +if test $glib_native_win32 = no; then + AC_CHECK_FUNCS(strndup setresuid setreuid) + AC_CHECK_HEADERS(sys/prctl.h arpa/nameser_compat.h) + AC_CHECK_FUNC(res_query, , + [AC_CHECK_LIB(resolv, res_query, [ LIBASYNCNS_LIBADD="-lresolv" ], + [ save_libs="$LIBS" + LIBS="-lresolv $LIBS" + AC_MSG_CHECKING([for res_query in -lresolv (alternate version)]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[#include ]], [[res_query(0,0,0,0,0)]])], + [ AC_MSG_RESULT(yes) + LIBASYNCNS_LIBADD="-lresolv" ], + [ AC_MSG_RESULT(no) + AC_CHECK_LIB(bind, res_query, + [ LIBASYNCNS_LIBADD="-lbind" ], + [ AC_MSG_ERROR(res_query not found) ] ) ] ) + LIBS="$save_libs" + ] ) + ] + ) + AC_SUBST(LIBASYNCNS_LIBADD) +fi + dnl dnl if statfs() takes 2 arguments (Posix) or 4 (Solaris) dnl @@ -3384,6 +3408,7 @@ gthread/Makefile gio/Makefile gio/xdgmime/Makefile gio/inotify/Makefile +gio/libasyncns/Makefile gio/fen/Makefile gio/fam/Makefile gio/win32/Makefile diff --git a/docs/reference/gio/Makefile.am b/docs/reference/gio/Makefile.am index 43e003f41..1cd0cdca6 100644 --- a/docs/reference/gio/Makefile.am +++ b/docs/reference/gio/Makefile.am @@ -43,11 +43,14 @@ IGNORE_HFILES= \ glocalvfs.h \ gnativevolumemonitor.h \ gpollfilemonitor.h \ + gthreadedresolver.h \ gunionvolumemonitor.h \ gunixdrive.h \ + gunixresolver.h \ gunixvolume.h \ gvolumeprivate.h \ gwin32appinfo.h \ + gwin32resolver.h \ inotify-diag.h \ inotify-helper.h \ inotify-kernel.h \ @@ -85,7 +88,8 @@ GTKDOC_LIBS = \ $(NULL) # Extra options to supply to gtkdoc-mkdb -MKDB_OPTIONS = --output-format=xml --sgml-mode --name-space=g +MKDB_OPTIONS = --output-format=xml --sgml-mode --name-space=g \ + --ignore-files=libasyncns # Images to copy into HTML directory HTML_IMAGES = \ diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml index 8872ed7af..223127cae 100644 --- a/docs/reference/gio/gio-docs.xml +++ b/docs/reference/gio/gio-docs.xml @@ -92,10 +92,12 @@ Networking + + Utilities diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index a2d19b7c5..ae97ca5bb 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1404,3 +1404,55 @@ G_UNIX_SOCKET_ADDRESS_GET_CLASS g_unix_socket_address_get_type + +
+gresolver +GResolver +GResolver +g_resolver_get_default +g_resolver_set_default +g_resolver_lookup_by_name +g_resolver_lookup_by_name_async +g_resolver_lookup_by_name_finish +g_resolver_free_addresses +g_resolver_lookup_by_address +g_resolver_lookup_by_address_async +g_resolver_lookup_by_address_finish +g_resolver_lookup_service +g_resolver_lookup_service_async +g_resolver_lookup_service_finish +g_resolver_free_targets + +G_RESOLVER_ERROR +GResolverError + +GResolverClass +G_IS_RESOLVER +G_IS_RESOLVER_CLASS +G_RESOLVER +G_RESOLVER_CLASS +G_RESOLVER_GET_CLASS +G_TYPE_RESOLVER + +g_resolver_get_type +g_resolver_error_quark +
+ +
+gsrvtarget +GSrvTarget +GSrvTarget +g_srv_target_new +g_srv_target_copy +g_srv_target_free +g_srv_target_get_hostname +g_srv_target_get_port +g_srv_target_get_priority +g_srv_target_get_weight +g_srv_target_get_expires +g_srv_target_array_sort + +G_TYPE_SRV_TARGET + +g_srv_target_get_type +
diff --git a/docs/reference/gio/gio.types b/docs/reference/gio/gio.types index cedae876a..54d834d5c 100644 --- a/docs/reference/gio/gio.types +++ b/docs/reference/gio/gio.types @@ -55,6 +55,7 @@ g_native_volume_monitor_get_type g_output_stream_get_type g_output_stream_splice_flags_get_type g_password_save_get_type +g_resolver_get_type g_seekable_get_type g_simple_async_result_get_type g_socket_address_get_type diff --git a/gio/Makefile.am b/gio/Makefile.am index 53b30c2a6..e889ecb38 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -5,7 +5,7 @@ NULL = SUBDIRS= if OS_UNIX -SUBDIRS += xdgmime +SUBDIRS += libasyncns xdgmime endif if OS_WIN32_AND_DLL_COMPILATION @@ -125,13 +125,15 @@ endif if OS_UNIX appinfo_sources += gdesktopappinfo.c gdesktopappinfo.h -platform_libadd += xdgmime/libxdgmime.la -platform_deps += xdgmime/libxdgmime.la +platform_libadd += libasyncns/libasyncns.la xdgmime/libxdgmime.la +platform_deps += libasyncns/libasyncns.la xdgmime/libxdgmime.la unix_sources = \ gunixmount.c \ gunixmount.h \ gunixmounts.c \ gunixmounts.h \ + gunixresolver.c \ + gunixresolver.h \ gunixsocketaddress.c \ gunixvolume.c \ gunixvolume.h \ @@ -154,10 +156,12 @@ endif if OS_WIN32 appinfo_sources += gwin32appinfo.c gwin32appinfo.h -platform_libadd += -lshlwapi -lws2_32 +platform_libadd += -lshlwapi -lws2_32 -ldnsapi win32_sources = \ gwin32mount.c \ gwin32mount.h \ + gwin32resolver.c \ + gwin32resolver.h \ gwin32volumemonitor.c \ gwin32volumemonitor.h \ $(NULL) @@ -217,10 +221,14 @@ libgio_2_0_la_SOURCES = \ goutputstream.c \ gpollfilemonitor.c \ gpollfilemonitor.h \ + gresolver.c \ gseekable.c \ gsimpleasyncresult.c \ gsocketaddress.c \ + gsrvtarget.c \ gthemedicon.c \ + gthreadedresolver.c \ + gthreadedresolver.h \ gunionvolumemonitor.c \ gunionvolumemonitor.h \ gvfs.c \ @@ -321,9 +329,11 @@ gio_headers = \ gmountoperation.h \ gnativevolumemonitor.h \ goutputstream.h \ + gresolver.h \ gseekable.h \ gsimpleasyncresult.h \ gsocketaddress.h \ + gsrvtarget.h \ gthemedicon.h \ gvfs.h \ gvolume.h \ diff --git a/gio/ginetaddress.c b/gio/ginetaddress.c index cc5856bff..72c08b804 100644 --- a/gio/ginetaddress.c +++ b/gio/ginetaddress.c @@ -36,7 +36,12 @@ * SECTION:ginetaddress * @short_description: An IPv4/IPv6 address * - * #GInetAddress represents an IPv4 or IPv6 internet address. + * #GInetAddress represents an IPv4 or IPv6 internet address. Use + * g_resolver_lookup_by_name() or g_resolver_lookup_by_name_async() to + * look up the #GInetAddress for a hostname. Use + * g_resolver_lookup_by_address() or + * g_resolver_lookup_by_address_async() to look up the hostname for a + * #GInetAddress. * * To actually connect to a remote host, you will need a * #GInetSocketAddress (which includes a #GInetAddress as well as a diff --git a/gio/gio.h b/gio/gio.h index 6e809bab2..779ce4082 100644 --- a/gio/gio.h +++ b/gio/gio.h @@ -64,9 +64,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include diff --git a/gio/gio.symbols b/gio/gio.symbols index 99ad117d1..4a8350ae9 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -834,6 +834,7 @@ g_ask_password_flags_get_type G_GNUC_CONST g_password_save_get_type G_GNUC_CONST g_emblem_origin_get_type G_GNUC_CONST g_socket_family_get_type G_GNUC_CONST +g_resolver_error_get_type G_GNUC_CONST #endif #endif @@ -908,3 +909,59 @@ g_socket_address_get_native_size g_socket_address_to_native #endif #endif + +#if IN_HEADER(__G_RESOLVER_H__) +#if IN_FILE(__G_RESOLVER_C__) +g_resolver_error_quark +g_resolver_free_addresses +g_resolver_free_targets +g_resolver_get_type G_GNUC_CONST +g_resolver_get_default +g_resolver_set_default +g_resolver_lookup_by_name +g_resolver_lookup_by_name_async +g_resolver_lookup_by_name_finish +g_resolver_lookup_by_address +g_resolver_lookup_by_address_async +g_resolver_lookup_by_address_finish +g_resolver_lookup_service +g_resolver_lookup_service_async +g_resolver_lookup_service_finish +#endif +#endif + +#if IN_HEADER(__G_THREADED_RESOLVER_H__) +#if IN_FILE(__G_THREADED_RESOLVER_C__) +g_threaded_resolver_get_type G_GNUC_CONST +#endif +#endif + +#if IN_HEADER(__G_UNIX_RESOLVER_H__) +#if IN_FILE(__G_UNIX_RESOLVER_C__) +#ifdef G_OS_UNIX +g_unix_resolver_get_type G_GNUC_CONST +#endif +#endif +#endif + +#if IN_HEADER(__G_WIN32_RESOLVER_H__) +#if IN_FILE(__G_WIN32_RESOLVER_C__) +#ifdef G_OS_WIN32 +g_win32_resolver_get_type G_GNUC_CONST +#endif +#endif +#endif + +#if IN_HEADER(__G_SRV_TARGET_H__) +#if IN_FILE(__G_SRV_TARGET_C__) +g_srv_target_get_type G_GNUC_CONST +g_srv_target_new +g_srv_target_copy +g_srv_target_free +g_srv_target_get_hostname +g_srv_target_get_port +g_srv_target_get_priority +g_srv_target_get_weight +g_srv_target_list_sort +#endif +#endif diff --git a/gio/gioenums.h b/gio/gioenums.h index e6e6dfcaf..e9f440bc3 100644 --- a/gio/gioenums.h +++ b/gio/gioenums.h @@ -476,6 +476,20 @@ typedef enum { G_EMBLEM_ORIGIN_TAG } GEmblemOrigin; +/** + * GResolverError: + * @G_RESOLVER_ERROR_NOT_FOUND: the requested name/address/service was not found + * @G_RESOLVER_ERROR_TEMPORARY_FAILURE: the requested information could not be looked up due to a network error or similar problem + * @G_RESOLVER_ERROR_INTERNAL: unknown error + * + * An error code used with %G_RESOLVER_ERROR in a #GError returned + * from a #GResolver routine. + */ +typedef enum { + G_RESOLVER_ERROR_NOT_FOUND, + G_RESOLVER_ERROR_TEMPORARY_FAILURE, + G_RESOLVER_ERROR_INTERNAL +} GResolverError; /** * GSocketFamily: diff --git a/gio/giotypes.h b/gio/giotypes.h index 6ea1c1b0e..1e44e4c49 100644 --- a/gio/giotypes.h +++ b/gio/giotypes.h @@ -99,12 +99,12 @@ typedef struct _GMemoryOutputStream GMemoryOutputStream; **/ typedef struct _GMount GMount; /* Dummy typedef */ typedef struct _GMountOperation GMountOperation; -typedef struct _GNetworkAddress GNetworkAddress; -typedef struct _GNetworkService GNetworkService; typedef struct _GOutputStream GOutputStream; +typedef struct _GResolver GResolver; typedef struct _GSeekable GSeekable; typedef struct _GSimpleAsyncResult GSimpleAsyncResult; typedef struct _GSocketAddress GSocketAddress; +typedef struct _GSrvTarget GSrvTarget; typedef struct _GThemedIcon GThemedIcon; typedef struct _GVfs GVfs; /* Dummy typedef */ diff --git a/gio/gnetworkingprivate.h b/gio/gnetworkingprivate.h index a90d2645d..c41f59863 100644 --- a/gio/gnetworkingprivate.h +++ b/gio/gnetworkingprivate.h @@ -50,4 +50,36 @@ #endif +G_BEGIN_DECLS + +extern struct addrinfo _g_resolver_addrinfo_hints; + +GList *_g_resolver_addresses_from_addrinfo (const char *hostname, + struct addrinfo *res, + gint gai_retval, + GError **error); + +void _g_resolver_address_to_sockaddr (GInetAddress *address, + struct sockaddr_storage *sa, + gsize *sa_len); +char *_g_resolver_name_from_nameinfo (GInetAddress *address, + const gchar *name, + gint gni_retval, + GError **error); + +#if defined(G_OS_UNIX) +GList *_g_resolver_targets_from_res_query (const gchar *rrname, + guchar *answer, + gint len, + gint herr, + GError **error); +#elif defined(G_OS_WIN32) +GList *_g_resolver_targets_from_DnsQuery (const gchar *rrname, + DNS_STATUS status, + DNS_RECORD *results, + GError **error); +#endif + +G_END_DECLS + #endif /* __G_NETWORKINGPRIVATE_H__ */ diff --git a/gio/gresolver.c b/gio/gresolver.c new file mode 100644 index 000000000..32a3767c8 --- /dev/null +++ b/gio/gresolver.c @@ -0,0 +1,855 @@ +/* -*- 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 +#include "glibintl.h" + +#include "gresolver.h" +#include "gnetworkingprivate.h" +#include "gasyncresult.h" +#include "ginetaddress.h" +#include "ginetsocketaddress.h" +#include "gsimpleasyncresult.h" +#include "gsrvtarget.h" + +#ifdef G_OS_UNIX +#include "gunixresolver.h" +#endif +#ifdef G_OS_WIN32 +#include "gwin32resolver.h" +#endif + +#include + +#include "gioalias.h" + +/** + * SECTION:gresolver + * @short_description: Asynchronous and cancellable DNS resolver + * @include: gio/gio.h + * + * #GResolver provides cancellable synchronous and asynchronous DNS + * resolution, for hostnames (g_resolver_lookup_by_address(), + * g_resolver_lookup_by_name() and their async variants) and SRV + * (service) records (g_resolver_lookup_service()). + **/ + +/** + * GResolver: + * + * The object that handles DNS resolution. Use g_resolver_get_default() + * to get the default resolver. + */ +G_DEFINE_TYPE (GResolver, g_resolver, G_TYPE_OBJECT) + +static void +g_resolver_class_init (GResolverClass *resolver_class) +{ + /* Make sure _g_networking_init() has been called */ + (void) g_inet_address_get_type (); + + /* Initialize _g_resolver_addrinfo_hints */ +#ifdef AI_ADDRCONFIG + _g_resolver_addrinfo_hints.ai_flags |= AI_ADDRCONFIG; +#endif + /* These two don't actually matter, they just get copied into the + * returned addrinfo structures (and then we ignore them). But if + * we leave them unset, we'll get back duplicate answers. + */ + _g_resolver_addrinfo_hints.ai_socktype = SOCK_STREAM; + _g_resolver_addrinfo_hints.ai_protocol = IPPROTO_TCP; +} + +static void +g_resolver_init (GResolver *resolver) +{ + ; +} + +static GResolver *default_resolver; + +/** + * g_resolver_get_default: + * + * Gets the default #GResolver. You should unref it when you are done + * with it. #GResolver may use its reference count as a hint about how + * many threads/processes, etc it should allocate for concurrent DNS + * resolutions. + * + * Return value: the #GResolver. + * + * Since: 2.22 + **/ +GResolver * +g_resolver_get_default (void) +{ + if (!default_resolver) + { + if (g_thread_supported ()) + default_resolver = g_object_new (G_TYPE_THREADED_RESOLVER, NULL); + else + { +#if defined(G_OS_UNIX) + default_resolver = g_object_new (G_TYPE_UNIX_RESOLVER, NULL); +#elif defined(G_OS_WIN32) + default_resolver = g_object_new (G_TYPE_WIN32_RESOLVER, NULL); +#endif + } + } + + return g_object_ref (default_resolver); +} + +/** + * g_resolver_set_default: + * @resolver: the new default #GResolver + * + * Sets @resolver to be the application's default resolver (reffing + * @resolver, and unreffing the previous default resolver, if any). + * Future calls to g_resolver_get_default() will return this resolver. + * + * This can be used if an application wants to perform any sort of DNS + * caching or "pinning"; it can implement its own #GResolver that + * calls the original default resolver for DNS operations, and + * implements its own cache policies on top of that, and then set + * itself as the default resolver for all later code to use. + * + * Since: 2.22 + **/ +void +g_resolver_set_default (GResolver *resolver) +{ + if (default_resolver) + g_object_unref (default_resolver); + default_resolver = g_object_ref (resolver); +} + + +/** + * g_resolver_lookup_by_name: + * @resolver: a #GResolver + * @hostname: the hostname to look up + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Synchronously resolves @hostname to determine its associated IP + * address(es). @hostname may be an ASCII-only or UTF-8 hostname, or + * the textual form of an IP address (in which case this just becomes + * a wrapper around g_inet_address_new_from_string()). + * + * On success, g_resolver_lookup_by_name() will return a #GList of + * #GInetAddress, sorted in order of preference. (That is, you should + * attempt to connect to the first address first, then the second if + * the first fails, etc.) + * + * If the DNS resolution fails, @error (if non-%NULL) will be set to a + * value from #GResolverError. + * + * If @cancellable is non-%NULL, it can be used to cancel the + * operation, in which case @error (if non-%NULL) will be set to + * %G_IO_ERROR_CANCELLED. + * + * Return value: a #GList of #GInetAddress, or %NULL on error. You + * must unref each of the addresses and free the list when you are + * done with it. (You can use g_resolver_free_addresses() to do this.) + * + * Since: 2.22 + **/ +GList * +g_resolver_lookup_by_name (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GError **error) +{ + GInetAddress *addr; + GList *addrs; + gchar *ascii_hostname = NULL; + + g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); + g_return_val_if_fail (hostname != NULL, NULL); + + /* Check if @hostname is just an IP address */ + addr = g_inet_address_new_from_string (hostname); + if (addr) + return g_list_append (NULL, addr); + + if (g_hostname_is_non_ascii (hostname)) + hostname = ascii_hostname = g_hostname_to_ascii (hostname); + + addrs = G_RESOLVER_GET_CLASS (resolver)-> + lookup_by_name (resolver, hostname, cancellable, error); + + g_free (ascii_hostname); + return addrs; +} + +/** + * g_resolver_lookup_by_name_async: + * @resolver: a #GResolver + * @hostname: the hostname to look up the address of + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to call after resolution completes + * @user_data: data for @callback + * + * Begins asynchronously resolving @hostname to determine its + * associated IP address(es), and eventually calls @callback, which + * must call g_resolver_lookup_by_name_finish() to get the result. See + * g_resolver_lookup_by_name() for more details. + * + * Since: 2.22 + **/ +void +g_resolver_lookup_by_name_async (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GInetAddress *addr; + gchar *ascii_hostname = NULL; + + g_return_if_fail (G_IS_RESOLVER (resolver)); + g_return_if_fail (hostname != NULL); + + /* Check if @hostname is just an IP address */ + addr = g_inet_address_new_from_string (hostname); + if (addr) + { + GSimpleAsyncResult *simple; + GList *addrs; + + simple = g_simple_async_result_new (G_OBJECT (resolver), + callback, user_data, + g_resolver_lookup_by_name_async); + + addrs = g_list_append (NULL, addr); + g_simple_async_result_set_op_res_gpointer (simple, addrs, (GDestroyNotify)g_resolver_free_addresses); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + if (g_hostname_is_non_ascii (hostname)) + hostname = ascii_hostname = g_hostname_to_ascii (hostname); + + G_RESOLVER_GET_CLASS (resolver)-> + lookup_by_name_async (resolver, hostname, cancellable, callback, user_data); + + g_free (ascii_hostname); +} + +/** + * g_resolver_lookup_by_name_finish: + * @resolver: a #GResolver + * @result: the result passed to your #GAsyncReadyCallback + * @error: return location for a #GError, or %NULL + * + * Retrieves the result of a call to + * g_resolver_lookup_by_name_async(). + * + * If the DNS resolution failed, @error (if non-%NULL) will be set to + * a value from #GResolverError. If the operation was cancelled, + * @error will be set to %G_IO_ERROR_CANCELLED. + * + * Return value: a #GList of #GInetAddress, or %NULL on error. See + * g_resolver_lookup_by_name() for more details. + * + * Since: 2.22 + **/ +GList * +g_resolver_lookup_by_name_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + /* Handle the stringified-IP-addr case */ + if (g_simple_async_result_get_source_tag (simple) == g_resolver_lookup_by_name_async) + { + GList *addrs; + + addrs = g_simple_async_result_get_op_res_gpointer (simple); + g_simple_async_result_set_op_res_gpointer (simple, NULL, NULL); + return addrs; + } + } + + return G_RESOLVER_GET_CLASS (resolver)-> + lookup_by_name_finish (resolver, result, error); +} + +/** + * g_resolver_free_addresses: + * @addresses: a #GList of #GInetAddress + * + * Frees @addresses (which should be the return value from + * g_resolver_lookup_by_name() or g_resolver_lookup_by_name_finish()). + * (This is a convenience method; you can also simply free the results + * by hand.) + * + * Since: 2.22 + **/ +void +g_resolver_free_addresses (GList *addresses) +{ + GList *a; + + for (a = addresses; a; a = a->next) + g_object_unref (a->data); + g_list_free (addresses); +} + +/** + * g_resolver_lookup_by_address: + * @resolver: a #GResolver + * @address: the address to reverse-resolve + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Synchronously reverse-resolves @address to determine its + * associated hostname. + * + * If the DNS resolution fails, @error (if non-%NULL) will be set to + * a value from #GResolverError. + * + * If @cancellable is non-%NULL, it can be used to cancel the + * operation, in which case @error (if non-%NULL) will be set to + * %G_IO_ERROR_CANCELLED. + * + * Return value: a hostname (either ASCII-only, or in ASCII-encoded + * form), or %NULL on error. + * + * Since: 2.22 + **/ +gchar * +g_resolver_lookup_by_address (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); + g_return_val_if_fail (G_IS_INET_ADDRESS (address), NULL); + + return G_RESOLVER_GET_CLASS (resolver)-> + lookup_by_address (resolver, address, cancellable, error); +} + +/** + * g_resolver_lookup_by_address_async: + * @resolver: a #GResolver + * @address: the address to reverse-resolve + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to call after resolution completes + * @user_data: data for @callback + * + * Begins asynchronously reverse-resolving @address to determine its + * associated hostname, and eventually calls @callback, which must + * call g_resolver_lookup_by_address_finish() to get the final result. + * + * Since: 2.22 + **/ +void +g_resolver_lookup_by_address_async (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (G_IS_RESOLVER (resolver)); + g_return_if_fail (G_IS_INET_ADDRESS (address)); + + G_RESOLVER_GET_CLASS (resolver)-> + lookup_by_address_async (resolver, address, cancellable, callback, user_data); +} + +/** + * g_resolver_lookup_by_address_finish: + * @resolver: a #GResolver + * @result: the result passed to your #GAsyncReadyCallback + * @error: return location for a #GError, or %NULL + * + * Retrieves the result of a previous call to + * g_resolver_lookup_by_address_async(). + * + * If the DNS resolution failed, @error (if non-%NULL) will be set to + * a value from #GResolverError. If the operation was cancelled, + * @error will be set to %G_IO_ERROR_CANCELLED. + * + * Return value: a hostname (either ASCII-only, or in ASCII-encoded + * form), or %NULL on error. + * + * Since: 2.22 + **/ +gchar * +g_resolver_lookup_by_address_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + return G_RESOLVER_GET_CLASS (resolver)-> + lookup_by_address_finish (resolver, result, error); +} + +static gchar * +g_resolver_get_service_rrname (const char *service, + const char *protocol, + const char *domain) +{ + gchar *rrname, *ascii_domain = NULL; + + if (g_hostname_is_non_ascii (domain)) + domain = ascii_domain = g_hostname_to_ascii (domain); + + rrname = g_strdup_printf ("_%s._%s.%s", service, protocol, domain); + + g_free (ascii_domain); + return rrname; +} + +/** + * g_resolver_lookup_service: + * @resolver: a #GResolver + * @service: the service type to look up (eg, "ldap") + * @protocol: the networking protocol to use for @service (eg, "tcp") + * @domain: the DNS domain to look up the service in + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Synchronously performs a DNS SRV lookup for the given @service and + * @protocol in the given @domain and returns an array of #GSrvTarget. + * @domain may be an ASCII-only or UTF-8 hostname. Note also that the + * @service and @protocol arguments do not + * include the leading underscore that appears in the actual DNS + * entry. + * + * On success, g_resolver_lookup_service() will return a #GList of + * #GSrvTarget, sorted in order of preference. (That is, you should + * attempt to connect to the first target first, then the second if + * the first fails, etc.) + * + * If the DNS resolution fails, @error (if non-%NULL) will be set to + * a value from #GResolverError. + * + * If @cancellable is non-%NULL, it can be used to cancel the + * operation, in which case @error (if non-%NULL) will be set to + * %G_IO_ERROR_CANCELLED. + * + * Return value: a #GList of #GSrvTarget, or %NULL on error. You must + * free each of the targets and the list when you are done with it. + * (You can use g_resolver_free_targets() to do this.) + * + * Since: 2.22 + **/ +GList * +g_resolver_lookup_service (GResolver *resolver, + const gchar *service, + const gchar *protocol, + const gchar *domain, + GCancellable *cancellable, + GError **error) +{ + GList *targets; + gchar *rrname; + + g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); + g_return_val_if_fail (service != NULL, NULL); + g_return_val_if_fail (protocol != NULL, NULL); + g_return_val_if_fail (domain != NULL, NULL); + + rrname = g_resolver_get_service_rrname (service, protocol, domain); + + targets = G_RESOLVER_GET_CLASS (resolver)-> + lookup_service (resolver, rrname, cancellable, error); + + g_free (rrname); + return targets; +} + +/** + * g_resolver_lookup_service_async: + * @resolver: a #GResolver + * @service: the service type to look up (eg, "ldap") + * @protocol: the networking protocol to use for @service (eg, "tcp") + * @domain: the DNS domain to look up the service in + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to call after resolution completes + * @user_data: data for @callback + * + * Begins asynchronously performing a DNS SRV lookup for the given + * @service and @protocol in the given @domain, and eventually calls + * @callback, which must call g_resolver_lookup_service_finish() to + * get the final result. See g_resolver_lookup_service() for more + * details. + * + * Since: 2.22 + **/ +void +g_resolver_lookup_service_async (GResolver *resolver, + const gchar *service, + const gchar *protocol, + const gchar *domain, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + gchar *rrname; + + g_return_if_fail (G_IS_RESOLVER (resolver)); + g_return_if_fail (service != NULL); + g_return_if_fail (protocol != NULL); + g_return_if_fail (domain != NULL); + + rrname = g_resolver_get_service_rrname (service, protocol, domain); + + G_RESOLVER_GET_CLASS (resolver)-> + lookup_service_async (resolver, rrname, cancellable, callback, user_data); + + g_free (rrname); +} + +/** + * g_resolver_lookup_service_finish: + * @resolver: a #GResolver + * @result: the result passed to your #GAsyncReadyCallback + * @error: return location for a #GError, or %NULL + * + * Retrieves the result of a previous call to + * g_resolver_lookup_service_async(). + * + * If the DNS resolution failed, @error (if non-%NULL) will be set to + * a value from #GResolverError. If the operation was cancelled, + * @error will be set to %G_IO_ERROR_CANCELLED. + * + * Return value: a #GList of #GSrvTarget, or %NULL on error. See + * g_resolver_lookup_service() for more details. + * + * Since: 2.22 + **/ +GList * +g_resolver_lookup_service_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + return G_RESOLVER_GET_CLASS (resolver)-> + lookup_service_finish (resolver, result, error); +} + +/** + * g_resolver_free_targets: + * @targets: a #GList of #GSrvTarget + * + * Frees @targets (which should be the return value from + * g_resolver_lookup_service() or g_resolver_lookup_service_finish()). + * (This is a convenience method; you can also simply free the + * results by hand.) + * + * Since: 2.22 + **/ +void +g_resolver_free_targets (GList *targets) +{ + GList *t; + + for (t = targets; t; t = t->next) + g_srv_target_free (t->data); + g_list_free (targets); +} + +/** + * g_resolver_error_quark: + * + * Gets the #GResolver Error Quark. + * + * Return value: a #GQuark. + * + * Since: 2.22 + **/ +GQuark +g_resolver_error_quark (void) +{ + return g_quark_from_static_string ("g-resolver-error-quark"); +} + + +static GResolverError +g_resolver_error_from_addrinfo_error (gint err) +{ + switch (err) + { + case EAI_FAIL: + case EAI_NODATA: + case EAI_NONAME: + return G_RESOLVER_ERROR_NOT_FOUND; + + case EAI_AGAIN: + return G_RESOLVER_ERROR_TEMPORARY_FAILURE; + + default: + return G_RESOLVER_ERROR_INTERNAL; + } +} + +struct addrinfo _g_resolver_addrinfo_hints; + +/* Private method to process a getaddrinfo() response. */ +GList * +_g_resolver_addresses_from_addrinfo (const char *hostname, + struct addrinfo *res, + gint gai_retval, + GError **error) +{ + struct addrinfo *ai; + GSocketAddress *sockaddr; + GInetAddress *addr; + GList *addrs; + + if (gai_retval != 0) + { + g_set_error (error, G_RESOLVER_ERROR, + g_resolver_error_from_addrinfo_error (gai_retval), + _("Error resolving '%s': %s"), + hostname, gai_strerror (gai_retval)); + return NULL; + } + + g_return_val_if_fail (res != NULL, NULL); + + addrs = NULL; + for (ai = res; ai; ai = ai->ai_next) + { + sockaddr = g_socket_address_new_from_native (ai->ai_addr, ai->ai_addrlen); + if (!sockaddr || !G_IS_INET_SOCKET_ADDRESS (sockaddr)) + continue; + + addr = g_object_ref (g_inet_socket_address_get_address ((GInetSocketAddress *)sockaddr)); + addrs = g_list_prepend (addrs, addr); + g_object_unref (sockaddr); + } + + return g_list_reverse (addrs); +} + +/* Private method to set up a getnameinfo() request */ +void +_g_resolver_address_to_sockaddr (GInetAddress *address, + struct sockaddr_storage *sa, + gsize *sa_len) +{ + GSocketAddress *sockaddr; + + sockaddr = g_inet_socket_address_new (address, 0); + g_socket_address_to_native (sockaddr, (struct sockaddr *)sa, sizeof (*sa)); + *sa_len = g_socket_address_get_native_size (sockaddr); + g_object_unref (sockaddr); +} + +/* Private method to process a getnameinfo() response. */ +char * +_g_resolver_name_from_nameinfo (GInetAddress *address, + const gchar *name, + gint gni_retval, + GError **error) +{ + if (gni_retval != 0) + { + gchar *phys; + + phys = g_inet_address_to_string (address); + g_set_error (error, G_RESOLVER_ERROR, + g_resolver_error_from_addrinfo_error (gni_retval), + _("Error reverse-resolving '%s': %s"), + phys ? phys : "(unknown)", gai_strerror (gni_retval)); + g_free (phys); + return NULL; + } + + return g_strdup (name); +} + +#if defined(G_OS_UNIX) +/* Private method to process a res_query response into GSrvTargets */ +GList * +_g_resolver_targets_from_res_query (const gchar *rrname, + guchar *answer, + gint len, + gint herr, + GError **error) +{ + gint count; + gchar namebuf[1024]; + guchar *end, *p; + guint16 type, qclass, rdlength, priority, weight, port; + guint32 ttl; + HEADER *header; + GSrvTarget *target; + GList *targets; + + if (len <= 0) + { + GResolverError errnum; + const gchar *format; + + if (len == 0 || herr == HOST_NOT_FOUND || herr == NO_DATA) + { + errnum = G_RESOLVER_ERROR_NOT_FOUND; + format = _("No service record for '%s'"); + } + else if (herr == TRY_AGAIN) + { + errnum = G_RESOLVER_ERROR_TEMPORARY_FAILURE; + format = _("Temporarily unable to resolve '%s'"); + } + else + { + errnum = G_RESOLVER_ERROR_INTERNAL; + format = _("Error resolving '%s'"); + } + + g_set_error (error, G_RESOLVER_ERROR, errnum, format, rrname); + return NULL; + } + + targets = NULL; + + header = (HEADER *)answer; + p = answer + sizeof (HEADER); + end = answer + len; + + /* Skip query */ + count = ntohs (header->qdcount); + while (count-- && p < end) + { + p += dn_expand (answer, end, p, namebuf, sizeof (namebuf)); + p += 4; + } + + /* Read answers */ + count = ntohs (header->ancount); + while (count-- && p < end) + { + p += dn_expand (answer, end, p, namebuf, sizeof (namebuf)); + GETSHORT (type, p); + GETSHORT (qclass, p); + GETLONG (ttl, p); + GETSHORT (rdlength, p); + + if (type != T_SRV || qclass != C_IN) + { + p += rdlength; + continue; + } + + GETSHORT (priority, p); + GETSHORT (weight, p); + GETSHORT (port, p); + p += dn_expand (answer, end, p, namebuf, sizeof (namebuf)); + + target = g_srv_target_new (namebuf, port, priority, weight); + targets = g_list_prepend (targets, target); + } + + return g_srv_target_list_sort (targets); +} +#elif defined(G_OS_WIN32) +/* Private method to process a DnsQuery response into GSrvTargets */ +GList * +_g_resolver_targets_from_DnsQuery (const gchar *rrname, + DNS_STATUS status, + DNS_RECORD *results, + GError **error) +{ + DNS_RECORD *rec; + GSrvTarget *target; + GList *targets; + + if (status != ERROR_SUCCESS) + { + GResolverError errnum; + const gchar *format; + + if (status == DNS_ERROR_RCODE_NAME_ERROR) + { + errnum = G_RESOLVER_ERROR_NOT_FOUND; + format = _("No service record for '%s'"); + } + else if (status == DNS_ERROR_RCODE_SERVER_FAILURE) + { + errnum = G_RESOLVER_ERROR_TEMPORARY_FAILURE; + format = _("Temporarily unable to resolve '%s'"); + } + else + { + errnum = G_RESOLVER_ERROR_INTERNAL; + format = _("Error resolving '%s'"); + } + + g_set_error (error, G_RESOLVER_ERROR, errnum, format, rrname); + return NULL; + } + + targets = NULL; + for (rec = results; rec; rec = rec->pNext) + { + if (rec->wType != DNS_TYPE_SRV) + continue; + + target = g_srv_target_new (rec->Data.SRV.pNameTarget, + rec->Data.SRV.wPort, + rec->Data.SRV.wPriority, + rec->Data.SRV.wWeight); + targets = g_list_prepend (targets, target); + } + + return g_srv_target_list_sort (targets); +} + +#endif + +#define __G_RESOLVER_C__ +#include "gioaliasdef.c" diff --git a/gio/gresolver.h b/gio/gresolver.h new file mode 100644 index 000000000..788851e45 --- /dev/null +++ b/gio/gresolver.h @@ -0,0 +1,159 @@ +/* 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. + */ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __G_RESOLVER_H__ +#define __G_RESOLVER_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_RESOLVER (g_resolver_get_type ()) +#define G_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_RESOLVER, GResolver)) +#define G_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_RESOLVER, GResolverClass)) +#define G_IS_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_RESOLVER)) +#define G_IS_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_RESOLVER)) +#define G_RESOLVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_RESOLVER, GResolverClass)) + +struct _GResolver { + GObject parent_instance; + +}; + +typedef struct { + GObjectClass parent_class; + + GList * ( *lookup_by_name) (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GError **error); + void ( *lookup_by_name_async) (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GList * ( *lookup_by_name_finish) (GResolver *resolver, + GAsyncResult *result, + GError **error); + + gchar * ( *lookup_by_address) (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GError **error); + void ( *lookup_by_address_async) (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gchar * ( *lookup_by_address_finish) (GResolver *resolver, + GAsyncResult *result, + GError **error); + + GList * ( *lookup_service) (GResolver *resolver, + const gchar *rrname, + GCancellable *cancellable, + GError **error); + void ( *lookup_service_async) (GResolver *resolver, + const gchar *rrname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GList * ( *lookup_service_finish) (GResolver *resolver, + GAsyncResult *result, + GError **error); + + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); + void (*_g_reserved6) (void); + +} GResolverClass; + +GType g_resolver_get_type (void) G_GNUC_CONST; +GResolver *g_resolver_get_default (void); +void g_resolver_set_default (GResolver *resolver); + +GList *g_resolver_lookup_by_name (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GError **error); +void g_resolver_lookup_by_name_async (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GList *g_resolver_lookup_by_name_finish (GResolver *resolver, + GAsyncResult *result, + GError **error); + +void g_resolver_free_addresses (GList *addresses); + +gchar *g_resolver_lookup_by_address (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GError **error); +void g_resolver_lookup_by_address_async (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gchar *g_resolver_lookup_by_address_finish (GResolver *resolver, + GAsyncResult *result, + GError **error); + +GList *g_resolver_lookup_service (GResolver *resolver, + const gchar *service, + const gchar *protocol, + const gchar *domain, + GCancellable *cancellable, + GError **error); +void g_resolver_lookup_service_async (GResolver *resolver, + const gchar *service, + const gchar *protocol, + const gchar *domain, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GList *g_resolver_lookup_service_finish (GResolver *resolver, + GAsyncResult *result, + GError **error); + +void g_resolver_free_targets (GList *targets); + +/** + * G_RESOLVER_ERROR: + * + * Error domain for #GResolver. Errors in this domain will be from the + * #GResolverError enumeration. See #GError for more information on + * error domains. + **/ +#define G_RESOLVER_ERROR (g_resolver_error_quark ()) +GQuark g_resolver_error_quark (void); + +G_END_DECLS + +#endif /* __G_RESOLVER_H__ */ diff --git a/gio/gsrvtarget.c b/gio/gsrvtarget.c new file mode 100644 index 000000000..537d81660 --- /dev/null +++ b/gio/gsrvtarget.c @@ -0,0 +1,334 @@ +/* -*- 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 +#include "glibintl.h" + +#include "gsrvtarget.h" + +#include +#include + +#include "gioalias.h" + +/** + * SECTION:gsrvtarget + * @short_description: DNS SRV record target + * @include: gio/gio.h + * + * SRV (service) records are used by some network protocols to provide + * service-specific aliasing and load-balancing. For example, XMPP + * (Jabber) uses SRV records to locate the XMPP server for a domain; + * rather than connecting directly to "example.com" or assuming a + * specific server hostname like "xmpp.example.com", an XMPP client + * would look up the "xmpp-client" SRV record for "example.com", and + * then connect to whatever host was pointed to by that record. + * + * Use g_resolver_lookup_service() or + * g_resolver_lookup_service_async() to find the #GSrvTargets + * for a given service. + **/ + +struct _GSrvTarget { + gchar *hostname; + guint16 port; + + guint16 priority; + guint16 weight; +}; + +/** + * GSrvTarget: + * + * A single target host/port that a network service is running on. + */ + +GType +g_srv_target_get_type (void) +{ + static volatile gsize type_volatile = 0; + + if (g_once_init_enter (&type_volatile)) + { + GType type = g_boxed_type_register_static ( + g_intern_static_string ("GSrvTarget"), + (GBoxedCopyFunc) g_srv_target_copy, + (GBoxedFreeFunc) g_srv_target_free); + g_once_init_leave (&type_volatile, type); + } + return type_volatile; +} + +/** + * g_srv_target_new: + * @hostname: the host that the service is running on + * @port: the port that the service is running on + * @priority: the target's priority + * @weight: the target's weight + * + * Creates a new #GSrvTarget with the given parameters. + * + * You should not need to use this; normally #GSrvTargets are + * created by #GResolver. + * + * Return value: a new #GSrvTarget. + * + * Since: 2.22 + **/ +GSrvTarget * +g_srv_target_new (const gchar *hostname, + guint16 port, + guint16 priority, + guint16 weight) +{ + GSrvTarget *target = g_slice_new0 (GSrvTarget); + + target->hostname = g_strdup (hostname); + target->port = port; + target->priority = priority; + target->weight = weight; + + return target; +} + +/** + * g_srv_target_copy: + * @target: a #GSrvTarget + * + * Copies @target + * + * Return value: a copy of @target + * + * Since: 2.22 + **/ +GSrvTarget * +g_srv_target_copy (GSrvTarget *target) +{ + return g_srv_target_new (target->hostname, target->port, + target->priority, target->weight); +} + +/** + * g_srv_target_free: + * @target: a #GSrvTarget + * + * Frees @target + * + * Since: 2.22 + **/ +void +g_srv_target_free (GSrvTarget *target) +{ + g_free (target->hostname); + g_slice_free (GSrvTarget, target); +} + +/** + * g_srv_target_get_hostname: + * @target: a #GSrvTarget + * + * Gets @target's hostname (in ASCII form; if you are going to present + * this to the user, you should use g_hostname_is_ascii_encoded() to + * check if it contains encoded Unicode segments, and use + * g_hostname_to_unicode() to convert it if it does.) + * + * Return value: @target's hostname + * + * Since: 2.22 + **/ +const gchar * +g_srv_target_get_hostname (GSrvTarget *target) +{ + return target->hostname; +} + +/** + * g_srv_target_get_port: + * @target: a #GSrvTarget + * + * Gets @target's port + * + * Return value: @target's port + * + * Since: 2.22 + **/ +guint16 +g_srv_target_get_port (GSrvTarget *target) +{ + return target->port; +} + +/** + * g_srv_target_get_priority: + * @target: a #GSrvTarget + * + * Gets @target's priority. You should not need to look at this; + * #GResolver already sorts the targets according to the algorithm in + * RFC 2782. + * + * Return value: @target's priority + * + * Since: 2.22 + **/ +guint16 +g_srv_target_get_priority (GSrvTarget *target) +{ + return target->priority; +} + +/** + * g_srv_target_get_weight: + * @target: a #GSrvTarget + * + * Gets @target's weight. You should not need to look at this; + * #GResolver already sorts the targets according to the algorithm in + * RFC 2782. + * + * Return value: @target's weight + * + * Since: 2.22 + **/ +guint16 +g_srv_target_get_weight (GSrvTarget *target) +{ + return target->weight; +} + +gint +compare_target (gconstpointer a, gconstpointer b) +{ + GSrvTarget *ta = (GSrvTarget *)a; + GSrvTarget *tb = (GSrvTarget *)b; + + if (ta->priority == tb->priority) + { + /* Arrange targets of the same priority "in any order, except + * that all those with weight 0 are placed at the beginning of + * the list" + */ + if (ta->weight == 0) + return -1; + else if (tb->weight == 0) + return 1; + else + return g_random_int_range (-1, 1); + } + else + return ta->priority - tb->priority; +} + +/** + * g_srv_target_list_sort: + * @targets: a #GList of #GSrvTarget + * + * Sorts @targets in place according to the algorithm in RFC 2782. + * + * Return value: the head of the sorted list. + * + * Since: 2.22 + **/ +GList * +g_srv_target_list_sort (GList *targets) +{ + gint sum, val, priority, weight; + GList *first, *last, *n; + GSrvTarget *target; + gpointer tmp; + + if (!targets) + return NULL; + + if (!targets->next) + { + target = targets->data; + if (!strcmp (target->hostname, ".")) + { + /* 'A Target of "." means that the service is decidedly not + * available at this domain.' + */ + g_srv_target_free (target); + g_list_free (targets); + return NULL; + } + } + + /* Sort by priority, and partly by weight */ + targets = g_list_sort (targets, compare_target); + + /* For each group of targets with the same priority, rebalance them + * according to weight. + */ + for (first = targets; first; first = last->next) + { + /* Skip @first to a non-0-weight target. */ + while (first && ((GSrvTarget *)first->data)->weight == 0) + first = first->next; + if (!first) + break; + + /* Skip @last to the last target of the same priority. */ + priority = ((GSrvTarget *)first->data)->priority; + last = first; + while (last->next && + ((GSrvTarget *)last->next->data)->priority == priority) + last = last->next; + + /* If there's only one non-0 weight target at this priority, + * we can move on to the next priority level. + */ + if (last == first) + continue; + + /* Randomly reorder the non-0 weight targets, giving precedence + * to the ones with higher weight. RFC 2782 describes this in + * terms of assigning a running sum to each target and building + * a new list. We do things slightly differently, but should get + * the same result. + */ + for (n = first, sum = 0; n != last->next; n = n->next) + sum += ((GSrvTarget *)n->data)->weight; + while (first != last) + { + val = g_random_int_range (0, sum); + for (n = first; n != last; n = n->next) + { + weight = ((GSrvTarget *)n->data)->weight; + if (val < weight) + break; + val -= weight; + } + + tmp = first->data; + first->data = n->data; + n->data = tmp; + + sum -= weight; + first = first->next; + } + } + + return targets; +} + +#define __G_SRV_TARGET_C__ +#include "gioaliasdef.c" diff --git a/gio/gsrvtarget.h b/gio/gsrvtarget.h new file mode 100644 index 000000000..67ce2a9e4 --- /dev/null +++ b/gio/gsrvtarget.h @@ -0,0 +1,52 @@ +/* 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. + */ + +#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __G_SRV_TARGET_H__ +#define __G_SRV_TARGET_H__ + +#include + +G_BEGIN_DECLS + +GType g_srv_target_get_type (void) G_GNUC_CONST; +#define G_TYPE_SRV_TARGET (g_srv_target_get_type ()) + +GSrvTarget *g_srv_target_new (const gchar *hostname, + guint16 port, + guint16 priority, + guint16 weight); +GSrvTarget *g_srv_target_copy (GSrvTarget *target); +void g_srv_target_free (GSrvTarget *target); + +const gchar *g_srv_target_get_hostname (GSrvTarget *target); +guint16 g_srv_target_get_port (GSrvTarget *target); +guint16 g_srv_target_get_priority (GSrvTarget *target); +guint16 g_srv_target_get_weight (GSrvTarget *target); + +GList *g_srv_target_list_sort (GList *targets); + +G_END_DECLS + +#endif /* __G_SRV_TARGET_H__ */ + diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c new file mode 100644 index 000000000..0440a3de8 --- /dev/null +++ b/gio/gthreadedresolver.c @@ -0,0 +1,617 @@ +/* -*- 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 +#include "glibintl.h" + +#include +#include + +#include "gthreadedresolver.h" +#include "gnetworkingprivate.h" + +#include "gcancellable.h" +#include "gsimpleasyncresult.h" +#include "gsocketaddress.h" + +#include "gioalias.h" + +G_DEFINE_TYPE (GThreadedResolver, g_threaded_resolver, G_TYPE_RESOLVER) + +static void threaded_resolver_thread (gpointer thread_data, gpointer pool_data); + +static void +g_threaded_resolver_init (GThreadedResolver *gtr) +{ + if (g_thread_supported ()) + { + gtr->thread_pool = g_thread_pool_new (threaded_resolver_thread, gtr, + -1, FALSE, NULL); + } +} + +static void +finalize (GObject *object) +{ + GThreadedResolver *gtr = G_THREADED_RESOLVER (object); + + g_thread_pool_free (gtr->thread_pool, FALSE, FALSE); + + G_OBJECT_CLASS (g_threaded_resolver_parent_class)->finalize (object); +} + +/* A GThreadedResolverRequest represents a request in progress + * (usually, but see case 1). It is refcounted, to make sure that it + * doesn't get freed too soon. In particular, it can't be freed until + * (a) the resolver thread has finished resolving, (b) the calling + * thread has received an answer, and (c) no other thread could be in + * the process of trying to cancel it. + * + * The possibilities: + * + * 1. Synchronous non-cancellable request: in this case, the request + * is simply done in the calling thread, without using + * GThreadedResolverRequest at all. + * + * 2. Synchronous cancellable request: A req is created with a GCond, + * and 3 refs (for the resolution thread, the calling thread, and + * the cancellation signal handler). + * + * a. If the resolution completes successfully, the thread pool + * function (threaded_resolver_thread()) will call + * g_threaded_resolver_request_complete(), which will detach + * the "cancelled" signal handler (dropping one ref on req) + * and signal the GCond, and then unref the req. The calling + * thread receives the signal from the GCond, processes the + * response, and unrefs the req, causing it to be freed. + * + * b. If the resolution is cancelled before completing, + * request_cancelled() will call + * g_threaded_resolver_request_complete(), which will detach + * the signal handler (as above, unreffing the req), set + * req->error to indicate that it was cancelled, and signal + * the GCond. The calling thread receives the signal from the + * GCond, processes the response, and unrefs the req. + * Eventually, the resolver thread finishes resolving (or + * times out in the resolver) and calls + * g_threaded_resolver_request_complete() again, but + * _request_complete() does nothing this time since the + * request is already complete. The thread pool func then + * unrefs the req, causing it to be freed. + * + * 3. Asynchronous request: A req is created with a GSimpleAsyncResult + * (and no GCond). The calling thread's ref on req is set up to be + * automatically dropped when the async_result is freed. Two + * sub-possibilities: + * + * a. If the resolution completes, the thread pool function + * (threaded_resolver_thread()) will call + * g_threaded_resolver_request_complete(), which will detach + * the "cancelled" signal handler (if it was present) + * (unreffing the req), queue the async_result to complete in + * an idle handler, unref the async_result (which is still + * reffed by the idle handler though), and then unref the req. + * The main thread then invokes the async_result's callback + * and processes the response. When it finishes, the + * async_result drops the ref that was taken by + * g_simple_async_result_complete_in_idle(), which causes the + * async_result to be freed, which causes req to be unreffed + * and freed. + * + * b. If the resolution is cancelled, request_cancelled() will + * call g_threaded_resolver_request_complete(), which will + * detach the signal handler (as above, unreffing the req) set + * req->error to indicate that it was cancelled, and queue and + * unref the async_result. The main thread completes the + * async_request and unrefs it and the req, as above. + * Eventually, the resolver thread finishes resolving (or + * times out in the resolver) and calls + * g_threaded_resolver_request_complete() again, but + * _request_complete() does nothing this time since the + * request is already complete. The thread pool func then + * unrefs the req, causing it to be freed. + * + * g_threaded_resolver_request_complete() ensures that if the request + * completes and cancels "at the same time" that only one of the two + * conditions gets processed. + */ + +typedef struct _GThreadedResolverRequest GThreadedResolverRequest; +typedef void (*GThreadedResolverResolveFunc) (GThreadedResolverRequest *, GError **); +typedef void (*GThreadedResolverFreeFunc) (GThreadedResolverRequest *); + +struct _GThreadedResolverRequest { + GThreadedResolverResolveFunc resolve_func; + GThreadedResolverFreeFunc free_func; + + union { + struct { + gchar *hostname; + GList *addresses; + } name; + struct { + GInetAddress *address; + gchar *name; + } address; + struct { + gchar *rrname; + GList *targets; + } service; + } u; + + GCancellable *cancellable; + GError *error; + + GMutex *mutex; + guint ref_count; + + GCond *cond; + GSimpleAsyncResult *async_result; + gboolean complete; + +}; + +static void g_threaded_resolver_request_unref (GThreadedResolverRequest *req); +static void request_cancelled (GCancellable *cancellable, gpointer req); +static void request_cancelled_disconnect_notify (gpointer req, GClosure *closure); + +static GThreadedResolverRequest * +g_threaded_resolver_request_new (GThreadedResolverResolveFunc resolve_func, + GThreadedResolverFreeFunc free_func, + GCancellable *cancellable) +{ + GThreadedResolverRequest *req; + + req = g_slice_new0 (GThreadedResolverRequest); + req->resolve_func = resolve_func; + req->free_func = free_func; + + /* Initial refcount is 2; one for the caller and one for resolve_func */ + req->ref_count = 2; + + if (g_thread_supported ()) + req->mutex = g_mutex_new (); + /* Initially locked; caller must unlock */ + g_mutex_lock (req->mutex); + + if (cancellable) + { + req->ref_count++; + req->cancellable = g_object_ref (cancellable); + g_signal_connect_data (cancellable, "cancelled", + G_CALLBACK (request_cancelled), req, + request_cancelled_disconnect_notify, 0); + } + + return req; +} + +static void +g_threaded_resolver_request_unref (GThreadedResolverRequest *req) +{ + guint ref_count; + + g_mutex_lock (req->mutex); + ref_count = --req->ref_count; + g_mutex_unlock (req->mutex); + if (ref_count > 0) + return; + + g_mutex_free (req->mutex); + + if (req->cond) + g_cond_free (req->cond); + + if (req->error) + g_error_free (req->error); + + if (req->free_func) + req->free_func (req); + + /* We don't have to free req->cancellable or req->async_result, + * since (if set), they must already have been freed by + * request_complete() in order to get here. + */ + + g_slice_free (GThreadedResolverRequest, req); +} + +static void +g_threaded_resolver_request_complete (GThreadedResolverRequest *req, + gboolean cancelled) +{ + g_mutex_lock (req->mutex); + if (req->complete) + { + /* The req was cancelled, and now it has finished resolving as + * well. But we have nowhere to send the result, so just return. + */ + g_mutex_unlock (req->mutex); + return; + } + + req->complete = TRUE; + g_mutex_unlock (req->mutex); + + if (req->cancellable) + { + /* Possibly propagate a cancellation error */ + if (cancelled && !req->error) + g_cancellable_set_error_if_cancelled (req->cancellable, &req->error); + + /* Drop the signal handler's ref on @req */ + g_signal_handlers_disconnect_by_func (req->cancellable, request_cancelled, req); + g_object_unref (req->cancellable); + req->cancellable = NULL; + } + + if (req->cond) + g_cond_signal (req->cond); + else if (req->async_result) + { + if (req->error) + g_simple_async_result_set_from_error (req->async_result, req->error); + g_simple_async_result_complete_in_idle (req->async_result); + + /* Drop our ref on the async_result, which will eventually cause + * it to drop its ref on req. + */ + g_object_unref (req->async_result); + req->async_result = NULL; + } +} + +static void +request_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + GThreadedResolverRequest *req = user_data; + + g_threaded_resolver_request_complete (req, TRUE); + + /* We can't actually cancel the resolver thread; it will eventually + * complete on its own and call request_complete() again, which will + * do nothing the second time. + */ +} + +static void +request_cancelled_disconnect_notify (gpointer req, + GClosure *closure) +{ + g_threaded_resolver_request_unref (req); +} + +static void +threaded_resolver_thread (gpointer thread_data, + gpointer pool_data) +{ + GThreadedResolverRequest *req = thread_data; + + req->resolve_func (req, &req->error); + g_threaded_resolver_request_complete (req, FALSE); + g_threaded_resolver_request_unref (req); +} + +static void +resolve_sync (GThreadedResolver *gtr, + GThreadedResolverRequest *req, + GError **error) +{ + if (!req->cancellable || !gtr->thread_pool) + { + req->resolve_func (req, error); + return; + } + + req->cond = g_cond_new (); + g_thread_pool_push (gtr->thread_pool, req, NULL); + g_cond_wait (req->cond, req->mutex); + g_mutex_unlock (req->mutex); + + if (req->error) + { + g_propagate_error (error, req->error); + req->error = NULL; + } +} + +static void +resolve_async (GThreadedResolver *gtr, + GThreadedResolverRequest *req, + GAsyncReadyCallback callback, + gpointer user_data, + gpointer tag) +{ + req->async_result = g_simple_async_result_new (G_OBJECT (gtr), + callback, user_data, tag); + g_simple_async_result_set_op_res_gpointer (req->async_result, req, NULL); + g_thread_pool_push (gtr->thread_pool, req, NULL); + g_mutex_unlock (req->mutex); +} + +static GThreadedResolverRequest * +resolve_finish (GResolver *resolver, + GAsyncResult *result, + gpointer tag, + GError **error) +{ + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), tag), NULL); + + return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)); +} + +static void +do_lookup_by_name (GThreadedResolverRequest *req, + GError **error) +{ + struct addrinfo *res = NULL; + gint retval; + + retval = getaddrinfo (req->u.name.hostname, NULL, + &_g_resolver_addrinfo_hints, &res); + req->u.name.addresses = + _g_resolver_addresses_from_addrinfo (req->u.name.hostname, res, retval, error); + if (res) + freeaddrinfo (res); +} + +static GList * +lookup_by_name (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GError **error) +{ + GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver); + GThreadedResolverRequest *req; + GList *addresses; + + req = g_threaded_resolver_request_new (do_lookup_by_name, NULL, cancellable); + req->u.name.hostname = (gchar *)hostname; + resolve_sync (gtr, req, error); + + addresses = req->u.name.addresses; + g_threaded_resolver_request_unref (req); + return addresses; +} + +static void +free_lookup_by_name (GThreadedResolverRequest *req) +{ + g_free (req->u.name.hostname); + if (req->u.name.addresses) + g_resolver_free_addresses (req->u.name.addresses); +} + +static void +lookup_by_name_async (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver); + GThreadedResolverRequest *req; + + req = g_threaded_resolver_request_new (do_lookup_by_name, free_lookup_by_name, + cancellable); + req->u.name.hostname = g_strdup (hostname); + resolve_async (gtr, req, callback, user_data, lookup_by_name_async); +} + +static GList * +lookup_by_name_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + GThreadedResolverRequest *req; + GList *addresses; + + req = resolve_finish (resolver, result, lookup_by_name_async, error); + addresses = req->u.name.addresses; + req->u.name.addresses = NULL; + return addresses; +} + + +static void +do_lookup_by_address (GThreadedResolverRequest *req, + GError **error) +{ + struct sockaddr_storage sockaddr; + gsize sockaddr_size; + gchar name[NI_MAXHOST]; + gint retval; + + _g_resolver_address_to_sockaddr (req->u.address.address, + &sockaddr, &sockaddr_size); + + retval = getnameinfo ((struct sockaddr *)&sockaddr, sockaddr_size, + name, sizeof (name), NULL, 0, NI_NAMEREQD); + req->u.address.name = _g_resolver_name_from_nameinfo (req->u.address.address, + name, retval, error); +} + +static gchar * +lookup_by_address (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GError **error) +{ + GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver); + GThreadedResolverRequest *req; + gchar *name; + + req = g_threaded_resolver_request_new (do_lookup_by_address, NULL, cancellable); + req->u.address.address = address; + resolve_sync (gtr, req, error); + + name = req->u.address.name; + g_threaded_resolver_request_unref (req); + return name; +} + +static void +free_lookup_by_address (GThreadedResolverRequest *req) +{ + g_object_unref (req->u.address.address); + if (req->u.address.name) + g_free (req->u.address.name); +} + +static void +lookup_by_address_async (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver); + GThreadedResolverRequest *req; + + req = g_threaded_resolver_request_new (do_lookup_by_address, + free_lookup_by_address, + cancellable); + req->u.address.address = g_object_ref (address); + resolve_async (gtr, req, callback, user_data, lookup_by_address_async); +} + +static gchar * +lookup_by_address_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + GThreadedResolverRequest *req; + gchar *name; + + req = resolve_finish (resolver, result, lookup_by_address_async, error); + name = req->u.address.name; + req->u.address.name = NULL; + return name; +} + + +static void +do_lookup_service (GThreadedResolverRequest *req, + GError **error) +{ +#if defined(G_OS_UNIX) + gint len, herr; + guchar answer[1024]; +#elif defined(G_OS_WIN32) + DNS_STATUS status; + DNS_RECORD *results; +#endif + +#if defined(G_OS_UNIX) + len = res_query (req->u.service.rrname, C_IN, T_SRV, answer, sizeof (answer)); + herr = h_errno; + req->u.service.targets = _g_resolver_targets_from_res_query (req->u.service.rrname, answer, len, herr, error); +#elif defined(G_OS_WIN32) + status = DnsQuery_A (req->u.service.rrname, DNS_TYPE_SRV, + DNS_QUERY_STANDARD, NULL, &results, NULL); + req->u.service.targets = _g_resolver_targets_from_DnsQuery (req->u.service.rrname, status, results, error); + DnsRecordListFree (results, DnsFreeRecordList); +#endif +} + +static GList * +lookup_service (GResolver *resolver, + const gchar *rrname, + GCancellable *cancellable, + GError **error) +{ + GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver); + GThreadedResolverRequest *req; + GList *targets; + + req = g_threaded_resolver_request_new (do_lookup_service, NULL, cancellable); + req->u.service.rrname = (char *)rrname; + resolve_sync (gtr, req, error); + + targets = req->u.service.targets; + g_threaded_resolver_request_unref (req); + return targets; +} + +static void +free_lookup_service (GThreadedResolverRequest *req) +{ + g_free (req->u.service.rrname); + if (req->u.service.targets) + g_resolver_free_targets (req->u.service.targets); +} + +static void +lookup_service_async (GResolver *resolver, + const char *rrname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver); + GThreadedResolverRequest *req; + + req = g_threaded_resolver_request_new (do_lookup_service, + free_lookup_service, + cancellable); + req->u.service.rrname = g_strdup (rrname); + resolve_async (gtr, req, callback, user_data, lookup_service_async); +} + +static GList * +lookup_service_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + GThreadedResolverRequest *req; + GList *targets; + + req = resolve_finish (resolver, result, lookup_service_async, error); + targets = req->u.service.targets; + req->u.service.targets = NULL; + return targets; +} + + +static void +g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class) +{ + GResolverClass *resolver_class = G_RESOLVER_CLASS (threaded_class); + GObjectClass *object_class = G_OBJECT_CLASS (threaded_class); + + resolver_class->lookup_by_name = lookup_by_name; + 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 = lookup_by_address; + resolver_class->lookup_by_address_async = lookup_by_address_async; + resolver_class->lookup_by_address_finish = lookup_by_address_finish; + resolver_class->lookup_service = lookup_service; + resolver_class->lookup_service_async = lookup_service_async; + resolver_class->lookup_service_finish = lookup_service_finish; + + object_class->finalize = finalize; +} + +#define __G_THREADED_RESOLVER_C__ +#include "gioaliasdef.c" diff --git a/gio/gthreadedresolver.h b/gio/gthreadedresolver.h new file mode 100644 index 000000000..79d3327a0 --- /dev/null +++ b/gio/gthreadedresolver.h @@ -0,0 +1,50 @@ +/* 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. + */ + +#ifndef __G_THREADED_RESOLVER_H__ +#define __G_THREADED_RESOLVER_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_THREADED_RESOLVER (g_threaded_resolver_get_type ()) +#define G_THREADED_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_THREADED_RESOLVER, GThreadedResolver)) +#define G_THREADED_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_THREADED_RESOLVER, GThreadedResolverClass)) +#define G_IS_THREADED_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_THREADED_RESOLVER)) +#define G_IS_THREADED_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_THREADED_RESOLVER)) +#define G_THREADED_RESOLVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_THREADED_RESOLVER, GThreadedResolverClass)) + +typedef struct { + GResolver parent_instance; + + GThreadPool *thread_pool; +} GThreadedResolver; + +typedef struct { + GResolverClass parent_class; + +} GThreadedResolverClass; + +GType g_threaded_resolver_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __G_RESOLVER_H__ */ diff --git a/gio/gunixresolver.c b/gio/gunixresolver.c new file mode 100644 index 000000000..36bb04661 --- /dev/null +++ b/gio/gunixresolver.c @@ -0,0 +1,433 @@ +/* -*- 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 +#include "glibintl.h" + +#include +#include +#include + +#include "gunixresolver.h" +#include "gnetworkingprivate.h" + +#include "gcancellable.h" +#include "gsimpleasyncresult.h" +#include "gsocketaddress.h" + +#include "gioalias.h" + +G_DEFINE_TYPE (GUnixResolver, g_unix_resolver, G_TYPE_THREADED_RESOLVER) + +static gboolean g_unix_resolver_watch (GIOChannel *iochannel, + GIOCondition condition, + gpointer user_data); + +static void +g_unix_resolver_init (GUnixResolver *gur) +{ + gint fd; + GIOChannel *io; + + /* FIXME: how many workers? */ + gur->asyncns = _g_asyncns_new (2); + + fd = _g_asyncns_fd (gur->asyncns); + io = g_io_channel_unix_new (fd); + gur->watch = g_io_add_watch (io, G_IO_IN | G_IO_HUP | G_IO_ERR, + g_unix_resolver_watch, gur); + g_io_channel_unref (io); +} + +static void +g_unix_resolver_finalize (GObject *object) +{ + GUnixResolver *gur = G_UNIX_RESOLVER (object); + + if (gur->watch) + g_source_remove (gur->watch); + _g_asyncns_free (gur->asyncns); + + G_OBJECT_CLASS (g_unix_resolver_parent_class)->finalize (object); +} + +/* 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: An appropriate _g_asyncns_query_t is created, and + * then a GUnixResolverRequest is created with that query and a + * GSimpleAsyncResult. Two sub-possibilities: + * + * a. The resolution completes: g_unix_resolver_watch() sees that + * the request has completed, and calls + * g_unix_resolver_request_complete(), which detaches the + * "cancelled" signal handler (if it was present) and then + * immediately completes the async_result (since + * g_unix_resolver_watch() is already run from main-loop + * time.) After completing the async_result, it unrefs it, + * causing the req to be freed as well. + * + * b. The resolution is cancelled: request_cancelled() calls + * _g_asyncns_cancel() to cancel the resolution. Then it calls + * g_unix_resolver_request_complete(), which detaches the + * signal handler, and queues async_result to complete in an + * idle handler. It then unrefs the async_result to ensure + * that after its callback runs, it will be destroyed, in turn + * causing the req to be freed. Because the asyncns resolution + * was cancelled, g_unix_resolver_watch() will never be + * triggered for this req. + * + * Since there's only a single thread, it's not possible for the + * request to both complete and be cancelled "at the same time", + * and each of the two possibilities takes steps to block the other + * from being able to happen later, so it's always safe to free req + * after the async_result completes. + */ + +typedef struct _GUnixResolverRequest GUnixResolverRequest; +typedef void (*GUnixResolverFreeFunc) (GUnixResolverRequest *); + +struct _GUnixResolverRequest { + GUnixResolver *gur; + + _g_asyncns_query_t *qy; + union { + gchar *hostname; + GInetAddress *address; + gchar *service; + } u; + GUnixResolverFreeFunc free_func; + + GCancellable *cancellable; + GSimpleAsyncResult *async_result; + +}; + +static void g_unix_resolver_request_free (GUnixResolverRequest *req); +static void request_cancelled (GCancellable *cancellable, + gpointer user_data); + +static GUnixResolverRequest * +g_unix_resolver_request_new (GUnixResolver *gur, + _g_asyncns_query_t *qy, + GUnixResolverFreeFunc free_func, + GCancellable *cancellable, + GSimpleAsyncResult *async_result) +{ + GUnixResolverRequest *req; + + req = g_slice_new0 (GUnixResolverRequest); + req->gur = g_object_ref (gur); + req->qy = qy; + req->free_func = free_func; + + if (cancellable) + { + req->cancellable = g_object_ref (cancellable); + g_signal_connect (cancellable, "cancelled", + G_CALLBACK (request_cancelled), req); + } + + req->async_result = g_object_ref (async_result); + + g_simple_async_result_set_op_res_gpointer (req->async_result, req, (GDestroyNotify)g_unix_resolver_request_free); + + return req; +} + +static void +g_unix_resolver_request_free (GUnixResolverRequest *req) +{ + /* If the user didn't call _finish the qy will still be around. */ + if (req->qy) + _g_asyncns_cancel (req->gur->asyncns, req->qy); + + /* We don't have to free req->cancellable and req->async_result, + * since they must already have been freed if we're here. + */ + + g_slice_free (GUnixResolverRequest, req); +} + +static void +g_unix_resolver_request_complete (GUnixResolverRequest *req, + gboolean need_idle) +{ + if (req->cancellable) + { + g_signal_handlers_disconnect_by_func (req->cancellable, request_cancelled, req); + g_object_unref (req->cancellable); + req->cancellable = NULL; + } + + if (need_idle) + g_simple_async_result_complete_in_idle (req->async_result); + else + g_simple_async_result_complete (req->async_result); + + /* If we completed_in_idle, that will have taken an extra ref on + * req->async_result; if not, then we're already done. Either way we + * need to unref the async_result to make sure it eventually is + * destroyed, causing req to be freed. + */ + g_object_unref (req->async_result); +} + +static void +request_cancelled (GCancellable *cancellable, + gpointer user_data) +{ + GUnixResolverRequest *req = user_data; + GError *error = NULL; + + _g_asyncns_cancel (req->gur->asyncns, req->qy); + req->qy = NULL; + + g_cancellable_set_error_if_cancelled (cancellable, &error); + g_simple_async_result_set_from_error (req->async_result, error); + g_error_free (error); + + g_unix_resolver_request_complete (req, TRUE); +} + +static gboolean +g_unix_resolver_watch (GIOChannel *iochannel, + GIOCondition condition, + gpointer user_data) +{ + GUnixResolver *gur = user_data; + _g_asyncns_query_t *qy; + GUnixResolverRequest *req; + + if (condition & (G_IO_HUP | G_IO_ERR)) + { + /* Shouldn't happen. Should we create a new asyncns? FIXME */ + g_warning ("asyncns died"); + gur->watch = 0; + return FALSE; + } + + while (_g_asyncns_wait (gur->asyncns, FALSE) == 0 && + (qy = _g_asyncns_getnext (gur->asyncns)) != NULL) + { + req = _g_asyncns_getuserdata (gur->asyncns, qy); + g_unix_resolver_request_complete (req, FALSE); + } + + return TRUE; +} + +static GUnixResolverRequest * +resolve_async (GUnixResolver *gur, + _g_asyncns_query_t *qy, + GUnixResolverFreeFunc free_func, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data, + gpointer tag) +{ + GSimpleAsyncResult *result; + GUnixResolverRequest *req; + + result = g_simple_async_result_new (G_OBJECT (gur), callback, user_data, tag); + req = g_unix_resolver_request_new (gur, qy, free_func, cancellable, result); + g_object_unref (result); + _g_asyncns_setuserdata (gur->asyncns, qy, req); + + return req; +} + +static void +lookup_by_name_free (GUnixResolverRequest *req) +{ + g_free (req->u.hostname); +} + +static void +lookup_by_name_async (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GUnixResolver *gur = G_UNIX_RESOLVER (resolver); + GUnixResolverRequest *req; + _g_asyncns_query_t *qy; + + qy = _g_asyncns_getaddrinfo (gur->asyncns, hostname, NULL, + &_g_resolver_addrinfo_hints); + req = resolve_async (gur, qy, lookup_by_name_free, cancellable, + callback, user_data, lookup_by_name_async); + req->u.hostname = g_strdup (hostname); +} + +static GList * +lookup_by_name_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GUnixResolverRequest *req; + struct addrinfo *res; + gint retval; + GList *addresses; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_by_name_async), FALSE); + simple = G_SIMPLE_ASYNC_RESULT (result); + + req = g_simple_async_result_get_op_res_gpointer (simple); + retval = _g_asyncns_getaddrinfo_done (req->gur->asyncns, req->qy, &res); + req->qy = NULL; + addresses = _g_resolver_addresses_from_addrinfo (req->u.hostname, res, retval, error); + if (res) + freeaddrinfo (res); + + return addresses; +} + + +static void +lookup_by_address_free (GUnixResolverRequest *req) +{ + g_object_unref (req->u.address); +} + +static void +lookup_by_address_async (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GUnixResolver *gur = G_UNIX_RESOLVER (resolver); + GUnixResolverRequest *req; + _g_asyncns_query_t *qy; + struct sockaddr_storage sockaddr; + gsize sockaddr_size; + + _g_resolver_address_to_sockaddr (address, &sockaddr, &sockaddr_size); + qy = _g_asyncns_getnameinfo (gur->asyncns, + (struct sockaddr *)&sockaddr, sockaddr_size, + NI_NAMEREQD, TRUE, FALSE); + req = resolve_async (gur, qy, lookup_by_address_free, cancellable, + callback, user_data, lookup_by_address_async); + req->u.address = g_object_ref (address); +} + +static gchar * +lookup_by_address_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GUnixResolverRequest *req; + gchar host[NI_MAXHOST], *name; + gint retval; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_by_address_async), FALSE); + simple = G_SIMPLE_ASYNC_RESULT (result); + + req = g_simple_async_result_get_op_res_gpointer (simple); + retval = _g_asyncns_getnameinfo_done (req->gur->asyncns, req->qy, + host, sizeof (host), NULL, 0); + req->qy = NULL; + name = _g_resolver_name_from_nameinfo (req->u.address, host, retval, error); + + return name; +} + + +static void +lookup_service_free (GUnixResolverRequest *req) +{ + g_free (req->u.service); +} + +static void +lookup_service_async (GResolver *resolver, + const char *rrname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GUnixResolver *gur = G_UNIX_RESOLVER (resolver); + GUnixResolverRequest *req; + _g_asyncns_query_t *qy; + + qy = _g_asyncns_res_query (gur->asyncns, rrname, C_IN, T_SRV); + req = resolve_async (gur, qy, lookup_service_free, cancellable, + callback, user_data, lookup_service_async); + req->u.service = g_strdup (rrname); +} + +static GList * +lookup_service_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + GUnixResolverRequest *req; + guchar *answer; + gint len, herr; + GList *targets; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_service_async), FALSE); + simple = G_SIMPLE_ASYNC_RESULT (result); + + req = g_simple_async_result_get_op_res_gpointer (simple); + len = _g_asyncns_res_done (req->gur->asyncns, req->qy, &answer); + req->qy = NULL; + if (len < 0) + herr = h_errno; + else + herr = 0; + + targets = _g_resolver_targets_from_res_query (req->u.service, answer, len, herr, error); + _g_asyncns_freeanswer (answer); + + return targets; +} + + +static void +g_unix_resolver_class_init (GUnixResolverClass *unix_class) +{ + GResolverClass *resolver_class = G_RESOLVER_CLASS (unix_class); + GObjectClass *object_class = G_OBJECT_CLASS (unix_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; + + object_class->finalize = g_unix_resolver_finalize; +} + +#define __G_UNIX_RESOLVER_C__ +#include "gioaliasdef.c" diff --git a/gio/gunixresolver.h b/gio/gunixresolver.h new file mode 100644 index 000000000..cf7765b72 --- /dev/null +++ b/gio/gunixresolver.h @@ -0,0 +1,53 @@ +/* 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. + */ + +#ifndef __G_UNIX_RESOLVER_H__ +#define __G_UNIX_RESOLVER_H__ + +#include +#include "libasyncns/asyncns.h" + +G_BEGIN_DECLS + +#define G_TYPE_UNIX_RESOLVER (g_unix_resolver_get_type ()) +#define G_UNIX_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_RESOLVER, GUnixResolver)) +#define G_UNIX_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_UNIX_RESOLVER, GUnixResolverClass)) +#define G_IS_UNIX_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_RESOLVER)) +#define G_IS_UNIX_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_UNIX_RESOLVER)) +#define G_UNIX_RESOLVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_UNIX_RESOLVER, GUnixResolverClass)) + +typedef struct { + GThreadedResolver parent_instance; + + _g_asyncns_t *asyncns; + guint watch; + +} GUnixResolver; + +typedef struct { + GThreadedResolverClass parent_class; + +} GUnixResolverClass; + +GType g_unix_resolver_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __G_RESOLVER_H__ */ diff --git a/gio/gwin32resolver.c b/gio/gwin32resolver.c new file mode 100644 index 000000000..cab45cc24 --- /dev/null +++ b/gio/gwin32resolver.c @@ -0,0 +1,481 @@ +/* -*- 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 +#include "glibintl.h" + +#include +#include + +#include "gwin32resolver.h" +#include "gnetworkingprivate.h" + +#include "gcancellable.h" +#include "gsimpleasyncresult.h" +#include "gsocketaddress.h" + +#include "gioalias.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; + guint 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_remove (req->cancelled_idle); + req->cancelled_idle = 0; + } + 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; + + req->cancelled_idle = 0; + + 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 id. + */ + req->cancelled_idle = g_idle_add (request_cancelled_idle, req); +} + +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, NULL); + return source; +} + +#define __G_WIN32_RESOLVER_C__ +#include "gioaliasdef.c" diff --git a/gio/gwin32resolver.h b/gio/gwin32resolver.h new file mode 100644 index 000000000..5cc6a2c67 --- /dev/null +++ b/gio/gwin32resolver.h @@ -0,0 +1,49 @@ +/* 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. + */ + +#ifndef __G_WIN32_RESOLVER_H__ +#define __G_WIN32_RESOLVER_H__ + +#include + +G_BEGIN_DECLS + +#define G_TYPE_WIN32_RESOLVER (g_win32_resolver_get_type ()) +#define G_WIN32_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_WIN32_RESOLVER, GWin32Resolver)) +#define G_WIN32_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_WIN32_RESOLVER, GWin32ResolverClass)) +#define G_IS_WIN32_RESOLVER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_WIN32_RESOLVER)) +#define G_IS_WIN32_RESOLVER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_WIN32_RESOLVER)) +#define G_WIN32_RESOLVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_WIN32_RESOLVER, GWin32ResolverClass)) + +typedef struct { + GThreadedResolver parent_instance; + +} GWin32Resolver; + +typedef struct { + GThreadedResolverClass parent_class; + +} GWin32ResolverClass; + +GType g_win32_resolver_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __G_RESOLVER_H__ */ diff --git a/gio/libasyncns/Makefile.am b/gio/libasyncns/Makefile.am new file mode 100644 index 000000000..80b20a2a0 --- /dev/null +++ b/gio/libasyncns/Makefile.am @@ -0,0 +1,15 @@ +## Process this file with automake to produce Makefile.in +include $(top_srcdir)/Makefile.decl + +INCLUDES = -I$(top_srcdir) + +noinst_LTLIBRARIES = libasyncns.la + +libasyncns_la_SOURCES = \ + asyncns.c \ + asyncns.h \ + g-asyncns.h + +libasyncns_la_LIBADD = $(LIBASYNCNS_LIBADD) + +EXTRA_DIST += README update.sh diff --git a/gio/libasyncns/README b/gio/libasyncns/README new file mode 100644 index 000000000..b9262917c --- /dev/null +++ b/gio/libasyncns/README @@ -0,0 +1,7 @@ +The sources are derived from Lennart Poettering's libasyncns library: + + http://0pointer.de/lennart/projects/libasyncns/ + +The 'update.sh' script in this directory, when pointed at +the original sources updates the files in this directory +to the new version diff --git a/gio/libasyncns/asyncns.c b/gio/libasyncns/asyncns.c new file mode 100644 index 000000000..3c4db73d1 --- /dev/null +++ b/gio/libasyncns/asyncns.c @@ -0,0 +1,1498 @@ +/*** + This file is part of libasyncns. + + Copyright 2005-2008 Lennart Poettering + + libasyncns 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. + + libasyncns 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 libasyncns. If not, see + . +***/ + +#ifdef HAVE_CONFIG_H +#include "g-asyncns.h" +#endif + +/* #undef HAVE_PTHREAD */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_ARPA_NAMESER_COMPAT_H +#include +#endif + +#ifdef HAVE_SYS_PRCTL_H +#include +#endif + +#if HAVE_PTHREAD +#include +#endif + +#include "asyncns.h" + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#define MAX_WORKERS 16 +#define MAX_QUERIES 256 +#define BUFSIZE (10240) + +typedef enum { + REQUEST_ADDRINFO, + RESPONSE_ADDRINFO, + REQUEST_NAMEINFO, + RESPONSE_NAMEINFO, + REQUEST_RES_QUERY, + REQUEST_RES_SEARCH, + RESPONSE_RES, + REQUEST_TERMINATE, + RESPONSE_DIED +} query_type_t; + +enum { + REQUEST_RECV_FD = 0, + REQUEST_SEND_FD = 1, + RESPONSE_RECV_FD = 2, + RESPONSE_SEND_FD = 3, + MESSAGE_FD_MAX = 4 +}; + +struct asyncns { + int fds[4]; + +#ifndef HAVE_PTHREAD + pid_t workers[MAX_WORKERS]; +#else + pthread_t workers[MAX_WORKERS]; +#endif + unsigned valid_workers; + + unsigned current_id, current_index; + _g_asyncns_query_t* queries[MAX_QUERIES]; + + _g_asyncns_query_t *done_head, *done_tail; + + int n_queries; + int dead; +}; + +struct _g_asyncns_query { + _g_asyncns_t *asyncns; + int done; + unsigned id; + query_type_t type; + _g_asyncns_query_t *done_next, *done_prev; + int ret; + int _errno; + int _h_errno; + struct addrinfo *addrinfo; + char *serv, *host; + void *userdata; +}; + +typedef struct rheader { + query_type_t type; + unsigned id; + size_t length; +} rheader_t; + +typedef struct addrinfo_request { + struct rheader header; + int hints_is_null; + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t node_len, service_len; +} addrinfo_request_t; + +typedef struct addrinfo_response { + struct rheader header; + int ret; + int _errno; + int _h_errno; + /* followed by addrinfo_serialization[] */ +} addrinfo_response_t; + +typedef struct addrinfo_serialization { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + size_t canonname_len; + /* Followed by ai_addr amd ai_canonname with variable lengths */ +} addrinfo_serialization_t; + +typedef struct nameinfo_request { + struct rheader header; + int flags; + socklen_t sockaddr_len; + int gethost, getserv; +} nameinfo_request_t; + +typedef struct nameinfo_response { + struct rheader header; + size_t hostlen, servlen; + int ret; + int _errno; + int _h_errno; +} nameinfo_response_t; + +typedef struct res_query_request { + struct rheader header; + int class; + int type; + size_t dname_len; +} res_request_t; + +typedef struct res_query_response { + struct rheader header; + int ret; + int _errno; + int _h_errno; +} res_response_t; + +#ifndef HAVE_STRNDUP + +static char *strndup(const char *s, size_t l) { + size_t a; + char *n; + + a = strlen(s); + if (a > l) + a = l; + + if (!(n = malloc(a+1))) + return NULL; + + memcpy(n, s, a); + n[a] = 0; + + return n; +} + +#endif + +#ifndef HAVE_PTHREAD + +static int close_allv(const int except_fds[]) { + struct rlimit rl; + int fd; + +#ifdef __linux__ + + DIR *d; + + assert(except_fds); + + if ((d = opendir("/proc/self/fd"))) { + + struct dirent *de; + + while ((de = readdir(d))) { + int found; + long l; + char *e = NULL; + int i; + + if (de->d_name[0] == '.') + continue; + + errno = 0; + l = strtol(de->d_name, &e, 10); + if (errno != 0 || !e || *e) { + closedir(d); + errno = EINVAL; + return -1; + } + + fd = (int) l; + + if ((long) fd != l) { + closedir(d); + errno = EINVAL; + return -1; + } + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + found = 0; + for (i = 0; except_fds[i] >= 0; i++) + if (except_fds[i] == fd) { + found = 1; + break; + } + + if (found) + continue; + + if (close(fd) < 0) { + int saved_errno; + + saved_errno = errno; + closedir(d); + errno = saved_errno; + + return -1; + } + } + + closedir(d); + return 0; + } + +#endif + + if (getrlimit(RLIMIT_NOFILE, &rl) < 0) + return -1; + + for (fd = 0; fd < (int) rl.rlim_max; fd++) { + int i; + + if (fd <= 3) + continue; + + for (i = 0; except_fds[i] >= 0; i++) + if (except_fds[i] == fd) + continue; + + if (close(fd) < 0 && errno != EBADF) + return -1; + } + + return 0; +} + +static int reset_sigsv(const int except[]) { + int sig; + assert(except); + + for (sig = 1; sig < NSIG; sig++) { + int reset = 1; + + switch (sig) { + case SIGKILL: + case SIGSTOP: + reset = 0; + break; + + default: { + int i; + + for (i = 0; except[i] > 0; i++) { + if (sig == except[i]) { + reset = 0; + break; + } + } + } + } + + if (reset) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if ((sigaction(sig, &sa, NULL) < 0)) + if (errno != EINVAL) + return -1; + } + } + + return 0; +} + +static int ignore_sigsv(const int ignore[]) { + int i; + assert(ignore); + + for (i = 0; ignore[i] > 0; i++) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + + if ((sigaction(ignore[i], &sa, NULL) < 0)) + return -1; + } + + return 0; +} + +#endif + +static int fd_nonblock(int fd) { + int i; + assert(fd >= 0); + + if ((i = fcntl(fd, F_GETFL, 0)) < 0) + return -1; + + if (i & O_NONBLOCK) + return 0; + + return fcntl(fd, F_SETFL, i | O_NONBLOCK); +} + +static int fd_cloexec(int fd) { + int v; + assert(fd >= 0); + + if ((v = fcntl(fd, F_GETFD, 0)) < 0) + return -1; + + if (v & FD_CLOEXEC) + return 0; + + return fcntl(fd, F_SETFD, v | FD_CLOEXEC); +} + +static int send_died(int out_fd) { + rheader_t rh; + assert(out_fd > 0); + + memset(&rh, 0, sizeof(rh)); + rh.type = RESPONSE_DIED; + rh.id = 0; + rh.length = sizeof(rh); + + return send(out_fd, &rh, rh.length, MSG_NOSIGNAL); +} + +static void *serialize_addrinfo(void *p, const struct addrinfo *ai, size_t *length, size_t maxlength) { + addrinfo_serialization_t s; + size_t cnl, l; + assert(p); + assert(ai); + assert(length); + assert(*length <= maxlength); + + cnl = (ai->ai_canonname ? strlen(ai->ai_canonname)+1 : 0); + l = sizeof(addrinfo_serialization_t) + ai->ai_addrlen + cnl; + + if (*length + l > maxlength) + return NULL; + + s.ai_flags = ai->ai_flags; + s.ai_family = ai->ai_family; + s.ai_socktype = ai->ai_socktype; + s.ai_protocol = ai->ai_protocol; + s.ai_addrlen = ai->ai_addrlen; + s.canonname_len = cnl; + + memcpy((uint8_t*) p, &s, sizeof(addrinfo_serialization_t)); + memcpy((uint8_t*) p + sizeof(addrinfo_serialization_t), ai->ai_addr, ai->ai_addrlen); + + if (ai->ai_canonname) + strcpy((char*) p + sizeof(addrinfo_serialization_t) + ai->ai_addrlen, ai->ai_canonname); + + *length += l; + return (uint8_t*) p + l; +} + +static int send_addrinfo_reply(int out_fd, unsigned id, int ret, struct addrinfo *ai, int _errno, int _h_errno) { + addrinfo_response_t data[BUFSIZE/sizeof(addrinfo_response_t) + 1]; + addrinfo_response_t *resp = data; + assert(out_fd >= 0); + + memset(data, 0, sizeof(data)); + resp->header.type = RESPONSE_ADDRINFO; + resp->header.id = id; + resp->header.length = sizeof(addrinfo_response_t); + resp->ret = ret; + resp->_errno = _errno; + resp->_h_errno = _h_errno; + + if (ret == 0 && ai) { + void *p = data + 1; + struct addrinfo *k; + + for (k = ai; k; k = k->ai_next) { + + if (!(p = serialize_addrinfo(p, k, &resp->header.length, (char*) data + BUFSIZE - (char*) p))) { + resp->ret = EAI_MEMORY; + break; + } + } + } + + if (ai) + freeaddrinfo(ai); + + return send(out_fd, resp, resp->header.length, MSG_NOSIGNAL); +} + +static int send_nameinfo_reply(int out_fd, unsigned id, int ret, const char *host, const char *serv, int _errno, int _h_errno) { + nameinfo_response_t data[BUFSIZE/sizeof(nameinfo_response_t) + 1]; + size_t hl, sl; + nameinfo_response_t *resp = data; + + assert(out_fd >= 0); + + sl = serv ? strlen(serv)+1 : 0; + hl = host ? strlen(host)+1 : 0; + + memset(data, 0, sizeof(data)); + resp->header.type = RESPONSE_NAMEINFO; + resp->header.id = id; + resp->header.length = sizeof(nameinfo_response_t) + hl + sl; + resp->ret = ret; + resp->_errno = _errno; + resp->_h_errno = _h_errno; + resp->hostlen = hl; + resp->servlen = sl; + + assert(sizeof(data) >= resp->header.length); + + if (host) + memcpy((uint8_t *)data + sizeof(nameinfo_response_t), host, hl); + + if (serv) + memcpy((uint8_t *)data + sizeof(nameinfo_response_t) + hl, serv, sl); + + return send(out_fd, resp, resp->header.length, MSG_NOSIGNAL); +} + +static int send_res_reply(int out_fd, unsigned id, const unsigned char *answer, int ret, int _errno, int _h_errno) { + res_response_t data[BUFSIZE/sizeof(res_response_t) + 1]; + res_response_t *resp = data; + + assert(out_fd >= 0); + + memset(data, 0, sizeof(data)); + resp->header.type = RESPONSE_RES; + resp->header.id = id; + resp->header.length = sizeof(res_response_t) + (ret < 0 ? 0 : ret); + resp->ret = ret; + resp->_errno = _errno; + resp->_h_errno = _h_errno; + + assert(sizeof(data) >= resp->header.length); + + if (ret > 0) + memcpy((uint8_t *)data + sizeof(res_response_t), answer, ret); + + return send(out_fd, resp, resp->header.length, MSG_NOSIGNAL); +} + +static int handle_request(int out_fd, const rheader_t *req, size_t length) { + assert(out_fd >= 0); + assert(req); + assert(length >= sizeof(rheader_t)); + assert(length == req->length); + + switch (req->type) { + case REQUEST_ADDRINFO: { + struct addrinfo ai, *result = NULL; + const addrinfo_request_t *ai_req = (const addrinfo_request_t*) req; + const char *node, *service; + int ret; + + assert(length >= sizeof(addrinfo_request_t)); + assert(length == sizeof(addrinfo_request_t) + ai_req->node_len + ai_req->service_len); + + memset(&ai, 0, sizeof(ai)); + ai.ai_flags = ai_req->ai_flags; + ai.ai_family = ai_req->ai_family; + ai.ai_socktype = ai_req->ai_socktype; + ai.ai_protocol = ai_req->ai_protocol; + + node = ai_req->node_len ? (const char*) req + sizeof(addrinfo_request_t) : NULL; + service = ai_req->service_len ? (const char*) req + sizeof(addrinfo_request_t) + ai_req->node_len : NULL; + + ret = getaddrinfo(node, service, + ai_req->hints_is_null ? NULL : &ai, + &result); + + /* send_addrinfo_reply() frees result */ + return send_addrinfo_reply(out_fd, req->id, ret, result, errno, h_errno); + } + + case REQUEST_NAMEINFO: { + int ret; + const nameinfo_request_t *ni_req = (const nameinfo_request_t*) req; + char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV]; + struct sockaddr_storage sa; + + assert(length >= sizeof(nameinfo_request_t)); + assert(length == sizeof(nameinfo_request_t) + ni_req->sockaddr_len); + + memcpy(&sa, (const uint8_t *)req + sizeof(nameinfo_request_t), ni_req->sockaddr_len); + + ret = getnameinfo((struct sockaddr *)&sa, ni_req->sockaddr_len, + ni_req->gethost ? hostbuf : NULL, ni_req->gethost ? sizeof(hostbuf) : 0, + ni_req->getserv ? servbuf : NULL, ni_req->getserv ? sizeof(servbuf) : 0, + ni_req->flags); + + return send_nameinfo_reply(out_fd, req->id, ret, + ret == 0 && ni_req->gethost ? hostbuf : NULL, + ret == 0 && ni_req->getserv ? servbuf : NULL, + errno, h_errno); + } + + case REQUEST_RES_QUERY: + case REQUEST_RES_SEARCH: { + int ret; + HEADER answer[BUFSIZE/sizeof(HEADER) + 1]; + const res_request_t *res_req = (const res_request_t *)req; + const char *dname; + + assert(length >= sizeof(res_request_t)); + assert(length == sizeof(res_request_t) + res_req->dname_len); + + dname = (const char *) req + sizeof(res_request_t); + + if (req->type == REQUEST_RES_QUERY) + ret = res_query(dname, res_req->class, res_req->type, (unsigned char *) answer, BUFSIZE); + else + ret = res_search(dname, res_req->class, res_req->type, (unsigned char *) answer, BUFSIZE); + + return send_res_reply(out_fd, req->id, (unsigned char *) answer, ret, errno, h_errno); + } + + case REQUEST_TERMINATE: + /* Quit */ + return -1; + + default: + ; + } + + return 0; +} + +#ifndef HAVE_PTHREAD + +static int process_worker(int in_fd, int out_fd) { + int have_death_sig = 0; + int good_fds[3]; + int ret = 1; + + const int ignore_sigs[] = { + SIGINT, + SIGHUP, + SIGPIPE, + SIGUSR1, + SIGUSR2, + -1 + }; + + assert(in_fd > 2); + assert(out_fd > 2); + + close(0); + close(1); + close(2); + + if (open("/dev/null", O_RDONLY) != 0) + goto fail; + + if (open("/dev/null", O_WRONLY) != 1) + goto fail; + + if (open("/dev/null", O_WRONLY) != 2) + goto fail; + + if (chdir("/") < 0) + goto fail; + + if (geteuid() == 0) { + struct passwd *pw; + int r; + + if ((pw = getpwnam("nobody"))) { +#ifdef HAVE_SETRESUID + r = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid); +#elif HAVE_SETREUID + r = setreuid(pw->pw_uid, pw->pw_uid); +#else + if ((r = setuid(pw->pw_uid)) >= 0) + r = seteuid(pw->pw_uid); +#endif + if (r < 0) + goto fail; + } + } + + if (reset_sigsv(ignore_sigs) < 0) + goto fail; + + if (ignore_sigsv(ignore_sigs) < 0) + goto fail; + + good_fds[0] = in_fd; good_fds[1] = out_fd; good_fds[2] = -1; + if (close_allv(good_fds) < 0) + goto fail; + +#ifdef PR_SET_PDEATHSIG + if (prctl(PR_SET_PDEATHSIG, SIGTERM) >= 0) + have_death_sig = 1; +#endif + + if (!have_death_sig) + fd_nonblock(in_fd); + + while (getppid() > 1) { /* if the parent PID is 1 our parent process died. */ + rheader_t buf[BUFSIZE/sizeof(rheader_t) + 1]; + ssize_t length; + + if (!have_death_sig) { + fd_set fds; + struct timeval tv = { 0, 500000 }; + + FD_ZERO(&fds); + FD_SET(in_fd, &fds); + + if (select(in_fd+1, &fds, NULL, NULL, &tv) < 0) + break; + + if (getppid() == 1) + break; + } + + if ((length = recv(in_fd, buf, sizeof(buf), 0)) <= 0) { + + if (length < 0 && + (errno == EAGAIN || errno == EINTR)) + continue; + + break; + } + + if (handle_request(out_fd, buf, (size_t) length) < 0) + break; + } + + ret = 0; + +fail: + send_died(out_fd); + + return ret; +} + +#else + +static void* thread_worker(void *p) { + _g_asyncns_t *asyncns = p; + sigset_t fullset; + + /* No signals in this thread please */ + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, NULL); + + while (!asyncns->dead) { + rheader_t buf[BUFSIZE/sizeof(rheader_t) + 1]; + ssize_t length; + + if ((length = recv(asyncns->fds[REQUEST_RECV_FD], buf, sizeof(buf), 0)) <= 0) { + + if (length < 0 && + (errno == EAGAIN || errno == EINTR)) + continue; + + break; + } + + if (asyncns->dead) + break; + + if (handle_request(asyncns->fds[RESPONSE_SEND_FD], buf, (size_t) length) < 0) + break; + } + + send_died(asyncns->fds[RESPONSE_SEND_FD]); + + return NULL; +} + +#endif + +_g_asyncns_t* _g_asyncns_new(unsigned n_proc) { + _g_asyncns_t *asyncns = NULL; + int i; + assert(n_proc >= 1); + + if (n_proc > MAX_WORKERS) + n_proc = MAX_WORKERS; + + if (!(asyncns = malloc(sizeof(_g_asyncns_t)))) { + errno = ENOMEM; + goto fail; + } + + asyncns->dead = 0; + asyncns->valid_workers = 0; + + for (i = 0; i < MESSAGE_FD_MAX; i++) + asyncns->fds[i] = -1; + + memset(asyncns->queries, 0, sizeof(asyncns->queries)); + + if (socketpair(PF_UNIX, SOCK_DGRAM, 0, asyncns->fds) < 0 || + socketpair(PF_UNIX, SOCK_DGRAM, 0, asyncns->fds+2) < 0) + goto fail; + + for (i = 0; i < MESSAGE_FD_MAX; i++) + fd_cloexec(asyncns->fds[i]); + + for (asyncns->valid_workers = 0; asyncns->valid_workers < n_proc; asyncns->valid_workers++) { + +#ifndef HAVE_PTHREAD + if ((asyncns->workers[asyncns->valid_workers] = fork()) < 0) + goto fail; + else if (asyncns->workers[asyncns->valid_workers] == 0) { + int ret; + + close(asyncns->fds[REQUEST_SEND_FD]); + close(asyncns->fds[RESPONSE_RECV_FD]); + ret = process_worker(asyncns->fds[REQUEST_RECV_FD], asyncns->fds[RESPONSE_SEND_FD]); + close(asyncns->fds[REQUEST_RECV_FD]); + close(asyncns->fds[RESPONSE_SEND_FD]); + _exit(ret); + } +#else + int r; + + if ((r = pthread_create(&asyncns->workers[asyncns->valid_workers], NULL, thread_worker, asyncns)) != 0) { + errno = r; + goto fail; + } +#endif + } + +#ifndef HAVE_PTHREAD + close(asyncns->fds[REQUEST_RECV_FD]); + close(asyncns->fds[RESPONSE_SEND_FD]); + asyncns->fds[REQUEST_RECV_FD] = asyncns->fds[RESPONSE_SEND_FD] = -1; +#endif + + asyncns->current_index = asyncns->current_id = 0; + asyncns->done_head = asyncns->done_tail = NULL; + asyncns->n_queries = 0; + + fd_nonblock(asyncns->fds[RESPONSE_RECV_FD]); + + return asyncns; + +fail: + if (asyncns) + _g_asyncns_free(asyncns); + + return NULL; +} + +void _g_asyncns_free(_g_asyncns_t *asyncns) { + int i; + int saved_errno = errno; + unsigned p; + + assert(asyncns); + + asyncns->dead = 1; + + if (asyncns->fds[REQUEST_SEND_FD] >= 0) { + rheader_t req; + + memset(&req, 0, sizeof(req)); + req.type = REQUEST_TERMINATE; + req.length = sizeof(req); + req.id = 0; + + /* Send one termination packet for each worker */ + for (p = 0; p < asyncns->valid_workers; p++) + send(asyncns->fds[REQUEST_SEND_FD], &req, req.length, MSG_NOSIGNAL); + } + + /* Now terminate them and wait until they are gone. */ + for (p = 0; p < asyncns->valid_workers; p++) { +#ifndef HAVE_PTHREAD + kill(asyncns->workers[p], SIGTERM); + for (;;) { + if (waitpid(asyncns->workers[p], NULL, 0) >= 0 || errno != EINTR) + break; + } +#else + for (;;) { + if (pthread_join(asyncns->workers[p], NULL) != EINTR) + break; + } +#endif + } + + /* Close all communication channels */ + for (i = 0; i < MESSAGE_FD_MAX; i++) + if (asyncns->fds[i] >= 0) + close(asyncns->fds[i]); + + for (p = 0; p < MAX_QUERIES; p++) + if (asyncns->queries[p]) + _g_asyncns_cancel(asyncns, asyncns->queries[p]); + + free(asyncns); + + errno = saved_errno; +} + +int _g_asyncns_fd(_g_asyncns_t *asyncns) { + assert(asyncns); + + return asyncns->fds[RESPONSE_RECV_FD]; +} + +static _g_asyncns_query_t *lookup_query(_g_asyncns_t *asyncns, unsigned id) { + _g_asyncns_query_t *q; + assert(asyncns); + + if ((q = asyncns->queries[id % MAX_QUERIES])) + if (q->id == id) + return q; + + return NULL; +} + +static void complete_query(_g_asyncns_t *asyncns, _g_asyncns_query_t *q) { + assert(asyncns); + assert(q); + assert(!q->done); + + q->done = 1; + + if ((q->done_prev = asyncns->done_tail)) + asyncns->done_tail->done_next = q; + else + asyncns->done_head = q; + + asyncns->done_tail = q; + q->done_next = NULL; +} + +static void *unserialize_addrinfo(void *p, struct addrinfo **ret_ai, size_t *length) { + addrinfo_serialization_t s; + size_t l; + struct addrinfo *ai; + assert(p); + assert(ret_ai); + assert(length); + + if (*length < sizeof(addrinfo_serialization_t)) + return NULL; + + memcpy(&s, p, sizeof(s)); + + l = sizeof(addrinfo_serialization_t) + s.ai_addrlen + s.canonname_len; + if (*length < l) + return NULL; + + if (!(ai = malloc(sizeof(struct addrinfo)))) + goto fail; + + ai->ai_addr = NULL; + ai->ai_canonname = NULL; + ai->ai_next = NULL; + + if (s.ai_addrlen && !(ai->ai_addr = malloc(s.ai_addrlen))) + goto fail; + + if (s.canonname_len && !(ai->ai_canonname = malloc(s.canonname_len))) + goto fail; + + ai->ai_flags = s.ai_flags; + ai->ai_family = s.ai_family; + ai->ai_socktype = s.ai_socktype; + ai->ai_protocol = s.ai_protocol; + ai->ai_addrlen = s.ai_addrlen; + + if (ai->ai_addr) + memcpy(ai->ai_addr, (uint8_t*) p + sizeof(addrinfo_serialization_t), s.ai_addrlen); + + if (ai->ai_canonname) + memcpy(ai->ai_canonname, (uint8_t*) p + sizeof(addrinfo_serialization_t) + s.ai_addrlen, s.canonname_len); + + *length -= l; + *ret_ai = ai; + + return (uint8_t*) p + l; + + +fail: + if (ai) + _g_asyncns_freeaddrinfo(ai); + + return NULL; +} + +static int handle_response(_g_asyncns_t *asyncns, rheader_t *resp, size_t length) { + _g_asyncns_query_t *q; + assert(asyncns); + assert(resp); + assert(length >= sizeof(rheader_t)); + assert(length == resp->length); + + if (resp->type == RESPONSE_DIED) { + asyncns->dead = 1; + return 0; + } + + if (!(q = lookup_query(asyncns, resp->id))) + return 0; + + switch (resp->type) { + case RESPONSE_ADDRINFO: { + const addrinfo_response_t *ai_resp = (addrinfo_response_t*) resp; + void *p; + size_t l; + struct addrinfo *prev = NULL; + + assert(length >= sizeof(addrinfo_response_t)); + assert(q->type == REQUEST_ADDRINFO); + + q->ret = ai_resp->ret; + q->_errno = ai_resp->_errno; + q->_h_errno = ai_resp->_h_errno; + l = length - sizeof(addrinfo_response_t); + p = (uint8_t*) resp + sizeof(addrinfo_response_t); + + while (l > 0 && p) { + struct addrinfo *ai = NULL; + p = unserialize_addrinfo(p, &ai, &l); + + if (!p || !ai) { + q->ret = EAI_MEMORY; + break; + } + + if (prev) + prev->ai_next = ai; + else + q->addrinfo = ai; + + prev = ai; + } + + complete_query(asyncns, q); + break; + } + + case RESPONSE_NAMEINFO: { + const nameinfo_response_t *ni_resp = (nameinfo_response_t*) resp; + + assert(length >= sizeof(nameinfo_response_t)); + assert(q->type == REQUEST_NAMEINFO); + + q->ret = ni_resp->ret; + q->_errno = ni_resp->_errno; + q->_h_errno = ni_resp->_h_errno; + + if (ni_resp->hostlen) + if (!(q->host = strndup((const char*) ni_resp + sizeof(nameinfo_response_t), ni_resp->hostlen-1))) + q->ret = EAI_MEMORY; + + if (ni_resp->servlen) + if (!(q->serv = strndup((const char*) ni_resp + sizeof(nameinfo_response_t) + ni_resp->hostlen, ni_resp->servlen-1))) + q->ret = EAI_MEMORY; + + complete_query(asyncns, q); + break; + } + + case RESPONSE_RES: { + const res_response_t *res_resp = (res_response_t *)resp; + + assert(length >= sizeof(res_response_t)); + assert(q->type == REQUEST_RES_QUERY || q->type == REQUEST_RES_SEARCH); + + q->ret = res_resp->ret; + q->_errno = res_resp->_errno; + q->_h_errno = res_resp->_h_errno; + + if (res_resp->ret >= 0) { + if (!(q->serv = malloc(res_resp->ret))) { + q->ret = -1; + q->_errno = ENOMEM; + } else + memcpy(q->serv, (char *)resp + sizeof(res_response_t), res_resp->ret); + } + + complete_query(asyncns, q); + break; + } + + default: + ; + } + + return 0; +} + +int _g_asyncns_wait(_g_asyncns_t *asyncns, int block) { + int handled = 0; + assert(asyncns); + + for (;;) { + rheader_t buf[BUFSIZE/sizeof(rheader_t) + 1]; + ssize_t l; + + if (asyncns->dead) { + errno = ECHILD; + return -1; + } + + if (((l = recv(asyncns->fds[RESPONSE_RECV_FD], buf, sizeof(buf), 0)) < 0)) { + fd_set fds; + + if (errno != EAGAIN) + return -1; + + if (!block || handled) + return 0; + + FD_ZERO(&fds); + FD_SET(asyncns->fds[RESPONSE_RECV_FD], &fds); + + if (select(asyncns->fds[RESPONSE_RECV_FD]+1, &fds, NULL, NULL, NULL) < 0) + return -1; + + continue; + } + + if (handle_response(asyncns, buf, (size_t) l) < 0) + return -1; + + handled = 1; + } +} + +static _g_asyncns_query_t *alloc_query(_g_asyncns_t *asyncns) { + _g_asyncns_query_t *q; + assert(asyncns); + + if (asyncns->n_queries >= MAX_QUERIES) { + errno = ENOMEM; + return NULL; + } + + while (asyncns->queries[asyncns->current_index]) { + + asyncns->current_index++; + asyncns->current_id++; + + while (asyncns->current_index >= MAX_QUERIES) + asyncns->current_index -= MAX_QUERIES; + } + + if (!(q = asyncns->queries[asyncns->current_index] = malloc(sizeof(_g_asyncns_query_t)))) { + errno = ENOMEM; + return NULL; + } + + asyncns->n_queries++; + + q->asyncns = asyncns; + q->done = 0; + q->id = asyncns->current_id; + q->done_next = q->done_prev = NULL; + q->ret = 0; + q->_errno = 0; + q->_h_errno = 0; + q->addrinfo = NULL; + q->userdata = NULL; + q->host = q->serv = NULL; + + return q; +} + +_g_asyncns_query_t* _g_asyncns_getaddrinfo(_g_asyncns_t *asyncns, const char *node, const char *service, const struct addrinfo *hints) { + addrinfo_request_t data[BUFSIZE/sizeof(addrinfo_request_t) + 1]; + addrinfo_request_t *req = data; + _g_asyncns_query_t *q; + assert(asyncns); + assert(node || service); + + if (asyncns->dead) { + errno = ECHILD; + return NULL; + } + + if (!(q = alloc_query(asyncns))) + return NULL; + + memset(req, 0, sizeof(addrinfo_request_t)); + + req->node_len = node ? strlen(node)+1 : 0; + req->service_len = service ? strlen(service)+1 : 0; + + req->header.id = q->id; + req->header.type = q->type = REQUEST_ADDRINFO; + req->header.length = sizeof(addrinfo_request_t) + req->node_len + req->service_len; + + if (req->header.length > BUFSIZE) { + errno = ENOMEM; + goto fail; + } + + if (!(req->hints_is_null = !hints)) { + req->ai_flags = hints->ai_flags; + req->ai_family = hints->ai_family; + req->ai_socktype = hints->ai_socktype; + req->ai_protocol = hints->ai_protocol; + } + + if (node) + strcpy((char*) req + sizeof(addrinfo_request_t), node); + + if (service) + strcpy((char*) req + sizeof(addrinfo_request_t) + req->node_len, service); + + if (send(asyncns->fds[REQUEST_SEND_FD], req, req->header.length, MSG_NOSIGNAL) < 0) + goto fail; + + return q; + +fail: + if (q) + _g_asyncns_cancel(asyncns, q); + + return NULL; +} + +int _g_asyncns_getaddrinfo_done(_g_asyncns_t *asyncns, _g_asyncns_query_t* q, struct addrinfo **ret_res) { + int ret; + assert(asyncns); + assert(q); + assert(q->asyncns == asyncns); + assert(q->type == REQUEST_ADDRINFO); + + if (asyncns->dead) { + errno = ECHILD; + return EAI_SYSTEM; + } + + if (!q->done) + return EAI_AGAIN; + + *ret_res = q->addrinfo; + q->addrinfo = NULL; + + ret = q->ret; + + if (ret == EAI_SYSTEM) + errno = q->_errno; + + if (ret != 0) + h_errno = q->_h_errno; + + _g_asyncns_cancel(asyncns, q); + + return ret; +} + +_g_asyncns_query_t* _g_asyncns_getnameinfo(_g_asyncns_t *asyncns, const struct sockaddr *sa, socklen_t salen, int flags, int gethost, int getserv) { + nameinfo_request_t data[BUFSIZE/sizeof(nameinfo_request_t) + 1]; + nameinfo_request_t *req = data; + _g_asyncns_query_t *q; + + assert(asyncns); + assert(sa); + assert(salen > 0); + + if (asyncns->dead) { + errno = ECHILD; + return NULL; + } + + if (!(q = alloc_query(asyncns))) + return NULL; + + memset(req, 0, sizeof(nameinfo_request_t)); + + req->header.id = q->id; + req->header.type = q->type = REQUEST_NAMEINFO; + req->header.length = sizeof(nameinfo_request_t) + salen; + + if (req->header.length > BUFSIZE) { + errno = ENOMEM; + goto fail; + } + + req->flags = flags; + req->sockaddr_len = salen; + req->gethost = gethost; + req->getserv = getserv; + + memcpy((uint8_t*) req + sizeof(nameinfo_request_t), sa, salen); + + if (send(asyncns->fds[REQUEST_SEND_FD], req, req->header.length, MSG_NOSIGNAL) < 0) + goto fail; + + return q; + +fail: + if (q) + _g_asyncns_cancel(asyncns, q); + + return NULL; +} + +int _g_asyncns_getnameinfo_done(_g_asyncns_t *asyncns, _g_asyncns_query_t* q, char *ret_host, size_t hostlen, char *ret_serv, size_t servlen) { + int ret; + assert(asyncns); + assert(q); + assert(q->asyncns == asyncns); + assert(q->type == REQUEST_NAMEINFO); + assert(!ret_host || hostlen); + assert(!ret_serv || servlen); + + if (asyncns->dead) { + errno = ECHILD; + return EAI_SYSTEM; + } + + if (!q->done) + return EAI_AGAIN; + + if (ret_host && q->host) { + strncpy(ret_host, q->host, hostlen); + ret_host[hostlen-1] = 0; + } + + if (ret_serv && q->serv) { + strncpy(ret_serv, q->serv, servlen); + ret_serv[servlen-1] = 0; + } + + ret = q->ret; + + if (ret == EAI_SYSTEM) + errno = q->_errno; + + if (ret != 0) + h_errno = q->_h_errno; + + _g_asyncns_cancel(asyncns, q); + + return ret; +} + +static _g_asyncns_query_t * _g_asyncns_res(_g_asyncns_t *asyncns, query_type_t qtype, const char *dname, int class, int type) { + res_request_t data[BUFSIZE/sizeof(res_request_t) + 1]; + res_request_t *req = data; + _g_asyncns_query_t *q; + + assert(asyncns); + assert(dname); + + if (asyncns->dead) { + errno = ECHILD; + return NULL; + } + + if (!(q = alloc_query(asyncns))) + return NULL; + + memset(req, 0, sizeof(res_request_t)); + + req->dname_len = strlen(dname) + 1; + + req->header.id = q->id; + req->header.type = q->type = qtype; + req->header.length = sizeof(res_request_t) + req->dname_len; + + if (req->header.length > BUFSIZE) { + errno = ENOMEM; + goto fail; + } + + req->class = class; + req->type = type; + + strcpy((char*) req + sizeof(res_request_t), dname); + + if (send(asyncns->fds[REQUEST_SEND_FD], req, req->header.length, MSG_NOSIGNAL) < 0) + goto fail; + + return q; + +fail: + if (q) + _g_asyncns_cancel(asyncns, q); + + return NULL; +} + +_g_asyncns_query_t* _g_asyncns_res_query(_g_asyncns_t *asyncns, const char *dname, int class, int type) { + return _g_asyncns_res(asyncns, REQUEST_RES_QUERY, dname, class, type); +} + +_g_asyncns_query_t* _g_asyncns_res_search(_g_asyncns_t *asyncns, const char *dname, int class, int type) { + return _g_asyncns_res(asyncns, REQUEST_RES_SEARCH, dname, class, type); +} + +int _g_asyncns_res_done(_g_asyncns_t *asyncns, _g_asyncns_query_t* q, unsigned char **answer) { + int ret; + assert(asyncns); + assert(q); + assert(q->asyncns == asyncns); + assert(q->type == REQUEST_RES_QUERY || q->type == REQUEST_RES_SEARCH); + assert(answer); + + if (asyncns->dead) { + errno = ECHILD; + return -ECHILD; + } + + if (!q->done) { + errno = EAGAIN; + return -EAGAIN; + } + + *answer = (unsigned char *)q->serv; + q->serv = NULL; + + ret = q->ret; + + if (ret < 0) { + errno = q->_errno; + h_errno = q->_h_errno; + } + + _g_asyncns_cancel(asyncns, q); + + return ret < 0 ? -errno : ret; +} + +_g_asyncns_query_t* _g_asyncns_getnext(_g_asyncns_t *asyncns) { + assert(asyncns); + return asyncns->done_head; +} + +int _g_asyncns_getnqueries(_g_asyncns_t *asyncns) { + assert(asyncns); + return asyncns->n_queries; +} + +void _g_asyncns_cancel(_g_asyncns_t *asyncns, _g_asyncns_query_t* q) { + int i; + int saved_errno = errno; + + assert(asyncns); + assert(q); + assert(q->asyncns == asyncns); + assert(asyncns->n_queries > 0); + + if (q->done) { + + if (q->done_prev) + q->done_prev->done_next = q->done_next; + else + asyncns->done_head = q->done_next; + + if (q->done_next) + q->done_next->done_prev = q->done_prev; + else + asyncns->done_tail = q->done_prev; + } + + i = q->id % MAX_QUERIES; + assert(asyncns->queries[i] == q); + asyncns->queries[i] = NULL; + + _g_asyncns_freeaddrinfo(q->addrinfo); + free(q->host); + free(q->serv); + + asyncns->n_queries--; + free(q); + + errno = saved_errno; +} + +void _g_asyncns_freeaddrinfo(struct addrinfo *ai) { + int saved_errno = errno; + + while (ai) { + struct addrinfo *next = ai->ai_next; + + free(ai->ai_addr); + free(ai->ai_canonname); + free(ai); + + ai = next; + } + + errno = saved_errno; +} + +void _g_asyncns_freeanswer(unsigned char *answer) { + int saved_errno = errno; + + if (!answer) + return; + + /* Please note that this function is new in libasyncns 0.4. In + * older versions you were supposed to free the answer directly + * with free(). Hence, if this function is changed to do more than + * just a simple free() this must be considered ABI/API breakage! */ + + free(answer); + + errno = saved_errno; +} + +int _g_asyncns_isdone(_g_asyncns_t *asyncns, _g_asyncns_query_t*q) { + assert(asyncns); + assert(q); + assert(q->asyncns == asyncns); + + return q->done; +} + +void _g_asyncns_setuserdata(_g_asyncns_t *asyncns, _g_asyncns_query_t *q, void *userdata) { + assert(q); + assert(asyncns); + assert(q->asyncns = asyncns); + + q->userdata = userdata; +} + +void* _g_asyncns_getuserdata(_g_asyncns_t *asyncns, _g_asyncns_query_t *q) { + assert(q); + assert(asyncns); + assert(q->asyncns = asyncns); + + return q->userdata; +} diff --git a/gio/libasyncns/asyncns.h b/gio/libasyncns/asyncns.h new file mode 100644 index 000000000..b3d49ffde --- /dev/null +++ b/gio/libasyncns/asyncns.h @@ -0,0 +1,163 @@ +#ifndef fooasyncnshfoo +#define fooasyncnshfoo + +/*** + This file is part of libasyncns. + + Copyright 2005-2008 Lennart Poettering + + libasyncns 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. + + libasyncns 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 libasyncns. If not, see + . +***/ + +#include +#include +#include + +/** \mainpage + * + * \section moo Method of operation + * + * To use libasyncns allocate an _g_asyncns_t object with + * _g_asyncns_new(). This will spawn a number of worker threads (or processes, depending on what is available) which + * are subsequently used to process the queries the controlling + * program issues via _g_asyncns_getaddrinfo() and + * _g_asyncns_getnameinfo(). Use _g_asyncns_free() to shut down the worker + * threads/processes. + * + * Since libasyncns may fork off new processes you have to make sure that + * your program is not irritated by spurious SIGCHLD signals. + */ + +/** \example asyncns-test.c + * An example program */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** An opaque libasyncns session structure */ +typedef struct asyncns _g_asyncns_t; + +/** An opaque libasyncns query structure */ +typedef struct _g_asyncns_query _g_asyncns_query_t; + +/** Allocate a new libasyncns session with n_proc worker processes/threads */ +_g_asyncns_t* _g_asyncns_new(unsigned n_proc); + +/** Free a libasyncns session. This destroys all attached + * _g_asyncns_query_t objects automatically */ +void _g_asyncns_free(_g_asyncns_t *asyncns); + +/** Return the UNIX file descriptor to select() for readability + * on. Use this function to integrate libasyncns with your custom main + * loop. */ +int _g_asyncns_fd(_g_asyncns_t *asyncns); + +/** Process pending responses. After this function is called you can + * get the next completed query object(s) using _g_asyncns_getnext(). If + * block is non-zero wait until at least one response has been + * processed. If block is zero, process all pending responses and + * return. */ +int _g_asyncns_wait(_g_asyncns_t *asyncns, int block); + +/** Issue a name to address query on the specified session. The + * arguments are compatible with the ones of libc's + * getaddrinfo(3). The function returns a new query object. When the + * query is completed you may retrieve the results using + * _g_asyncns_getaddrinfo_done().*/ +_g_asyncns_query_t* _g_asyncns_getaddrinfo(_g_asyncns_t *asyncns, const char *node, const char *service, const struct addrinfo *hints); + +/** Retrieve the results of a preceding _g_asyncns_getaddrinfo() + * call. Returns a addrinfo structure and a return value compatible + * with libc's getaddrinfo(3). The query object q is destroyed by this + * call and may not be used any further. Make sure to free the + * returned addrinfo structure with _g_asyncns_freeaddrinfo() and not + * libc's freeaddrinfo(3)! If the query is not completed yet EAI_AGAIN + * is returned.*/ +int _g_asyncns_getaddrinfo_done(_g_asyncns_t *asyncns, _g_asyncns_query_t* q, struct addrinfo **ret_res); + +/** Issue an address to name query on the specified session. The + * arguments are compatible with the ones of libc's + * getnameinfo(3). The function returns a new query object. When the + * query is completed you may retrieve the results using + * _g_asyncns_getnameinfo_done(). Set gethost (resp. getserv) to non-zero + * if you want to query the hostname (resp. the service name). */ +_g_asyncns_query_t* _g_asyncns_getnameinfo(_g_asyncns_t *asyncns, const struct sockaddr *sa, socklen_t salen, int flags, int gethost, int getserv); + +/** Retrieve the results of a preceding _g_asyncns_getnameinfo() + * call. Returns the hostname and the service name in ret_host and + * ret_serv. The query object q is destroyed by this call and may not + * be used any further. If the query is not completed yet EAI_AGAIN is + * returned. */ +int _g_asyncns_getnameinfo_done(_g_asyncns_t *asyncns, _g_asyncns_query_t* q, char *ret_host, size_t hostlen, char *ret_serv, size_t servlen); + +/** Issue a resolver query on the specified session. The arguments are + * compatible with the ones of libc's res_query(3). The function returns a new + * query object. When the query is completed you may retrieve the results using + * _g_asyncns_res_done(). */ +_g_asyncns_query_t* _g_asyncns_res_query(_g_asyncns_t *asyncns, const char *dname, int class, int type); + +/** Issue an resolver query on the specified session. The arguments are + * compatible with the ones of libc's res_search(3). The function returns a new + * query object. When the query is completed you may retrieve the results using + * _g_asyncns_res_done(). */ +_g_asyncns_query_t* _g_asyncns_res_search(_g_asyncns_t *asyncns, const char *dname, int class, int type); + +/** Retrieve the results of a preceding _g_asyncns_res_query() or + * _g_asyncns_res_search call. The query object q is destroyed by this + * call and may not be used any further. Returns a pointer to the + * answer of the res_query call. If the query is not completed yet + * -EAGAIN is returned, on failure -errno is returned, otherwise the + * length of answer is returned. Make sure to free the answer is a + * call to _g_asyncns_freeanswer(). */ +int _g_asyncns_res_done(_g_asyncns_t *asyncns, _g_asyncns_query_t* q, unsigned char **answer); + +/** Return the next completed query object. If no query has been + * completed yet, return NULL. Please note that you need to run + * _g_asyncns_wait() before this function will return sensible data. */ +_g_asyncns_query_t* _g_asyncns_getnext(_g_asyncns_t *asyncns); + +/** Return the number of query objects (completed or not) attached to + * this session */ +int _g_asyncns_getnqueries(_g_asyncns_t *asyncns); + +/** Cancel a currently running query. q is is destroyed by this call + * and may not be used any futher. */ +void _g_asyncns_cancel(_g_asyncns_t *asyncns, _g_asyncns_query_t* q); + +/** Free the addrinfo structure as returned by + * _g_asyncns_getaddrinfo_done(). Make sure to use this functions instead + * of the libc's freeaddrinfo()! */ +void _g_asyncns_freeaddrinfo(struct addrinfo *ai); + +/** Free the answer data as returned by _g_asyncns_res_done().*/ +void _g_asyncns_freeanswer(unsigned char *answer); + +/** Returns non-zero when the query operation specified by q has been completed */ +int _g_asyncns_isdone(_g_asyncns_t *asyncns, _g_asyncns_query_t*q); + +/** Assign some opaque userdata with a query object */ +void _g_asyncns_setuserdata(_g_asyncns_t *asyncns, _g_asyncns_query_t *q, void *userdata); + +/** Return userdata assigned to a query object. Use + * _g_asyncns_setuserdata() to set this data. If no data has been set + * prior to this call it returns NULL. */ +void* _g_asyncns_getuserdata(_g_asyncns_t *asyncns, _g_asyncns_query_t *q); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gio/libasyncns/g-asyncns.h b/gio/libasyncns/g-asyncns.h new file mode 100644 index 000000000..a40fadb2c --- /dev/null +++ b/gio/libasyncns/g-asyncns.h @@ -0,0 +1,28 @@ +/* GLIB - Library of useful routines for C programming + * 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. + */ +#ifndef __G_ASYNCNS_H__ + +#include "config.h" + +#define _GNU_SOURCE +#undef HAVE_PTHREAD + +#include "asyncns.h" + +#endif diff --git a/gio/libasyncns/update.sh b/gio/libasyncns/update.sh new file mode 100644 index 000000000..4c43ca525 --- /dev/null +++ b/gio/libasyncns/update.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +if test $# = 1 ; then + ORIGINAL=$1 +else + echo "Usage: update.sh /path/to/libasyncns" 1>&2 + exit 1 +fi + +if test -f $ORIGINAL/libasyncns/asyncns.c ; then : ; else + echo "Usage: update.sh /path/to/libasyncns" 1>&2 + exit 1 +fi + +for i in asyncns.c asyncns.h ; do + sed -e 's/\([^a-z]\)asyncns_/\1_g_asyncns_/g' \ + -e 's/^asyncns_/_g_asyncns_/' \ + -e 's//"g-asyncns\.h"/' \ + $ORIGINAL/libasyncns/$i > $i +done diff --git a/gio/pltcheck.sh b/gio/pltcheck.sh index e09a587c5..be29be806 100755 --- a/gio/pltcheck.sh +++ b/gio/pltcheck.sh @@ -9,7 +9,7 @@ if ! which readelf 2>/dev/null >/dev/null; then exit 0 fi -SKIP='\ +#include "glibintl.h" + +#include +#include +#include +#include +#include + +#include + +static GResolver *resolver; +static GCancellable *cancellable; +static GMainLoop *loop; +static int nlookups = 0; + +static void +usage (void) +{ + fprintf (stderr, "Usage: resolver [-t] [-s] [hostname | IP | service/protocol/domain ] ...\n"); + fprintf (stderr, " Use -t to enable threading.\n"); + fprintf (stderr, " Use -s to do synchronous lookups.\n"); + fprintf (stderr, " Both together will result in simultaneous lookups in multiple threads\n"); + exit (1); +} + +G_LOCK_DEFINE_STATIC (response); + +static void +done_lookup (void) +{ + nlookups--; + if (nlookups == 0) + { + /* In the sync case we need to make sure we don't call + * g_main_loop_quit before the loop is actually running... + */ + g_idle_add ((GSourceFunc)g_main_loop_quit, loop); + } +} + +static void +print_resolved_name (const char *phys, + char *name, + GError *error) +{ + G_LOCK (response); + printf ("Address: %s\n", phys); + if (error) + { + printf ("Error: %s\n", error->message); + g_error_free (error); + } + else + { + printf ("Name: %s\n", name); + g_free (name); + } + printf ("\n"); + + done_lookup (); + G_UNLOCK (response); +} + +static void +print_resolved_addresses (const char *name, + GList *addresses, + GError *error) +{ + char *phys; + GList *a; + + G_LOCK (response); + printf ("Name: %s\n", name); + if (error) + { + printf ("Error: %s\n", error->message); + g_error_free (error); + } + else + { + for (a = addresses; a; a = a->next) + { + phys = g_inet_address_to_string (a->data); + printf ("Address: %s\n", phys); + g_free (phys); + g_object_unref (a->data); + } + g_list_free (addresses); + } + printf ("\n"); + + done_lookup (); + G_UNLOCK (response); +} + +static void +print_resolved_service (const char *service, + GList *targets, + GError *error) +{ + GList *t; + + G_LOCK (response); + printf ("Service: %s\n", service); + if (error) + { + printf ("Error: %s\n", error->message); + g_error_free (error); + } + else + { + for (t = targets; t; t = t->next) + { + printf ("%s:%u (pri %u, weight %u)\n", + g_srv_target_get_hostname (t->data), + g_srv_target_get_port (t->data), + g_srv_target_get_priority (t->data), + g_srv_target_get_weight (t->data)); + g_srv_target_free (t->data); + } + g_list_free (targets); + } + printf ("\n"); + + done_lookup (); + G_UNLOCK (response); +} + +static void +lookup_one_sync (const char *arg) +{ + GError *error = NULL; + + if (strchr (arg, '/')) + { + GList *targets; + /* service/protocol/domain */ + char **parts = g_strsplit (arg, "/", 3); + + if (!parts || !parts[2]) + usage (); + + targets = g_resolver_lookup_service (resolver, + parts[0], parts[1], parts[2], + cancellable, &error); + print_resolved_service (arg, targets, error); + } + else if (g_hostname_is_ip_address (arg)) + { + GInetAddress *addr = g_inet_address_new_from_string (arg); + char *name; + + name = g_resolver_lookup_by_address (resolver, addr, cancellable, &error); + print_resolved_name (arg, name, error); + g_object_unref (addr); + } + else + { + GList *addresses; + + addresses = g_resolver_lookup_by_name (resolver, arg, cancellable, &error); + print_resolved_addresses (arg, addresses, error); + } +} + +static gpointer +lookup_thread (gpointer arg) +{ + lookup_one_sync (arg); + return NULL; +} + +static void +start_threaded_lookups (char **argv, int argc) +{ + int i; + + for (i = 0; i < argc; i++) + g_thread_create (lookup_thread, argv[i], FALSE, NULL); +} + +static void +start_sync_lookups (char **argv, int argc) +{ + int i; + + for (i = 0; i < argc; i++) + lookup_one_sync (argv[i]); +} + +static void +lookup_by_addr_callback (GObject *source, GAsyncResult *result, + gpointer user_data) +{ + const char *phys = user_data; + GError *error = NULL; + char *name; + + name = g_resolver_lookup_by_address_finish (resolver, result, &error); + print_resolved_name (phys, name, error); +} + +static void +lookup_by_name_callback (GObject *source, GAsyncResult *result, + gpointer user_data) +{ + const char *name = user_data; + GError *error = NULL; + GList *addresses; + + addresses = g_resolver_lookup_by_name_finish (resolver, result, &error); + print_resolved_addresses (name, addresses, error); +} + +static void +lookup_service_callback (GObject *source, GAsyncResult *result, + gpointer user_data) +{ + const char *service = user_data; + GError *error = NULL; + GList *targets; + + targets = g_resolver_lookup_service_finish (resolver, result, &error); + print_resolved_service (service, targets, error); +} + +static void +start_async_lookups (char **argv, int argc) +{ + int i; + + for (i = 0; i < argc; i++) + { + if (strchr (argv[i], '/')) + { + /* service/protocol/domain */ + char **parts = g_strsplit (argv[i], "/", 3); + + if (!parts || !parts[2]) + usage (); + + g_resolver_lookup_service_async (resolver, + parts[0], parts[1], parts[2], + cancellable, + lookup_service_callback, argv[i]); + } + else if (g_hostname_is_ip_address (argv[i])) + { + GInetAddress *addr = g_inet_address_new_from_string (argv[i]); + + g_resolver_lookup_by_address_async (resolver, addr, cancellable, + lookup_by_addr_callback, argv[i]); + g_object_unref (addr); + } + else + { + g_resolver_lookup_by_name_async (resolver, argv[i], cancellable, + lookup_by_name_callback, + argv[i]); + } + } +} + +#ifdef G_OS_UNIX +static int cancel_fds[2]; + +static void +interrupted (int sig) +{ + signal (SIGINT, SIG_DFL); + write (cancel_fds[1], "x", 1); +} + +static gboolean +async_cancel (GIOChannel *source, GIOCondition cond, gpointer cancellable) +{ + g_cancellable_cancel (cancellable); + return FALSE; +} +#endif + +int +main (int argc, char **argv) +{ + gboolean threaded = FALSE, synchronous = FALSE; +#ifdef G_OS_UNIX + GIOChannel *chan; + guint watch; +#endif + + /* We can't use GOptionContext because we use the arguments to + * decide whether or not to call g_thread_init(). + */ + while (argc >= 2 && argv[1][0] == '-') + { + if (!strcmp (argv[1], "-t")) + { + g_thread_init (NULL); + threaded = TRUE; + } + else if (!strcmp (argv[1], "-s")) + synchronous = TRUE; + else + usage (); + + argv++; + argc--; + } + g_type_init (); + + if (argc < 2) + usage (); + + resolver = g_resolver_get_default (); + + cancellable = g_cancellable_new (); + +#ifdef G_OS_UNIX + /* Set up cancellation; we want to cancel if the user ^C's the + * program, but we can't cancel directly from an interrupt. + */ + signal (SIGINT, interrupted); + + if (pipe (cancel_fds) == -1) + { + perror ("pipe"); + exit (1); + } + chan = g_io_channel_unix_new (cancel_fds[0]); + watch = g_io_add_watch (chan, G_IO_IN, async_cancel, cancellable); + g_io_channel_unref (chan); +#endif + + nlookups = argc - 1; + loop = g_main_loop_new (NULL, TRUE); + + if (threaded && synchronous) + start_threaded_lookups (argv + 1, argc - 1); + else if (synchronous) + start_sync_lookups (argv + 1, argc - 1); + else + start_async_lookups (argv + 1, argc - 1); + + g_main_run (loop); + g_main_loop_unref (loop); + +#ifdef G_OS_UNIX + g_source_remove (watch); +#endif + g_object_unref (cancellable); + + return 0; +}