gvfs/common/gvfsdnssdresolver.c

1310 lines
40 KiB
C

/* 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., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* Author: David Zeuthen <davidz@redhat.com>
*/
/*
* TODO: - locking
* - cancellation
* - error handling
*/
#include <config.h>
#include <string.h>
#include <glib/gi18n-lib.h>
#include <avahi-client/client.h>
#include <avahi-client/lookup.h>
#include <avahi-common/error.h>
#include <avahi-common/timeval.h>
#include <avahi-glib/glib-watch.h>
#include <avahi-glib/glib-malloc.h>
#include <net/if.h>
#include "gvfsdnssdutils.h"
#include "gvfsdnssdresolver.h"
enum
{
PROP_0,
PROP_ENCODED_TRIPLE,
PROP_REQUIRED_TXT_KEYS,
PROP_SERVICE_NAME,
PROP_SERVICE_TYPE,
PROP_DOMAIN,
PROP_TIMEOUT_MSEC,
PROP_IS_RESOLVED,
PROP_ADDRESS,
PROP_INTERFACE,
PROP_PORT,
PROP_TXT_RECORDS,
};
enum {
CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
struct _GVfsDnsSdResolver
{
GObject parent_instance;
char *encoded_triple;
char *service_name;
char *service_type;
char *domain;
char *required_txt_keys;
char **required_txt_keys_broken_out;
guint timeout_msec;
gboolean is_resolved;
char *address;
gchar *interface;
guint port;
char **txt_records;
AvahiServiceResolver *avahi_resolver;
guint start_avahi_resolver_id;
};
struct _GVfsDnsSdResolverClass
{
GObjectClass parent_class;
/* signals */
void (*changed) (GVfsDnsSdResolver *resolver);
};
G_DEFINE_TYPE (GVfsDnsSdResolver, g_vfs_dns_sd_resolver, G_TYPE_OBJECT);
static gboolean resolver_supports_mdns = FALSE;
static AvahiClient *global_client = NULL;
static gboolean avahi_initialized = FALSE;
static void free_global_avahi_client (void);
static AvahiClient *get_global_avahi_client (GError **error);
static void ensure_avahi_resolver (GVfsDnsSdResolver *resolver);
static void service_resolver_cb (AvahiServiceResolver *resolver,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiResolverEvent event,
const char *name,
const char *type,
const char *domain,
const char *host_name,
const AvahiAddress *a,
uint16_t port,
AvahiStringList *txt,
AvahiLookupResultFlags flags,
void *user_data);
static GList *resolvers = NULL;
static void
clear_avahi_data (GVfsDnsSdResolver *resolver);
static void
remove_client_from_resolver (GVfsDnsSdResolver *resolver)
{
if (resolver->avahi_resolver != NULL)
{
avahi_service_resolver_free (resolver->avahi_resolver);
resolver->avahi_resolver = NULL;
}
clear_avahi_data (resolver);
}
static void
add_client_to_resolver (GVfsDnsSdResolver *resolver)
{
ensure_avahi_resolver (resolver);
}
/* Callback for state changes on the Client */
static void
avahi_client_callback (AvahiClient *client, AvahiClientState state, void *userdata)
{
if (global_client == NULL)
global_client = client;
if (state == AVAHI_CLIENT_FAILURE)
{
if (avahi_client_errno (client) == AVAHI_ERR_DISCONNECTED)
{
free_global_avahi_client ();
/* Attempt to reconnect */
get_global_avahi_client (NULL);
}
}
else if (state == AVAHI_CLIENT_S_RUNNING)
{
/* Start resolving again */
g_list_foreach (resolvers, (GFunc) add_client_to_resolver, NULL);
}
}
static void
free_global_avahi_client (void)
{
/* Remove current resolvers */
g_list_foreach (resolvers, (GFunc) remove_client_from_resolver, NULL);
/* Destroy client */
if (global_client)
{
avahi_client_free (global_client);
}
global_client = NULL;
avahi_initialized = FALSE;
}
static AvahiClient *
get_global_avahi_client (GError **error)
{
static AvahiGLibPoll *glib_poll = NULL;
int avahi_error;
if (!avahi_initialized)
{
avahi_initialized = TRUE;
if (glib_poll == NULL)
{
avahi_set_allocator (avahi_glib_allocator ());
glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
}
/* Create a new AvahiClient instance */
global_client = avahi_client_new (avahi_glib_poll_get (glib_poll),
AVAHI_CLIENT_NO_FAIL,
avahi_client_callback,
glib_poll,
&avahi_error);
if (global_client == NULL)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Error initializing Avahi: %s"),
avahi_strerror (avahi_error));
goto out;
}
}
out:
return global_client;
}
static gboolean
start_avahi_resolver (gpointer user_data)
{
GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (user_data);
AvahiClient *avahi_client;
avahi_client = get_global_avahi_client (NULL);
if (avahi_client == NULL)
goto out;
resolver->avahi_resolver = avahi_service_resolver_new (avahi_client,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
resolver->service_name,
resolver->service_type,
resolver->domain,
AVAHI_PROTO_UNSPEC,
0, /* AvahiLookupFlags */
service_resolver_cb,
resolver);
out:
resolver->start_avahi_resolver_id = 0;
g_object_unref (resolver);
return FALSE;
}
static void
ensure_avahi_resolver (GVfsDnsSdResolver *resolver)
{
if (resolver->avahi_resolver != NULL || resolver->start_avahi_resolver_id != 0)
return;
resolver->start_avahi_resolver_id = g_idle_add (start_avahi_resolver, g_object_ref (resolver));
}
static void
g_vfs_dns_sd_resolver_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (object);
switch (prop_id)
{
case PROP_ENCODED_TRIPLE:
g_value_set_string (value, resolver->encoded_triple);
break;
case PROP_REQUIRED_TXT_KEYS:
g_value_set_string (value, resolver->required_txt_keys);
break;
case PROP_SERVICE_NAME:
g_value_set_string (value, resolver->service_name);
break;
case PROP_SERVICE_TYPE:
g_value_set_string (value, resolver->service_type);
break;
case PROP_DOMAIN:
g_value_set_string (value, resolver->domain);
break;
case PROP_TIMEOUT_MSEC:
g_value_set_uint (value, resolver->timeout_msec);
break;
case PROP_IS_RESOLVED:
g_value_set_boolean (value, resolver->is_resolved);
break;
case PROP_ADDRESS:
g_value_set_string (value, resolver->address);
break;
case PROP_INTERFACE:
g_value_set_string (value, resolver->interface);
break;
case PROP_PORT:
g_value_set_uint (value, resolver->port);
break;
case PROP_TXT_RECORDS:
g_value_set_boxed (value, resolver->txt_records);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_vfs_dns_sd_resolver_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (object);
switch (prop_id)
{
case PROP_ENCODED_TRIPLE:
resolver->encoded_triple = g_strdup (g_value_get_string (value));
break;
case PROP_REQUIRED_TXT_KEYS:
resolver->required_txt_keys = g_strdup (g_value_get_string (value));
if (resolver->required_txt_keys != NULL)
{
/* TODO: maybe support escaping ',' */
resolver->required_txt_keys_broken_out = g_strsplit (resolver->required_txt_keys, ",", 0);
}
break;
case PROP_SERVICE_NAME:
resolver->service_name = g_strdup (g_value_get_string (value));
break;
case PROP_SERVICE_TYPE:
resolver->service_type = g_strdup (g_value_get_string (value));
break;
case PROP_DOMAIN:
resolver->domain = g_strdup (g_value_get_string (value));
break;
case PROP_TIMEOUT_MSEC:
resolver->timeout_msec = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_vfs_dns_sd_resolver_finalize (GObject *object)
{
GVfsDnsSdResolver *resolver;
resolver = G_VFS_DNS_SD_RESOLVER (object);
g_free (resolver->encoded_triple);
g_free (resolver->service_name);
g_free (resolver->service_type);
g_free (resolver->domain);
g_free (resolver->required_txt_keys);
g_strfreev (resolver->required_txt_keys_broken_out);
g_free (resolver->address);
g_free (resolver->interface);
g_strfreev (resolver->txt_records);
if (resolver->avahi_resolver != NULL)
avahi_service_resolver_free (resolver->avahi_resolver);
if (resolver->start_avahi_resolver_id != 0)
g_source_remove (resolver->start_avahi_resolver_id);
resolvers = g_list_remove (resolvers, resolver);
/* free the global avahi client for the last resolver */
if (resolvers == NULL)
{
free_global_avahi_client ();
}
G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->finalize (object);
}
static void
g_vfs_dns_sd_resolver_constructed (GObject *object)
{
GVfsDnsSdResolver *resolver;
resolver = G_VFS_DNS_SD_RESOLVER (object);
if (resolver->encoded_triple != NULL)
{
GError *error;
if (resolver->service_name != NULL)
{
g_warning ("Ignoring service-name since encoded-triple is already set");
g_free (resolver->service_name);
resolver->service_name = NULL;
}
if (resolver->service_type != NULL)
{
g_warning ("Ignoring service-type since encoded-triple is already set");
g_free (resolver->service_type);
resolver->service_type = NULL;
}
if (resolver->domain != NULL)
{
g_warning ("Ignoring domain since encoded-triple is already set");
g_free (resolver->domain);
resolver->domain = NULL;
}
error = NULL;
if (!g_vfs_decode_dns_sd_triple (resolver->encoded_triple,
&(resolver->service_name),
&(resolver->service_type),
&(resolver->domain),
&error))
{
/* GObject construction can't fail. So whine if the triple isn't valid. */
g_warning ("Malformed construction data passed: %s", error->message);
g_error_free (error);
g_free (resolver->encoded_triple);
g_free (resolver->service_name);
g_free (resolver->service_type);
g_free (resolver->domain);
resolver->encoded_triple = NULL;
resolver->service_name = NULL;
resolver->service_type = NULL;
resolver->domain = NULL;
goto out;
}
}
/* Always set encoded triple to what we encode; this is because we can decode
* an encoded triple that isn't 100% properly URI encoded, e.g.
*
* "davidz's public files on quad.fubar.dk._webdav._tcp.local"
*
* will be properly decoded. But we want to return a properly URI encoded triple
*
* "davidz%27s%20public%20files%20on%20quad%2efubar%2edk._webdav._tcp.local"
*
* for e.g. setting the GMountSpec. This is useful because the use can
* put the former into the pathbar in a file manager and then it will
* be properly rewritten on mount.
*/
g_free (resolver->encoded_triple);
resolver->encoded_triple = g_vfs_encode_dns_sd_triple (resolver->service_name,
resolver->service_type,
resolver->domain);
/* start resolving immediately */
ensure_avahi_resolver (resolver);
resolvers = g_list_prepend (resolvers, resolver);
out:
if (G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->constructed != NULL)
G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->constructed (object);
}
static void
g_vfs_dns_sd_resolver_class_init (GVfsDnsSdResolverClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
resolver_supports_mdns = (avahi_nss_support () > 0);
gobject_class->get_property = g_vfs_dns_sd_resolver_get_property;
gobject_class->set_property = g_vfs_dns_sd_resolver_set_property;
gobject_class->finalize = g_vfs_dns_sd_resolver_finalize;
gobject_class->constructed = g_vfs_dns_sd_resolver_constructed;
/**
* GVfsDnsSdResolver::changed:
* @resolver: The resolver emitting the signal.
*
* Emitted when resolved data changes.
*/
signals[CHANGED] = g_signal_new ("changed",
G_VFS_TYPE_DNS_SD_RESOLVER,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GVfsDnsSdResolverClass, changed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* GVfsDnsSdResolver:encoded-triple:
*
* The encoded DNS-SD triple for the resolver.
*/
g_object_class_install_property (gobject_class,
PROP_ENCODED_TRIPLE,
g_param_spec_string ("encoded-triple",
"Encoded triple",
"Encoded triple",
NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:required-txt-keys:
*
* A comma separated list of keys that must appear in the TXT
* records in order to consider the service being resolved.
*/
g_object_class_install_property (gobject_class,
PROP_REQUIRED_TXT_KEYS,
g_param_spec_string ("required-txt-keys",
"Required TXT keys",
"Required TXT keys",
NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:service-name:
*
* The name of the service for the resolver.
*/
g_object_class_install_property (gobject_class,
PROP_SERVICE_NAME,
g_param_spec_string ("service-name",
"Service Name",
"Service Name",
NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:service-type:
*
* The type of the service for the resolver.
*/
g_object_class_install_property (gobject_class,
PROP_SERVICE_TYPE,
g_param_spec_string ("service-type",
"Service Type",
"Service Type",
NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:domain:
*
* The domain for the resolver.
*/
g_object_class_install_property (gobject_class,
PROP_DOMAIN,
g_param_spec_string ("domain",
"Domain",
"Domain",
NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:timeout-msec:
*
* Timeout in milliseconds to use when resolving.
*/
g_object_class_install_property (gobject_class,
PROP_TIMEOUT_MSEC,
g_param_spec_uint ("timeout-msec",
"Timeout in milliseconds",
"Timeout in milliseconds",
0,
G_MAXUINT,
5000,
G_PARAM_CONSTRUCT |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:is-resolved:
*
* Whether the service is resolved.
*/
g_object_class_install_property (gobject_class,
PROP_IS_RESOLVED,
g_param_spec_boolean ("is-resolved",
"Is resolved",
"Is resolved",
FALSE,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:address:
*
* The resolved address.
*/
g_object_class_install_property (gobject_class,
PROP_DOMAIN,
g_param_spec_string ("address",
"Address",
"Address",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:interface:
*
* The resolved interface.
*/
g_object_class_install_property (gobject_class,
PROP_INTERFACE,
g_param_spec_string ("interface",
"Interface",
"Interface",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:port:
*
* The resolved port.
*/
g_object_class_install_property (gobject_class,
PROP_PORT,
g_param_spec_uint ("port",
"Port",
"Port",
0,
65536,
0,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
/**
* GVfsDnsSdResolver:txt-records:
*
* The resolved TXT records.
*/
g_object_class_install_property (gobject_class,
PROP_TXT_RECORDS,
g_param_spec_boxed ("txt-records",
"TXT Records",
"TXT Records",
G_TYPE_STRV,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_BLURB |
G_PARAM_STATIC_NICK));
}
static void
g_vfs_dns_sd_resolver_init (GVfsDnsSdResolver *resolver)
{
}
gboolean
g_vfs_dns_sd_resolver_is_resolved (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), FALSE);
return resolver->is_resolved;
}
const gchar *
g_vfs_dns_sd_resolver_get_encoded_triple (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return resolver->encoded_triple;
}
const gchar *
g_vfs_dns_sd_resolver_get_required_txt_keys (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return resolver->required_txt_keys;
}
const gchar *
g_vfs_dns_sd_resolver_get_service_name (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return resolver->service_name;
}
const gchar *
g_vfs_dns_sd_resolver_get_service_type (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return resolver->service_type;
}
const gchar *
g_vfs_dns_sd_resolver_get_domain (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return resolver->domain;
}
gchar *
g_vfs_dns_sd_resolver_get_address (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return g_strdup (resolver->address);
}
gchar *
g_vfs_dns_sd_resolver_get_interface (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return g_strdup (resolver->interface);
}
guint
g_vfs_dns_sd_resolver_get_port (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), (guint) -1);
return resolver->port;
}
gchar **
g_vfs_dns_sd_resolver_get_txt_records (GVfsDnsSdResolver *resolver)
{
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
return g_strdupv (resolver->txt_records);
}
gchar *
g_vfs_dns_sd_resolver_lookup_txt_record (GVfsDnsSdResolver *resolver,
const gchar *key)
{
gint n;
gchar *result;
gsize key_len;
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
g_return_val_if_fail (key != NULL, NULL);
result = NULL;
if (resolver->txt_records == NULL)
goto out;
key_len = strlen (key);
for (n = 0; resolver->txt_records[n] != NULL; n++)
{
const gchar *s = resolver->txt_records[n];
const gchar *p;
p = strchr (s, '=');
if (p != NULL && (p - s) == key_len)
{
if (g_ascii_strncasecmp (s,
key,
p - s) == 0)
{
result = g_strdup (p + 1);
goto out;
}
}
}
out:
return result;
}
GVfsDnsSdResolver *
g_vfs_dns_sd_resolver_new_for_encoded_triple (const gchar *encoded_triple,
const gchar *required_txt_keys)
{
g_return_val_if_fail (encoded_triple != NULL, NULL);
return G_VFS_DNS_SD_RESOLVER (g_object_new (G_VFS_TYPE_DNS_SD_RESOLVER,
"encoded-triple", encoded_triple,
"required-txt-keys", required_txt_keys,
NULL));
}
GVfsDnsSdResolver *
g_vfs_dns_sd_resolver_new_for_service (const gchar *service_name,
const gchar *service_type,
const gchar *domain,
const gchar *required_txt_keys)
{
g_return_val_if_fail (service_name != NULL, NULL);
g_return_val_if_fail (service_type != NULL, NULL);
g_return_val_if_fail (domain != NULL, NULL);
return G_VFS_DNS_SD_RESOLVER (g_object_new (G_VFS_TYPE_DNS_SD_RESOLVER,
"service-name", service_name,
"service-type", service_type,
"domain", domain,
"required-txt-keys", required_txt_keys,
NULL));
}
static int
safe_strcmp (const char *a, const char *b)
{
if (a == NULL)
a = "";
if (b == NULL)
b = "";
return strcmp (a, b);
}
static gboolean
strv_equal (char **a, char **b)
{
static char *dummy[1] = {NULL};
int n;
gboolean ret;
if (a == NULL)
a = dummy;
if (b == NULL)
b = dummy;
ret = FALSE;
if (g_strv_length (a) != g_strv_length (b))
goto out;
for (n = 0; a[n] != NULL && b[n] != NULL; n++)
{
if (strcmp (a[n], b[n]) != 0)
goto out;
}
ret = TRUE;
out:
return ret;
}
static gboolean
has_required_txt_keys (GVfsDnsSdResolver *resolver)
{
gboolean ret;
int n;
char *value;
ret = FALSE;
if (resolver->required_txt_keys_broken_out != NULL)
{
for (n = 0; resolver->required_txt_keys_broken_out[n] != NULL; n++)
{
value = g_vfs_dns_sd_resolver_lookup_txt_record (resolver,
resolver->required_txt_keys_broken_out[n]);
if (value == NULL)
{
/* key is missing */
goto out;
}
g_free (value);
}
}
ret = TRUE;
out:
return ret;
}
static void
set_avahi_data (GVfsDnsSdResolver *resolver,
const char *host_name,
AvahiProtocol protocol,
const AvahiAddress *a,
AvahiIfIndex interface,
uint16_t port,
AvahiStringList *txt)
{
char *address;
gchar ifname[IF_NAMESIZE] = {0};
gboolean changed;
AvahiStringList *l;
GPtrArray *p;
char **txt_records;
gboolean is_resolved;
changed = FALSE;
if (resolver_supports_mdns)
{
address = g_strdup (host_name);
}
else
{
char aa[128];
avahi_address_snprint (aa, sizeof(aa), a);
if (protocol == AVAHI_PROTO_INET6)
{
/* an ipv6 address, follow RFC 2732 */
address = g_strdup_printf ("[%s]", aa);
}
else
{
address = g_strdup (aa);
}
}
if (safe_strcmp (resolver->address, address) != 0)
{
g_free (resolver->address);
resolver->address = g_strdup (address);
g_object_notify (G_OBJECT (resolver), "address");
changed = TRUE;
}
g_free (address);
if_indextoname (interface, ifname);
if (safe_strcmp (resolver->interface, ifname) != 0)
{
g_free (resolver->interface);
resolver->interface = g_strdup (ifname);
g_object_notify (G_OBJECT (resolver), "interface");
changed = TRUE;
}
if (resolver->port != port)
{
resolver->port = port;
g_object_notify (G_OBJECT (resolver), "port");
changed = TRUE;
}
p = g_ptr_array_new ();
for (l = txt; l != NULL; l = avahi_string_list_get_next (l))
{
g_ptr_array_add (p, g_strdup ((const char *) l->text));
}
g_ptr_array_add (p, NULL);
txt_records = (char **) g_ptr_array_free (p, FALSE);
if (strv_equal (resolver->txt_records, txt_records))
{
g_strfreev (txt_records);
}
else
{
g_strfreev (resolver->txt_records);
resolver->txt_records = txt_records;
g_object_notify (G_OBJECT (resolver), "txt-records");
changed = TRUE;
}
is_resolved = has_required_txt_keys (resolver);
if (is_resolved != resolver->is_resolved)
{
resolver->is_resolved = is_resolved;
g_object_notify (G_OBJECT (resolver), "is-resolved");
changed = TRUE;
}
if (changed)
g_signal_emit (resolver, signals[CHANGED], 0);
}
static void
clear_avahi_data (GVfsDnsSdResolver *resolver)
{
gboolean changed;
changed = FALSE;
if (resolver->is_resolved)
{
resolver->is_resolved = FALSE;
g_object_notify (G_OBJECT (resolver), "is-resolved");
changed = TRUE;
}
if (resolver->address != NULL)
{
g_free (resolver->address);
resolver->address = NULL;
g_object_notify (G_OBJECT (resolver), "address");
changed = TRUE;
}
if (resolver->interface != NULL)
{
g_free (resolver->interface);
resolver->interface = NULL;
g_object_notify (G_OBJECT (resolver), "interface");
changed = TRUE;
}
if (resolver->port != 0)
{
resolver->port = 0;
g_object_notify (G_OBJECT (resolver), "port");
changed = TRUE;
}
if (resolver->txt_records != NULL)
{
resolver->txt_records = NULL;
g_object_notify (G_OBJECT (resolver), "txt-records");
changed = TRUE;
}
if (changed)
g_signal_emit (resolver, signals[CHANGED], 0);
}
static void
service_resolver_cb (AvahiServiceResolver *avahi_resolver,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiResolverEvent event,
const char *name,
const char *type,
const char *domain,
const char *host_name,
const AvahiAddress *a,
uint16_t port,
AvahiStringList *txt,
AvahiLookupResultFlags flags,
void *user_data)
{
GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (user_data);
switch (event)
{
case AVAHI_RESOLVER_FOUND:
set_avahi_data (resolver,
host_name,
protocol,
a,
interface,
port,
txt);
break;
case AVAHI_RESOLVER_FAILURE:
clear_avahi_data (resolver);
break;
}
}
typedef struct
{
GVfsDnsSdResolver *resolver;
guint timeout_id;
} ResolveData;
static void service_resolver_changed (GVfsDnsSdResolver *resolver, GTask *task);
static void
resolve_data_free (ResolveData *data)
{
if (data->timeout_id > 0)
g_source_remove (data->timeout_id);
g_signal_handlers_disconnect_by_func (data->resolver, service_resolver_changed, data);
g_free (data);
}
static void
service_resolver_changed (GVfsDnsSdResolver *resolver,
GTask *task)
{
ResolveData *data = g_task_get_task_data (task);
if (resolver->is_resolved)
{
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
else
{
if (data->resolver->address != NULL)
{
/* keep going until timeout if we're missing TXT records */
}
else
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
/* Translators:
* - the first %s refers to the service type
* - the second %s refers to the service name
* - the third %s refers to the domain
*/
_("Error resolving “%s” service “%s” on domain “%s”"),
data->resolver->service_type,
data->resolver->service_name,
data->resolver->domain);
g_object_unref (task);
}
}
}
static gboolean
service_resolver_timed_out (GTask *task)
{
ResolveData *data = g_task_get_task_data (task);
if (data->resolver->address != NULL)
{
/* special case if one of the required TXT records are missing */
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
/* Translators:
* - the first %s refers to the service type
* - the second %s refers to the service name
* - the third %s refers to the domain
* - the fourth %s refers to the required TXT keys
*/
_("Error resolving “%s” service “%s” on domain “%s”. "
"One or more TXT records are missing. Keys required: “%s”."),
data->resolver->service_type,
data->resolver->service_name,
data->resolver->domain,
data->resolver->required_txt_keys);
}
else
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
/* Translators:
* - the first %s refers to the service type
* - the second %s refers to the service name
* - the third %s refers to the domain
*/
_("Timed out resolving “%s” service “%s” on domain “%s”"),
data->resolver->service_type,
data->resolver->service_name,
data->resolver->domain);
}
data->timeout_id = 0;
g_object_unref (task);
return FALSE;
}
gboolean
g_vfs_dns_sd_resolver_resolve_finish (GVfsDnsSdResolver *resolver,
GAsyncResult *res,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (res, resolver), FALSE);
g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_dns_sd_resolver_resolve), FALSE);
return g_task_propagate_boolean (G_TASK (res), error);
}
void
g_vfs_dns_sd_resolver_resolve (GVfsDnsSdResolver *resolver,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
ResolveData *data;
GTask *task;
g_return_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver));
task = g_task_new (resolver, cancellable, callback, user_data);
g_task_set_source_tag (task, g_vfs_dns_sd_resolver_resolve);
if (resolver->service_type == NULL)
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Error initializing Avahi resolver"));
g_object_unref (task);
return;
}
if (resolver->is_resolved)
{
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
ensure_avahi_resolver (resolver);
data = g_new0 (ResolveData, 1);
data->resolver = resolver;
data->timeout_id = g_timeout_add (resolver->timeout_msec,
(GSourceFunc) service_resolver_timed_out,
task);
g_task_set_task_data (task, data, (GDestroyNotify) resolve_data_free);
g_signal_connect (resolver,
"changed",
(GCallback) service_resolver_changed,
task);
}
typedef struct
{
GMutex mutex;
GCond cond;
gboolean done;
GError *error;
gboolean ret;
} ResolveDataSync;
static void
resolve_sync_cb (GVfsDnsSdResolver *resolver,
GAsyncResult *res,
ResolveDataSync *data)
{
data->ret = g_vfs_dns_sd_resolver_resolve_finish (resolver,
res,
&(data->error));
g_mutex_lock (&data->mutex);
data->done = TRUE;
g_cond_signal (&data->cond);
g_mutex_unlock (&data->mutex);
}
/* Do not call from the global main loop thread. */
gboolean
g_vfs_dns_sd_resolver_resolve_sync (GVfsDnsSdResolver *resolver,
GCancellable *cancellable,
GError **error)
{
ResolveDataSync *data;
gboolean ret;
g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), FALSE);
data = g_new0 (ResolveDataSync, 1);
g_cond_init (&data->cond);
g_mutex_init (&data->mutex);
g_mutex_lock (&data->mutex);
g_vfs_dns_sd_resolver_resolve (resolver,
cancellable,
(GAsyncReadyCallback) resolve_sync_cb,
data);
while (!data->done)
g_cond_wait (&data->cond, &data->mutex);
g_mutex_unlock (&data->mutex);
ret = data->ret;
if (data->error != NULL)
g_propagate_error (error, data->error);
g_mutex_clear (&data->mutex);
g_cond_clear (&data->cond);
g_free (data);
return ret;
}