From 021dd960cf9c02b0ea59cbfa1db603d3f9a467f0 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 19 Aug 2009 12:07:53 -0400 Subject: [PATCH] Re-run res_init() when resolv.conf changes libc caches the contents of resolv.conf, so if it changes (eg, because the network state changed), we need re-run res_init(). http://bugzilla.gnome.org/show_bug.cgi?id=584246 --- gio/gnetworkingprivate.h | 4 +++ gio/gresolver.c | 68 +++++++++++++++++++++++++++++++++++++ gio/gresolver.h | 9 ++++- gio/gunixresolver.c | 72 +++++++++++++++++++++++++++------------- gio/tests/resolver.c | 3 ++ 5 files changed, 132 insertions(+), 24 deletions(-) diff --git a/gio/gnetworkingprivate.h b/gio/gnetworkingprivate.h index b6a811a68..050b3a297 100644 --- a/gio/gnetworkingprivate.h +++ b/gio/gnetworkingprivate.h @@ -56,6 +56,10 @@ #include #include +#ifndef _PATH_RESCONF +#define _PATH_RESCONF "/etc/resolv.conf" +#endif + #endif G_BEGIN_DECLS diff --git a/gio/gresolver.c b/gio/gresolver.c index f631a7823..2b3045721 100644 --- a/gio/gresolver.c +++ b/gio/gresolver.c @@ -34,6 +34,7 @@ #ifdef G_OS_UNIX #include "gunixresolver.h" +#include #endif #ifdef G_OS_WIN32 #include "gwin32resolver.h" @@ -58,6 +59,21 @@ * making it easy to connect to a remote host/service. */ +enum { + RELOAD, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _GResolverPrivate { +#ifdef G_OS_UNIX + time_t resolv_conf_timestamp; +#else + int dummy; +#endif +}; + /** * GResolver: * @@ -71,6 +87,8 @@ g_resolver_class_init (GResolverClass *resolver_class) { volatile GType type; + g_type_class_add_private (resolver_class, sizeof (GResolverPrivate)); + /* Make sure _g_networking_init() has been called */ type = g_inet_address_get_type (); @@ -84,11 +102,37 @@ g_resolver_class_init (GResolverClass *resolver_class) */ _g_resolver_addrinfo_hints.ai_socktype = SOCK_STREAM; _g_resolver_addrinfo_hints.ai_protocol = IPPROTO_TCP; + + /** + * GResolver::reload: + * @resolver: a #GResolver + * + * Emitted when the resolver notices that the system resolver + * configuration has changed. + **/ + signals[RELOAD] = + g_signal_new (I_("reload"), + G_TYPE_RESOLVER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GResolverClass, reload), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); } static void g_resolver_init (GResolver *resolver) { +#ifdef G_OS_UNIX + struct stat st; +#endif + + resolver->priv = G_TYPE_INSTANCE_GET_PRIVATE (resolver, G_TYPE_RESOLVER, GResolverPrivate); + +#ifdef G_OS_UNIX + if (stat (_PATH_RESCONF, &st) == 0) + resolver->priv->resolv_conf_timestamp = st.st_mtime; +#endif } static GResolver *default_resolver; @@ -150,6 +194,24 @@ g_resolver_set_default (GResolver *resolver) } +static void +g_resolver_maybe_reload (GResolver *resolver) +{ +#ifdef G_OS_UNIX + struct stat st; + + if (stat (_PATH_RESCONF, &st) == 0) + { + if (st.st_mtime != resolver->priv->resolv_conf_timestamp) + { + resolver->priv->resolv_conf_timestamp = st.st_mtime; + res_init (); + g_signal_emit (resolver, signals[RELOAD], 0); + } + } +#endif +} + /** * g_resolver_lookup_by_name: * @resolver: a #GResolver @@ -205,6 +267,7 @@ g_resolver_lookup_by_name (GResolver *resolver, if (g_hostname_is_non_ascii (hostname)) hostname = ascii_hostname = g_hostname_to_ascii (hostname); + g_resolver_maybe_reload (resolver); addrs = G_RESOLVER_GET_CLASS (resolver)-> lookup_by_name (resolver, hostname, cancellable, error); @@ -259,6 +322,7 @@ g_resolver_lookup_by_name_async (GResolver *resolver, if (g_hostname_is_non_ascii (hostname)) hostname = ascii_hostname = g_hostname_to_ascii (hostname); + g_resolver_maybe_reload (resolver); G_RESOLVER_GET_CLASS (resolver)-> lookup_by_name_async (resolver, hostname, cancellable, callback, user_data); @@ -363,6 +427,7 @@ g_resolver_lookup_by_address (GResolver *resolver, g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL); g_return_val_if_fail (G_IS_INET_ADDRESS (address), NULL); + g_resolver_maybe_reload (resolver); return G_RESOLVER_GET_CLASS (resolver)-> lookup_by_address (resolver, address, cancellable, error); } @@ -391,6 +456,7 @@ g_resolver_lookup_by_address_async (GResolver *resolver, g_return_if_fail (G_IS_RESOLVER (resolver)); g_return_if_fail (G_IS_INET_ADDRESS (address)); + g_resolver_maybe_reload (resolver); G_RESOLVER_GET_CLASS (resolver)-> lookup_by_address_async (resolver, address, cancellable, callback, user_data); } @@ -504,6 +570,7 @@ g_resolver_lookup_service (GResolver *resolver, rrname = g_resolver_get_service_rrname (service, protocol, domain); + g_resolver_maybe_reload (resolver); targets = G_RESOLVER_GET_CLASS (resolver)-> lookup_service (resolver, rrname, cancellable, error); @@ -547,6 +614,7 @@ g_resolver_lookup_service_async (GResolver *resolver, rrname = g_resolver_get_service_rrname (service, protocol, domain); + g_resolver_maybe_reload (resolver); G_RESOLVER_GET_CLASS (resolver)-> lookup_service_async (resolver, rrname, cancellable, callback, user_data); diff --git a/gio/gresolver.h b/gio/gresolver.h index 6bdbc9085..1187a3fc9 100644 --- a/gio/gresolver.h +++ b/gio/gresolver.h @@ -36,15 +36,22 @@ G_BEGIN_DECLS #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)) +typedef struct _GResolverPrivate GResolverPrivate; +typedef struct _GResolverClass GResolverClass; + struct _GResolver { GObject parent_instance; + GResolverPrivate *priv; }; -typedef struct _GResolverClass GResolverClass; struct _GResolverClass { GObjectClass parent_class; + /* Signals */ + void ( *reload) (GResolver *resolver); + + /* Virtual methods */ GList * ( *lookup_by_name) (GResolver *resolver, const gchar *hostname, GCancellable *cancellable, diff --git a/gio/gunixresolver.c b/gio/gunixresolver.c index d25274fab..2155c661f 100644 --- a/gio/gunixresolver.c +++ b/gio/gunixresolver.c @@ -41,21 +41,12 @@ 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_reload (GResolver *resolver); 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); + g_unix_resolver_reload (G_RESOLVER (gur)); } static void @@ -70,6 +61,33 @@ g_unix_resolver_finalize (GObject *object) G_OBJECT_CLASS (g_unix_resolver_parent_class)->finalize (object); } +static void +g_unix_resolver_reload (GResolver *resolver) +{ + GUnixResolver *gur = G_UNIX_RESOLVER (resolver); + gint fd; + GIOChannel *io; + + if (gur->asyncns) + { + if (_g_asyncns_getnqueries (gur->asyncns) == 0) + { + g_source_remove (gur->watch); + _g_asyncns_free (gur->asyncns); + } + /* else, we will free it later from g_unix_resolver_watch */ + } + + /* 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->asyncns); + g_io_channel_unref (io); +} + /* The various request possibilities: * * 1. Synchronous: handed off to the base class (GThreadedResolver); @@ -107,6 +125,7 @@ typedef void (*GUnixResolverFunc) (GUnixResolverRequest *); struct _GUnixResolverRequest { GUnixResolver *gur; + _g_asyncns_t *asyncns; _g_asyncns_query_t *qy; union { @@ -148,6 +167,7 @@ g_unix_resolver_request_new (GUnixResolver *gur, req = g_slice_new0 (GUnixResolverRequest); req->gur = g_object_ref (gur); + req->asyncns = gur->asyncns; req->qy = qy; req->process_func = process_func; req->free_func = free_func; @@ -170,12 +190,17 @@ static void g_unix_resolver_request_free (GUnixResolverRequest *req) { req->free_func (req); - g_object_unref (req->gur); /* We don't have to free req->cancellable and req->async_result, * since they must already have been freed if we're here. */ + /* Check if this was the last request remaining on an old asyncns. */ + if (req->asyncns != req->gur->asyncns && + _g_asyncns_getnqueries (req->asyncns) == 0) + _g_asyncns_free (req->asyncns); + + g_object_unref (req->gur); g_slice_free (GUnixResolverRequest, req); } @@ -204,7 +229,7 @@ request_cancelled (GCancellable *cancellable, GUnixResolverRequest *req = user_data; GError *error = NULL; - _g_asyncns_cancel (req->gur->asyncns, req->qy); + _g_asyncns_cancel (req->asyncns, req->qy); req->qy = NULL; g_cancellable_set_error_if_cancelled (cancellable, &error); @@ -219,22 +244,22 @@ g_unix_resolver_watch (GIOChannel *iochannel, GIOCondition condition, gpointer user_data) { - GUnixResolver *gur = user_data; + _g_asyncns_t *asyncns = 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; + /* Will happen if we reload, and then eventually kill the old + * _g_asyncns_t when it's done processing requests. + */ return FALSE; } - while (_g_asyncns_wait (gur->asyncns, FALSE) == 0 && - (qy = _g_asyncns_getnext (gur->asyncns)) != NULL) + while (_g_asyncns_wait (asyncns, FALSE) == 0 && + (qy = _g_asyncns_getnext (asyncns)) != NULL) { - req = _g_asyncns_getuserdata (gur->asyncns, qy); + req = _g_asyncns_getuserdata (asyncns, qy); req->process_func (req); g_unix_resolver_request_complete (req); } @@ -271,7 +296,7 @@ lookup_by_name_process (GUnixResolverRequest *req) gint retval; GError *error = NULL; - retval = _g_asyncns_getaddrinfo_done (req->gur->asyncns, req->qy, &res); + retval = _g_asyncns_getaddrinfo_done (req->asyncns, req->qy, &res); req->u.name.addresses = _g_resolver_addresses_from_addrinfo (req->u.name.hostname, res, retval, &error); @@ -341,7 +366,7 @@ lookup_by_address_process (GUnixResolverRequest *req) gint retval; GError *error = NULL; - retval = _g_asyncns_getnameinfo_done (req->gur->asyncns, req->qy, + retval = _g_asyncns_getnameinfo_done (req->asyncns, req->qy, host, sizeof (host), NULL, 0); req->u.address.hostname = _g_resolver_name_from_nameinfo (req->u.address.address, @@ -415,7 +440,7 @@ lookup_service_process (GUnixResolverRequest *req) gint len, herr; GError *error = NULL; - len = _g_asyncns_res_done (req->gur->asyncns, req->qy, &answer); + len = _g_asyncns_res_done (req->asyncns, req->qy, &answer); if (len < 0) herr = h_errno; else @@ -487,6 +512,7 @@ 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->reload = g_unix_resolver_reload; 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; diff --git a/gio/tests/resolver.c b/gio/tests/resolver.c index 055e152ac..a6b4d945c 100644 --- a/gio/tests/resolver.c +++ b/gio/tests/resolver.c @@ -284,6 +284,9 @@ start_async_lookups (char **argv, int argc) lookup_by_name_callback, argv[i]); } + + /* Stress-test the reloading code */ + g_signal_emit_by_name (resolver, "reload"); } }